@xopcai/xopc 0.0.47 → 0.0.48

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 (62) hide show
  1. package/dist/extensions/telegram/src/command-handler.js +1 -1
  2. package/dist/extensions/telegram/src/command-handler.js.map +1 -1
  3. package/dist/extensions/telegram/xopc.extension.json +1 -1
  4. package/dist/gateway/static/root/assets/{agents-DdWPgyn-.js → agents-BdISC5UA.js} +2 -2
  5. package/dist/gateway/static/root/assets/{agents-DdWPgyn-.js.map → agents-BdISC5UA.js.map} +1 -1
  6. package/dist/gateway/static/root/assets/{apps-page-BTi_W1y1.js → apps-page-CXU_Jg95.js} +2 -2
  7. package/dist/gateway/static/root/assets/{apps-page-BTi_W1y1.js.map → apps-page-CXU_Jg95.js.map} +1 -1
  8. package/dist/gateway/static/root/assets/{channels-settings-CjUmKQrC.js → channels-settings-Doe1ciOW.js} +2 -2
  9. package/dist/gateway/static/root/assets/{channels-settings-CjUmKQrC.js.map → channels-settings-Doe1ciOW.js.map} +1 -1
  10. package/dist/gateway/static/root/assets/{cron-dreaming-jobs-DinMur-Z.js → cron-dreaming-jobs-C-V4_3vz.js} +2 -2
  11. package/dist/gateway/static/root/assets/{cron-dreaming-jobs-DinMur-Z.js.map → cron-dreaming-jobs-C-V4_3vz.js.map} +1 -1
  12. package/dist/gateway/static/root/assets/{cron-page-Bu05Z2oL.js → cron-page-CIIy81K6.js} +2 -2
  13. package/dist/gateway/static/root/assets/{cron-page-Bu05Z2oL.js.map → cron-page-CIIy81K6.js.map} +1 -1
  14. package/dist/gateway/static/root/assets/{dist-wDej8fSi.js → dist-VW7dXc5X.js} +2 -2
  15. package/dist/gateway/static/root/assets/{dist-wDej8fSi.js.map → dist-VW7dXc5X.js.map} +1 -1
  16. package/dist/gateway/static/root/assets/{extension-debug-page-CZBu7-zM.js → extension-debug-page-Cslwx62-.js} +2 -2
  17. package/dist/gateway/static/root/assets/{extension-debug-page-CZBu7-zM.js.map → extension-debug-page-Cslwx62-.js.map} +1 -1
  18. package/dist/gateway/static/root/assets/{extension-page-CnOyLPrh.js → extension-page-Dzyebr6T.js} +2 -2
  19. package/dist/gateway/static/root/assets/{extension-page-CnOyLPrh.js.map → extension-page-Dzyebr6T.js.map} +1 -1
  20. package/dist/gateway/static/root/assets/{extension-settings-page-BOHn3S1a.js → extension-settings-page-B07uetL_.js} +2 -2
  21. package/dist/gateway/static/root/assets/{extension-settings-page-BOHn3S1a.js.map → extension-settings-page-B07uetL_.js.map} +1 -1
  22. package/dist/gateway/static/root/assets/{heartbeat-config-api-OqFXdrMO.js → heartbeat-config-api-kmvGNriW.js} +2 -2
  23. package/dist/gateway/static/root/assets/{heartbeat-config-api-OqFXdrMO.js.map → heartbeat-config-api-kmvGNriW.js.map} +1 -1
  24. package/dist/gateway/static/root/assets/{index-DeELk--t.js → index-DW6JvymK.js} +11 -11
  25. package/dist/gateway/static/root/assets/{index-DeELk--t.js.map → index-DW6JvymK.js.map} +1 -1
  26. package/dist/gateway/static/root/assets/{logs-page-DiN42-yE.js → logs-page-BiRnV2Ex.js} +2 -2
  27. package/dist/gateway/static/root/assets/{logs-page-DiN42-yE.js.map → logs-page-BiRnV2Ex.js.map} +1 -1
  28. package/dist/gateway/static/root/assets/{sessions-page-B5oxRfRm.js → sessions-page-DQq0QaQh.js} +2 -2
  29. package/dist/gateway/static/root/assets/{sessions-page-B5oxRfRm.js.map → sessions-page-DQq0QaQh.js.map} +1 -1
  30. package/dist/gateway/static/root/assets/{settings-page-DaRY3XEp.js → settings-page-BV_l8nJ3.js} +2 -2
  31. package/dist/gateway/static/root/assets/{settings-page-DaRY3XEp.js.map → settings-page-BV_l8nJ3.js.map} +1 -1
  32. package/dist/gateway/static/root/assets/{skills-page-BGDLiQZ6.js → skills-page-Bv0pphDM.js} +2 -2
  33. package/dist/gateway/static/root/assets/{skills-page-BGDLiQZ6.js.map → skills-page-Bv0pphDM.js.map} +1 -1
  34. package/dist/gateway/static/root/assets/{use-image-provider-credentials-DcP2SYn3.js → use-image-provider-credentials-BPcW1K0N.js} +2 -2
  35. package/dist/gateway/static/root/assets/{use-image-provider-credentials-DcP2SYn3.js.map → use-image-provider-credentials-BPcW1K0N.js.map} +1 -1
  36. package/dist/gateway/static/root/index.html +1 -1
  37. package/dist/package.js +1 -1
  38. package/dist/src/agent/goals/checklist-judge.js +21 -17
  39. package/dist/src/agent/goals/checklist-judge.js.map +1 -1
  40. package/dist/src/agent/goals/evaluate-turn.js +6 -11
  41. package/dist/src/agent/goals/evaluate-turn.js.map +1 -1
  42. package/dist/src/agent/goals/judge.d.ts +16 -0
  43. package/dist/src/agent/goals/judge.js +61 -13
  44. package/dist/src/agent/goals/judge.js.map +1 -1
  45. package/dist/src/agent/goals/state.d.ts +7 -0
  46. package/dist/src/agent/goals/state.js +24 -1
  47. package/dist/src/agent/goals/state.js.map +1 -1
  48. package/dist/src/chat-commands/builtins/model.d.ts +2 -2
  49. package/dist/src/chat-commands/builtins/model.js +10 -8
  50. package/dist/src/chat-commands/builtins/model.js.map +1 -1
  51. package/dist/src/chat-commands/builtins/system.js +1 -1
  52. package/dist/src/chat-commands/builtins/system.js.map +1 -1
  53. package/dist/src/tui/backends/embedded-backend.d.ts +1 -1
  54. package/dist/src/tui/backends/embedded-backend.js +24 -3
  55. package/dist/src/tui/backends/embedded-backend.js.map +1 -1
  56. package/dist/src/tui/backends/gateway-sse-backend.js +36 -10
  57. package/dist/src/tui/backends/gateway-sse-backend.js.map +1 -1
  58. package/dist/src/tui/tui-commands.js +1 -1
  59. package/dist/src/tui/tui-commands.js.map +1 -1
  60. package/dist/src/tui/tui.js +12 -1
  61. package/dist/src/tui/tui.js.map +1 -1
  62. package/package.json +1 -1
@@ -1,2 +1,2 @@
1
- import{i as e}from"./rolldown-runtime-DWdDZTNf.js";import{i as t,t as n}from"./vendor-react-DbimaAId.js";import{o as r}from"./vendor-swr-B5fPo7KK.js";import{An as i,In as a,Jr as o,Mn as s,Or as c,St as l,Xr as u,Y as d,Yr as f,c as p,ci as m,dn as h,gn as g,l as _,n as v,ti as y,ur as b,vn as x,wi as S,xt as C}from"./index-DeELk--t.js";var w=`/api/image/providers`;async function T(){return(await g(h(`/api/image/providers`)))?.payload?.providers??[]}var E=e(t(),1);function D(){return{apiKey:``,region:``,baseUrl:``,imageBaseUrl:``}}function O(e){return e?.apiKey?`••••••••••••`:``}function k(e,t){let n=(()=>{if(!e||typeof e!=`object`||!(`providersConfig`in e))return;let t=e.providersConfig;if(!(!t||typeof t!=`object`||Array.isArray(t)))return t})(),r={};for(let e of t){let t=n?.[e];r[e]={apiKey:O(t),region:t?.region??``,baseUrl:t?.baseUrl??``,imageBaseUrl:t?.imageBaseUrl??``}}return r}function A(e,t,n){let r=e[n].trim();if(r!==t[n].trim())return r||null}function j(e,t){let n=e.trim(),r=t.trim();if(n!==r&&!(v(n)&&v(r)))return n||(r?null:void 0)}function M(e,t,n){let r={};for(let i of e){let e=t[i]??D(),a=n[i]??D();if(JSON.stringify(e)===JSON.stringify(a))continue;let o={},s=j(e.apiKey,a.apiKey);s!==void 0&&(o.apiKey=s);let c=A(e,a,`region`);c!==void 0&&(o.region=c);let l=A(e,a,`baseUrl`);l!==void 0&&(o.baseUrl=l);let u=A(e,a,`imageBaseUrl`);u!==void 0&&(o.imageBaseUrl=u),Object.keys(o).length>0&&(r[i]=o)}return r}async function N(e){let t=await g(h(`/api/image/providers/${encodeURIComponent(e)}/reveal-api-key`),{method:`POST`,headers:{"Content-Type":`application/json`},body:`{}`});if(!t.ok||!t.payload)throw Error(t.error?.message??`Reveal failed`);return t.payload}async function P(e){Object.keys(e).length!==0&&(await g(h(`/api/config`),{method:`PATCH`,body:JSON.stringify({providersConfig:e})}),await C())}var F=n();function I({providerId:e,value:t,onChange:n,labels:r,apiKeyLinks:s,apiKeyLinkLabels:l}){let[p,h]=(0,E.useState)(!1),[g,b]=(0,E.useState)(void 0),[x,S]=(0,E.useState)(!1),[C,w]=(0,E.useState)(null),[T,D]=(0,E.useState)(!1),O=v(t);(0,E.useEffect)(()=>{O||(b(void 0),w(null))},[O,t]);let k=O&&p&&typeof g==`string`?g:t,A=!O||O&&p&&typeof g==`string`?`text`:`password`,j=!O&&t.trim().length>0&&!v(t)||!!p&&typeof g==`string`&&g.length>0,M=(0,E.useCallback)(async()=>{let e=!O&&t.trim()&&!v(t)?t.trim():typeof g==`string`&&g.length>0?g:``;if(e)try{await navigator.clipboard.writeText(e),D(!0),window.setTimeout(()=>D(!1),2e3)}catch{}},[O,g,t]),P=(0,E.useCallback)(async()=>{if(w(null),!O){h(e=>!e);return}if(g!==void 0){h(e=>!e);return}S(!0);try{b((await N(e)).apiKey??null),h(!0)}catch(e){w(e instanceof Error?e.message:r.loadFailed),b(null)}finally{S(!1)}},[O,e,g,r.loadFailed]);return(0,F.jsxs)(`div`,{className:`flex min-w-0 flex-col gap-1 sm:col-span-2`,children:[(0,F.jsx)(`label`,{className:`text-xs font-medium text-fg-muted`,htmlFor:`img-cred-key-${e}`,children:r.apiKeyLabel}),s.length>0?(0,F.jsx)(`div`,{className:`flex flex-col gap-1`,children:s.map(e=>(0,F.jsxs)(`a`,{href:e.href,target:`_blank`,rel:`noopener noreferrer`,className:`inline-flex w-fit items-center gap-1 text-xs font-medium text-accent-fg hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent`,children:[_(e.kind,l),(0,F.jsx)(u,{className:`size-3`,"aria-hidden":!0})]},`${e.kind}-${e.href}`))}):null,O?(0,F.jsx)(`p`,{className:`text-[11px] text-fg-subtle`,children:r.maskedHelp}):null,(0,F.jsxs)(`div`,{className:`relative min-w-0`,children:[(0,F.jsx)(`input`,{id:`img-cred-key-${e}`,type:A,autoComplete:`off`,spellCheck:!1,className:a(`w-full rounded-lg border border-edge bg-surface-panel py-2 pl-3 pr-24 font-mono text-sm text-fg`,`placeholder:text-fg-subtle`,i),value:k,placeholder:O?`••••••••`:r.optionalPlaceholder,onChange:e=>{let t=e.target.value;O&&typeof g==`string`&&p&&t!==g&&(b(void 0),h(!1)),n(t)}}),(0,F.jsxs)(`div`,{className:`absolute right-1 top-1/2 flex -translate-y-1/2 gap-0.5`,children:[j?(0,F.jsx)(`button`,{type:`button`,className:a(`rounded p-1.5 text-fg-subtle hover:bg-surface-hover hover:text-fg`,d.transition,d.press,d.focusRingPanel),title:T?r.copied:r.copy,"aria-label":T?r.copied:r.copy,onClick:()=>void M(),children:T?(0,F.jsx)(m,{className:`size-4`}):(0,F.jsx)(y,{className:`size-4`})}):null,(0,F.jsx)(`button`,{type:`button`,className:a(`rounded p-1.5 text-fg-subtle hover:bg-surface-hover hover:text-fg disabled:opacity-40`,d.transition,d.press,d.focusRingPanel),title:p?r.hide:r.show,"aria-label":p?r.hide:r.show,disabled:x,onClick:()=>void P(),children:x?(0,F.jsx)(c,{className:`size-4 animate-spin`,"aria-hidden":!0}):p?(0,F.jsx)(f,{className:`size-4`,"aria-hidden":!0}):(0,F.jsx)(o,{className:`size-4`,"aria-hidden":!0})})]})]}),O&&p&&g===null&&!C?(0,F.jsx)(`p`,{className:`text-xs text-amber-700 dark:text-amber-400/90`,children:r.notInConfigFile}):null,C?(0,F.jsx)(`p`,{className:`text-xs text-red-600 dark:text-red-400`,children:C}):null]})}function L(){return a(`w-full rounded-lg border border-edge bg-surface-panel px-3 py-2 text-sm text-fg`,`placeholder:text-fg-subtle`,i)}function R(){return a(L(),`appearance-none bg-[length:1rem] bg-[right_0.5rem_center] bg-no-repeat pr-9`)}var z=`__custom__`;function B(e,t){if(!e.region.trim()&&!e.imageBaseUrl.trim())return``;let n=e.region.trim().toLowerCase();return t.some(e=>e.value===n)?n:z}function V(e,t){let n=e.baseUrl.trim().replace(/\/+$/,``);if(!n)return``;let r=t.map(e=>e.value.replace(/\/+$/,``)).indexOf(n);return r>=0?t[r].value:z}function H(e,t,n){return t===`beijing`?e.dashscopeRegion_beijing:t===`singapore`?e.dashscopeRegion_singapore:t===`us`?e.dashscopeRegion_us:n}function U(e,t){return t===`minimax`?e.minimaxClusterLabel:t===`fal`?e.falQueueBaseLabel:e.baseUrlLabel}function W(e,t){return t===`minimax`?e.minimaxClusterHint:t===`fal`?e.falQueueBaseHint:null}function G({summaries:e,credDraft:t,credDirty:n,credSaving:r,credError:i,credSavedFlash:o,credNoopFlash:l,updateCredRow:d,onDiscardCredentials:f,onSaveCredentials:m,extensionIds:h,showExtensionLinks:g,showImageModelsLink:_,language:v,apiKeyLinkLabels:y,messages:x}){if(e.length===0)return null;let C=e.some(e=>(e.ui?.regions?.length??0)>0),w=e.some(e=>(e.ui?.baseUrlPresets?.length??0)>0);return(0,F.jsxs)(`div`,{className:`flex flex-col gap-4`,children:[(0,F.jsxs)(`div`,{className:`flex flex-col gap-1 text-xs leading-relaxed text-fg-muted`,children:[(0,F.jsx)(`p`,{children:x.credentialsIntro}),C?(0,F.jsx)(`p`,{className:`text-fg-subtle`,children:x.regionHint}):null,w?(0,F.jsx)(`p`,{className:`text-fg-subtle`,children:x.endpointPresetsHint}):null,_?(0,F.jsx)(`p`,{children:(0,F.jsx)(S,{to:`/settings/image-models`,className:`font-medium text-accent hover:underline`,title:x.imageModelsLinkTitle,children:x.openImageModelsPage})}):null]}),i?(0,F.jsx)(`div`,{className:`rounded-lg border border-red-500/30 bg-red-500/10 px-3 py-2 text-sm text-red-700 dark:text-red-300`,children:i}):null,(0,F.jsxs)(`div`,{className:`flex flex-wrap items-center justify-end gap-2`,children:[o?(0,F.jsx)(`span`,{className:`text-sm text-fg-muted`,children:x.credentialsSaved}):null,l?(0,F.jsx)(`span`,{className:`text-sm text-fg-muted`,children:x.credentialsNothingToSave}):null,(0,F.jsx)(s,{type:`button`,variant:`secondary`,onClick:f,disabled:!n||r,children:x.discardCredentials}),(0,F.jsx)(s,{type:`button`,variant:`primary`,onClick:m,disabled:!n||r,children:r?(0,F.jsxs)(F.Fragment,{children:[(0,F.jsx)(c,{className:`size-3.5 animate-spin`}),(0,F.jsx)(`span`,{className:`ml-1.5`,children:x.savingCredentials})]}):(0,F.jsxs)(F.Fragment,{children:[(0,F.jsx)(b,{className:`size-3.5`}),(0,F.jsx)(`span`,{className:`ml-1.5`,children:x.saveCredentials})]})})]}),(0,F.jsx)(`div`,{className:`flex flex-col gap-4`,children:e.map(e=>{let n=t[e.id]??D(),r=e.ui,i=g&&h.has(e.id)?`/settings/ext/${encodeURIComponent(e.id)}`:null;return(0,F.jsxs)(`div`,{className:`rounded-lg border border-edge bg-surface-panel px-4 py-3 shadow-sm dark:shadow-none`,children:[(0,F.jsxs)(`div`,{className:`flex flex-wrap items-center justify-between gap-3`,children:[(0,F.jsxs)(`div`,{className:`flex min-w-0 flex-wrap items-center gap-2`,children:[(0,F.jsx)(`span`,{className:`text-sm font-semibold text-fg`,children:e.label??e.id}),(0,F.jsxs)(`span`,{className:`text-xs text-fg-subtle`,children:[`(`,e.id,`)`]}),i?(0,F.jsxs)(S,{to:i,className:`inline-flex items-center gap-1 text-xs font-medium text-accent hover:underline`,title:x.extensionSettingsLinkTitle,children:[(0,F.jsx)(u,{className:`size-3`}),x.openExtensionSettings]}):null]}),e.configured?(0,F.jsx)(`span`,{className:`rounded-full bg-accent-soft px-2 py-0.5 text-xs font-medium text-accent-fg`,children:x.configured}):(0,F.jsx)(`span`,{className:`rounded-full border border-amber-500/40 bg-amber-500/10 px-2 py-0.5 text-xs font-medium text-amber-700 dark:text-amber-300`,children:x.missingKey})]}),e.defaultModel?(0,F.jsxs)(`p`,{className:`mt-1 text-xs text-fg-subtle`,children:[(0,F.jsxs)(`span`,{className:`text-fg-muted`,children:[x.defaultModel,`:`]}),` `,e.id,`/`,e.defaultModel]}):null,e.models.length>0?(0,F.jsxs)(`p`,{className:`mt-0.5 text-xs text-fg-subtle`,children:[(0,F.jsxs)(`span`,{className:`text-fg-muted`,children:[x.modelsLabel,`:`]}),` `,e.models.map(t=>`${e.id}/${t}`).join(`, `)]}):null,(0,F.jsxs)(`div`,{className:`mt-4 grid gap-3 sm:grid-cols-2`,children:[(0,F.jsx)(I,{providerId:e.id,value:n.apiKey,onChange:t=>d(e.id,{apiKey:t}),apiKeyLinks:p(e.id,v),apiKeyLinkLabels:y,labels:{apiKeyLabel:x.apiKeyLabel,optionalPlaceholder:x.optionalPlaceholder,maskedHelp:x.apiKeyMaskedHelp,copy:x.apiKeyCopy,copied:x.apiKeyCopied,show:x.apiKeyShow,hide:x.apiKeyHide,notInConfigFile:x.apiKeyNotInConfigFile,loadFailed:x.apiKeyRevealFailed}}),r?.regions?.length?(0,F.jsxs)(`div`,{className:`flex min-w-0 flex-col gap-1 sm:col-span-2`,children:[(0,F.jsx)(`label`,{className:`text-xs font-medium text-fg-muted`,htmlFor:`img-cred-region-preset-${e.id}`,children:x.regionLabel}),(0,F.jsxs)(`select`,{id:`img-cred-region-preset-${e.id}`,className:R(),value:B(n,r.regions),onChange:t=>{let n=t.target.value;if(n===``){d(e.id,{region:``,imageBaseUrl:``});return}if(n===z){d(e.id,{region:``,imageBaseUrl:``});return}let i=r.regions.find(e=>e.value===n);i&&d(e.id,{region:i.value,imageBaseUrl:i.imageBaseUrl})},children:[(0,F.jsx)(`option`,{value:``,children:x.regionPresetDefault}),r.regions.map(e=>(0,F.jsx)(`option`,{value:e.value,children:H(x,e.value,e.label)},e.value)),(0,F.jsx)(`option`,{value:z,children:x.regionPresetCustom})]}),B(n,r.regions)===z?(0,F.jsxs)(`div`,{className:`mt-2 grid gap-2 sm:grid-cols-2`,children:[(0,F.jsx)(`input`,{type:`text`,className:L(),value:n.region,placeholder:`region`,onChange:t=>d(e.id,{region:t.target.value})}),(0,F.jsx)(`input`,{type:`url`,className:L(),value:n.imageBaseUrl,placeholder:x.imageBaseUrlLabel,onChange:t=>d(e.id,{imageBaseUrl:t.target.value})})]}):null]}):null,r?.baseUrlPresets?.length?(0,F.jsxs)(`div`,{className:`flex min-w-0 flex-col gap-1 sm:col-span-2`,children:[(0,F.jsx)(`label`,{className:`text-xs font-medium text-fg-muted`,htmlFor:`img-cred-base-preset-${e.id}`,children:U(x,r.baseUrlPresetKind)}),W(x,r.baseUrlPresetKind)?(0,F.jsx)(`p`,{className:`text-[11px] text-fg-subtle`,children:W(x,r.baseUrlPresetKind)}):null,(0,F.jsxs)(`select`,{id:`img-cred-base-preset-${e.id}`,className:R(),value:V(n,r.baseUrlPresets),onChange:t=>{let n=t.target.value;if(n===``){d(e.id,{baseUrl:``});return}if(n===z){d(e.id,{baseUrl:``});return}d(e.id,{baseUrl:n.replace(/\/+$/,``)})},children:[(0,F.jsx)(`option`,{value:``,children:x.baseUrlPresetDefault}),r.baseUrlPresets.map(e=>(0,F.jsx)(`option`,{value:e.value,children:e.label},e.value)),(0,F.jsx)(`option`,{value:z,children:x.baseUrlPresetCustom})]}),V(n,r.baseUrlPresets)===z?(0,F.jsx)(`input`,{type:`url`,className:a(L(),`mt-2`),value:n.baseUrl,placeholder:`https://…`,onChange:t=>d(e.id,{baseUrl:t.target.value})}):null]}):null,r?.regions?.length&&B(n,r.regions)!==z?(0,F.jsxs)(`div`,{className:`flex min-w-0 flex-col gap-1 sm:col-span-2`,children:[(0,F.jsx)(`label`,{className:`text-xs font-medium text-fg-muted`,htmlFor:`img-cred-imgbase-ro-${e.id}`,children:x.imageBaseUrlLabel}),(0,F.jsx)(`input`,{id:`img-cred-imgbase-ro-${e.id}`,type:`url`,readOnly:!0,className:a(L(),`cursor-not-allowed opacity-90`),value:n.imageBaseUrl,title:x.imageBaseUrlPresetHint}),(0,F.jsx)(`p`,{className:`text-[11px] text-fg-subtle`,children:x.imageBaseUrlPresetHint})]}):null]})]},e.id)})})]})}function K(e){let t=l(x(e=>!!e.token)),n=t.data,i=(0,E.useMemo)(()=>e.map(e=>e.id),[e]),[a,o]=(0,E.useState)({}),[s,c]=(0,E.useState)({}),[u,d]=(0,E.useState)(!1),[f,p]=(0,E.useState)(null),[m,g]=(0,E.useState)(!1),[_,v]=(0,E.useState)(!1),y=(0,E.useMemo)(()=>k(n?.payload?.config,i),[n?.payload?.config,i]),b=(0,E.useMemo)(()=>JSON.stringify(a)!==JSON.stringify(s),[a,s]);return(0,E.useEffect)(()=>{b||(o(structuredClone(y)),c(structuredClone(y)))},[y,b]),{gwSwr:t,credDraft:a,credBaseline:s,credDirty:b,credSaving:u,credError:f,credSavedFlash:m,credNoopFlash:_,updateCredRow:(0,E.useCallback)((e,t)=>{o(n=>{let r=n[e]??D();return{...n,[e]:{...r,...t}}})},[]),onDiscardCredentials:(0,E.useCallback)(()=>{o(structuredClone(s)),p(null),g(!1),v(!1)},[s]),saveCredentials:(0,E.useCallback)(async e=>{let n=M(i,a,s);if(Object.keys(n).length===0){v(!0),window.setTimeout(()=>v(!1),2200);return}d(!0),p(null),g(!1);try{await P(n);let e=await t.mutate?.();r(h(w));let a=k(e?.payload?.config,i);o(structuredClone(a)),c(structuredClone(a)),g(!0),window.setTimeout(()=>g(!1),2e3)}catch(t){p(t instanceof Error?t.message:e)}finally{d(!1)}},[i,a,s,t])}}export{w as i,G as n,T as r,K as t};
2
- //# sourceMappingURL=use-image-provider-credentials-DcP2SYn3.js.map
1
+ import{i as e}from"./rolldown-runtime-DWdDZTNf.js";import{i as t,t as n}from"./vendor-react-DbimaAId.js";import{o as r}from"./vendor-swr-B5fPo7KK.js";import{An as i,In as a,Jr as o,Mn as s,Or as c,St as l,Xr as u,Y as d,Yr as f,c as p,ci as m,dn as h,gn as g,l as _,n as v,ti as y,ur as b,vn as x,wi as S,xt as C}from"./index-DW6JvymK.js";var w=`/api/image/providers`;async function T(){return(await g(h(`/api/image/providers`)))?.payload?.providers??[]}var E=e(t(),1);function D(){return{apiKey:``,region:``,baseUrl:``,imageBaseUrl:``}}function O(e){return e?.apiKey?`••••••••••••`:``}function k(e,t){let n=(()=>{if(!e||typeof e!=`object`||!(`providersConfig`in e))return;let t=e.providersConfig;if(!(!t||typeof t!=`object`||Array.isArray(t)))return t})(),r={};for(let e of t){let t=n?.[e];r[e]={apiKey:O(t),region:t?.region??``,baseUrl:t?.baseUrl??``,imageBaseUrl:t?.imageBaseUrl??``}}return r}function A(e,t,n){let r=e[n].trim();if(r!==t[n].trim())return r||null}function j(e,t){let n=e.trim(),r=t.trim();if(n!==r&&!(v(n)&&v(r)))return n||(r?null:void 0)}function M(e,t,n){let r={};for(let i of e){let e=t[i]??D(),a=n[i]??D();if(JSON.stringify(e)===JSON.stringify(a))continue;let o={},s=j(e.apiKey,a.apiKey);s!==void 0&&(o.apiKey=s);let c=A(e,a,`region`);c!==void 0&&(o.region=c);let l=A(e,a,`baseUrl`);l!==void 0&&(o.baseUrl=l);let u=A(e,a,`imageBaseUrl`);u!==void 0&&(o.imageBaseUrl=u),Object.keys(o).length>0&&(r[i]=o)}return r}async function N(e){let t=await g(h(`/api/image/providers/${encodeURIComponent(e)}/reveal-api-key`),{method:`POST`,headers:{"Content-Type":`application/json`},body:`{}`});if(!t.ok||!t.payload)throw Error(t.error?.message??`Reveal failed`);return t.payload}async function P(e){Object.keys(e).length!==0&&(await g(h(`/api/config`),{method:`PATCH`,body:JSON.stringify({providersConfig:e})}),await C())}var F=n();function I({providerId:e,value:t,onChange:n,labels:r,apiKeyLinks:s,apiKeyLinkLabels:l}){let[p,h]=(0,E.useState)(!1),[g,b]=(0,E.useState)(void 0),[x,S]=(0,E.useState)(!1),[C,w]=(0,E.useState)(null),[T,D]=(0,E.useState)(!1),O=v(t);(0,E.useEffect)(()=>{O||(b(void 0),w(null))},[O,t]);let k=O&&p&&typeof g==`string`?g:t,A=!O||O&&p&&typeof g==`string`?`text`:`password`,j=!O&&t.trim().length>0&&!v(t)||!!p&&typeof g==`string`&&g.length>0,M=(0,E.useCallback)(async()=>{let e=!O&&t.trim()&&!v(t)?t.trim():typeof g==`string`&&g.length>0?g:``;if(e)try{await navigator.clipboard.writeText(e),D(!0),window.setTimeout(()=>D(!1),2e3)}catch{}},[O,g,t]),P=(0,E.useCallback)(async()=>{if(w(null),!O){h(e=>!e);return}if(g!==void 0){h(e=>!e);return}S(!0);try{b((await N(e)).apiKey??null),h(!0)}catch(e){w(e instanceof Error?e.message:r.loadFailed),b(null)}finally{S(!1)}},[O,e,g,r.loadFailed]);return(0,F.jsxs)(`div`,{className:`flex min-w-0 flex-col gap-1 sm:col-span-2`,children:[(0,F.jsx)(`label`,{className:`text-xs font-medium text-fg-muted`,htmlFor:`img-cred-key-${e}`,children:r.apiKeyLabel}),s.length>0?(0,F.jsx)(`div`,{className:`flex flex-col gap-1`,children:s.map(e=>(0,F.jsxs)(`a`,{href:e.href,target:`_blank`,rel:`noopener noreferrer`,className:`inline-flex w-fit items-center gap-1 text-xs font-medium text-accent-fg hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent`,children:[_(e.kind,l),(0,F.jsx)(u,{className:`size-3`,"aria-hidden":!0})]},`${e.kind}-${e.href}`))}):null,O?(0,F.jsx)(`p`,{className:`text-[11px] text-fg-subtle`,children:r.maskedHelp}):null,(0,F.jsxs)(`div`,{className:`relative min-w-0`,children:[(0,F.jsx)(`input`,{id:`img-cred-key-${e}`,type:A,autoComplete:`off`,spellCheck:!1,className:a(`w-full rounded-lg border border-edge bg-surface-panel py-2 pl-3 pr-24 font-mono text-sm text-fg`,`placeholder:text-fg-subtle`,i),value:k,placeholder:O?`••••••••`:r.optionalPlaceholder,onChange:e=>{let t=e.target.value;O&&typeof g==`string`&&p&&t!==g&&(b(void 0),h(!1)),n(t)}}),(0,F.jsxs)(`div`,{className:`absolute right-1 top-1/2 flex -translate-y-1/2 gap-0.5`,children:[j?(0,F.jsx)(`button`,{type:`button`,className:a(`rounded p-1.5 text-fg-subtle hover:bg-surface-hover hover:text-fg`,d.transition,d.press,d.focusRingPanel),title:T?r.copied:r.copy,"aria-label":T?r.copied:r.copy,onClick:()=>void M(),children:T?(0,F.jsx)(m,{className:`size-4`}):(0,F.jsx)(y,{className:`size-4`})}):null,(0,F.jsx)(`button`,{type:`button`,className:a(`rounded p-1.5 text-fg-subtle hover:bg-surface-hover hover:text-fg disabled:opacity-40`,d.transition,d.press,d.focusRingPanel),title:p?r.hide:r.show,"aria-label":p?r.hide:r.show,disabled:x,onClick:()=>void P(),children:x?(0,F.jsx)(c,{className:`size-4 animate-spin`,"aria-hidden":!0}):p?(0,F.jsx)(f,{className:`size-4`,"aria-hidden":!0}):(0,F.jsx)(o,{className:`size-4`,"aria-hidden":!0})})]})]}),O&&p&&g===null&&!C?(0,F.jsx)(`p`,{className:`text-xs text-amber-700 dark:text-amber-400/90`,children:r.notInConfigFile}):null,C?(0,F.jsx)(`p`,{className:`text-xs text-red-600 dark:text-red-400`,children:C}):null]})}function L(){return a(`w-full rounded-lg border border-edge bg-surface-panel px-3 py-2 text-sm text-fg`,`placeholder:text-fg-subtle`,i)}function R(){return a(L(),`appearance-none bg-[length:1rem] bg-[right_0.5rem_center] bg-no-repeat pr-9`)}var z=`__custom__`;function B(e,t){if(!e.region.trim()&&!e.imageBaseUrl.trim())return``;let n=e.region.trim().toLowerCase();return t.some(e=>e.value===n)?n:z}function V(e,t){let n=e.baseUrl.trim().replace(/\/+$/,``);if(!n)return``;let r=t.map(e=>e.value.replace(/\/+$/,``)).indexOf(n);return r>=0?t[r].value:z}function H(e,t,n){return t===`beijing`?e.dashscopeRegion_beijing:t===`singapore`?e.dashscopeRegion_singapore:t===`us`?e.dashscopeRegion_us:n}function U(e,t){return t===`minimax`?e.minimaxClusterLabel:t===`fal`?e.falQueueBaseLabel:e.baseUrlLabel}function W(e,t){return t===`minimax`?e.minimaxClusterHint:t===`fal`?e.falQueueBaseHint:null}function G({summaries:e,credDraft:t,credDirty:n,credSaving:r,credError:i,credSavedFlash:o,credNoopFlash:l,updateCredRow:d,onDiscardCredentials:f,onSaveCredentials:m,extensionIds:h,showExtensionLinks:g,showImageModelsLink:_,language:v,apiKeyLinkLabels:y,messages:x}){if(e.length===0)return null;let C=e.some(e=>(e.ui?.regions?.length??0)>0),w=e.some(e=>(e.ui?.baseUrlPresets?.length??0)>0);return(0,F.jsxs)(`div`,{className:`flex flex-col gap-4`,children:[(0,F.jsxs)(`div`,{className:`flex flex-col gap-1 text-xs leading-relaxed text-fg-muted`,children:[(0,F.jsx)(`p`,{children:x.credentialsIntro}),C?(0,F.jsx)(`p`,{className:`text-fg-subtle`,children:x.regionHint}):null,w?(0,F.jsx)(`p`,{className:`text-fg-subtle`,children:x.endpointPresetsHint}):null,_?(0,F.jsx)(`p`,{children:(0,F.jsx)(S,{to:`/settings/image-models`,className:`font-medium text-accent hover:underline`,title:x.imageModelsLinkTitle,children:x.openImageModelsPage})}):null]}),i?(0,F.jsx)(`div`,{className:`rounded-lg border border-red-500/30 bg-red-500/10 px-3 py-2 text-sm text-red-700 dark:text-red-300`,children:i}):null,(0,F.jsxs)(`div`,{className:`flex flex-wrap items-center justify-end gap-2`,children:[o?(0,F.jsx)(`span`,{className:`text-sm text-fg-muted`,children:x.credentialsSaved}):null,l?(0,F.jsx)(`span`,{className:`text-sm text-fg-muted`,children:x.credentialsNothingToSave}):null,(0,F.jsx)(s,{type:`button`,variant:`secondary`,onClick:f,disabled:!n||r,children:x.discardCredentials}),(0,F.jsx)(s,{type:`button`,variant:`primary`,onClick:m,disabled:!n||r,children:r?(0,F.jsxs)(F.Fragment,{children:[(0,F.jsx)(c,{className:`size-3.5 animate-spin`}),(0,F.jsx)(`span`,{className:`ml-1.5`,children:x.savingCredentials})]}):(0,F.jsxs)(F.Fragment,{children:[(0,F.jsx)(b,{className:`size-3.5`}),(0,F.jsx)(`span`,{className:`ml-1.5`,children:x.saveCredentials})]})})]}),(0,F.jsx)(`div`,{className:`flex flex-col gap-4`,children:e.map(e=>{let n=t[e.id]??D(),r=e.ui,i=g&&h.has(e.id)?`/settings/ext/${encodeURIComponent(e.id)}`:null;return(0,F.jsxs)(`div`,{className:`rounded-lg border border-edge bg-surface-panel px-4 py-3 shadow-sm dark:shadow-none`,children:[(0,F.jsxs)(`div`,{className:`flex flex-wrap items-center justify-between gap-3`,children:[(0,F.jsxs)(`div`,{className:`flex min-w-0 flex-wrap items-center gap-2`,children:[(0,F.jsx)(`span`,{className:`text-sm font-semibold text-fg`,children:e.label??e.id}),(0,F.jsxs)(`span`,{className:`text-xs text-fg-subtle`,children:[`(`,e.id,`)`]}),i?(0,F.jsxs)(S,{to:i,className:`inline-flex items-center gap-1 text-xs font-medium text-accent hover:underline`,title:x.extensionSettingsLinkTitle,children:[(0,F.jsx)(u,{className:`size-3`}),x.openExtensionSettings]}):null]}),e.configured?(0,F.jsx)(`span`,{className:`rounded-full bg-accent-soft px-2 py-0.5 text-xs font-medium text-accent-fg`,children:x.configured}):(0,F.jsx)(`span`,{className:`rounded-full border border-amber-500/40 bg-amber-500/10 px-2 py-0.5 text-xs font-medium text-amber-700 dark:text-amber-300`,children:x.missingKey})]}),e.defaultModel?(0,F.jsxs)(`p`,{className:`mt-1 text-xs text-fg-subtle`,children:[(0,F.jsxs)(`span`,{className:`text-fg-muted`,children:[x.defaultModel,`:`]}),` `,e.id,`/`,e.defaultModel]}):null,e.models.length>0?(0,F.jsxs)(`p`,{className:`mt-0.5 text-xs text-fg-subtle`,children:[(0,F.jsxs)(`span`,{className:`text-fg-muted`,children:[x.modelsLabel,`:`]}),` `,e.models.map(t=>`${e.id}/${t}`).join(`, `)]}):null,(0,F.jsxs)(`div`,{className:`mt-4 grid gap-3 sm:grid-cols-2`,children:[(0,F.jsx)(I,{providerId:e.id,value:n.apiKey,onChange:t=>d(e.id,{apiKey:t}),apiKeyLinks:p(e.id,v),apiKeyLinkLabels:y,labels:{apiKeyLabel:x.apiKeyLabel,optionalPlaceholder:x.optionalPlaceholder,maskedHelp:x.apiKeyMaskedHelp,copy:x.apiKeyCopy,copied:x.apiKeyCopied,show:x.apiKeyShow,hide:x.apiKeyHide,notInConfigFile:x.apiKeyNotInConfigFile,loadFailed:x.apiKeyRevealFailed}}),r?.regions?.length?(0,F.jsxs)(`div`,{className:`flex min-w-0 flex-col gap-1 sm:col-span-2`,children:[(0,F.jsx)(`label`,{className:`text-xs font-medium text-fg-muted`,htmlFor:`img-cred-region-preset-${e.id}`,children:x.regionLabel}),(0,F.jsxs)(`select`,{id:`img-cred-region-preset-${e.id}`,className:R(),value:B(n,r.regions),onChange:t=>{let n=t.target.value;if(n===``){d(e.id,{region:``,imageBaseUrl:``});return}if(n===z){d(e.id,{region:``,imageBaseUrl:``});return}let i=r.regions.find(e=>e.value===n);i&&d(e.id,{region:i.value,imageBaseUrl:i.imageBaseUrl})},children:[(0,F.jsx)(`option`,{value:``,children:x.regionPresetDefault}),r.regions.map(e=>(0,F.jsx)(`option`,{value:e.value,children:H(x,e.value,e.label)},e.value)),(0,F.jsx)(`option`,{value:z,children:x.regionPresetCustom})]}),B(n,r.regions)===z?(0,F.jsxs)(`div`,{className:`mt-2 grid gap-2 sm:grid-cols-2`,children:[(0,F.jsx)(`input`,{type:`text`,className:L(),value:n.region,placeholder:`region`,onChange:t=>d(e.id,{region:t.target.value})}),(0,F.jsx)(`input`,{type:`url`,className:L(),value:n.imageBaseUrl,placeholder:x.imageBaseUrlLabel,onChange:t=>d(e.id,{imageBaseUrl:t.target.value})})]}):null]}):null,r?.baseUrlPresets?.length?(0,F.jsxs)(`div`,{className:`flex min-w-0 flex-col gap-1 sm:col-span-2`,children:[(0,F.jsx)(`label`,{className:`text-xs font-medium text-fg-muted`,htmlFor:`img-cred-base-preset-${e.id}`,children:U(x,r.baseUrlPresetKind)}),W(x,r.baseUrlPresetKind)?(0,F.jsx)(`p`,{className:`text-[11px] text-fg-subtle`,children:W(x,r.baseUrlPresetKind)}):null,(0,F.jsxs)(`select`,{id:`img-cred-base-preset-${e.id}`,className:R(),value:V(n,r.baseUrlPresets),onChange:t=>{let n=t.target.value;if(n===``){d(e.id,{baseUrl:``});return}if(n===z){d(e.id,{baseUrl:``});return}d(e.id,{baseUrl:n.replace(/\/+$/,``)})},children:[(0,F.jsx)(`option`,{value:``,children:x.baseUrlPresetDefault}),r.baseUrlPresets.map(e=>(0,F.jsx)(`option`,{value:e.value,children:e.label},e.value)),(0,F.jsx)(`option`,{value:z,children:x.baseUrlPresetCustom})]}),V(n,r.baseUrlPresets)===z?(0,F.jsx)(`input`,{type:`url`,className:a(L(),`mt-2`),value:n.baseUrl,placeholder:`https://…`,onChange:t=>d(e.id,{baseUrl:t.target.value})}):null]}):null,r?.regions?.length&&B(n,r.regions)!==z?(0,F.jsxs)(`div`,{className:`flex min-w-0 flex-col gap-1 sm:col-span-2`,children:[(0,F.jsx)(`label`,{className:`text-xs font-medium text-fg-muted`,htmlFor:`img-cred-imgbase-ro-${e.id}`,children:x.imageBaseUrlLabel}),(0,F.jsx)(`input`,{id:`img-cred-imgbase-ro-${e.id}`,type:`url`,readOnly:!0,className:a(L(),`cursor-not-allowed opacity-90`),value:n.imageBaseUrl,title:x.imageBaseUrlPresetHint}),(0,F.jsx)(`p`,{className:`text-[11px] text-fg-subtle`,children:x.imageBaseUrlPresetHint})]}):null]})]},e.id)})})]})}function K(e){let t=l(x(e=>!!e.token)),n=t.data,i=(0,E.useMemo)(()=>e.map(e=>e.id),[e]),[a,o]=(0,E.useState)({}),[s,c]=(0,E.useState)({}),[u,d]=(0,E.useState)(!1),[f,p]=(0,E.useState)(null),[m,g]=(0,E.useState)(!1),[_,v]=(0,E.useState)(!1),y=(0,E.useMemo)(()=>k(n?.payload?.config,i),[n?.payload?.config,i]),b=(0,E.useMemo)(()=>JSON.stringify(a)!==JSON.stringify(s),[a,s]);return(0,E.useEffect)(()=>{b||(o(structuredClone(y)),c(structuredClone(y)))},[y,b]),{gwSwr:t,credDraft:a,credBaseline:s,credDirty:b,credSaving:u,credError:f,credSavedFlash:m,credNoopFlash:_,updateCredRow:(0,E.useCallback)((e,t)=>{o(n=>{let r=n[e]??D();return{...n,[e]:{...r,...t}}})},[]),onDiscardCredentials:(0,E.useCallback)(()=>{o(structuredClone(s)),p(null),g(!1),v(!1)},[s]),saveCredentials:(0,E.useCallback)(async e=>{let n=M(i,a,s);if(Object.keys(n).length===0){v(!0),window.setTimeout(()=>v(!1),2200);return}d(!0),p(null),g(!1);try{await P(n);let e=await t.mutate?.();r(h(w));let a=k(e?.payload?.config,i);o(structuredClone(a)),c(structuredClone(a)),g(!0),window.setTimeout(()=>g(!1),2e3)}catch(t){p(t instanceof Error?t.message:e)}finally{d(!1)}},[i,a,s,t])}}export{w as i,G as n,T as r,K as t};
2
+ //# sourceMappingURL=use-image-provider-credentials-BPcW1K0N.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"use-image-provider-credentials-DcP2SYn3.js","names":[],"sources":["../../../../../web/src/features/settings/image-providers-swr-key.ts","../../../../../web/src/features/settings/fetch-image-providers.ts","../../../../../web/src/features/settings/image-providers-config-api.ts","../../../../../web/src/features/settings/image-provider-api-key-field.tsx","../../../../../web/src/features/settings/image-provider-credentials-panel.tsx","../../../../../web/src/features/settings/use-image-provider-credentials.ts"],"sourcesContent":["/** Shared SWR key for GET `/api/image/providers` (image settings + extension image pages). */\nexport const IMAGE_PROVIDERS_SWR_KEY = '/api/image/providers';\n","import { fetchJson } from '@/lib/fetch';\nimport { apiUrl } from '@/lib/url';\n\nimport { IMAGE_PROVIDERS_SWR_KEY } from '@/features/settings/image-providers-swr-key';\nimport type { ImageGenProviderCredentialSummary } from '@/features/settings/use-image-provider-credentials';\n\nexport async function fetchImageProvidersList(): Promise<ImageGenProviderCredentialSummary[]> {\n const res = await fetchJson<{\n ok?: boolean;\n payload?: { providers?: ImageGenProviderCredentialSummary[] };\n }>(apiUrl(IMAGE_PROVIDERS_SWR_KEY));\n return res?.payload?.providers ?? [];\n}\n","import { isMaskedKey } from '@/features/settings/providers-api';\nimport { revalidateGatewayConfig } from '@/features/gateway/gateway-config-swr';\nimport { fetchJson } from '@/lib/fetch';\nimport { apiUrl } from '@/lib/url';\n\n/** One row of image-provider credential fields (matches PATCH `providersConfig` subset). */\nexport type ImageProviderCredRow = {\n apiKey: string;\n region: string;\n baseUrl: string;\n imageBaseUrl: string;\n};\n\nexport function emptyImageProviderCredRow(): ImageProviderCredRow {\n return { apiKey: '', region: '', baseUrl: '', imageBaseUrl: '' };\n}\n\nexport type SafeProviderAuthEntry = {\n apiKey: string;\n region?: string;\n baseUrl?: string;\n imageBaseUrl?: string;\n};\n\nfunction maskedApiKeyDisplay(safe?: SafeProviderAuthEntry): string {\n if (!safe?.apiKey) return '';\n return '••••••••••••';\n}\n\n/** Read `payload.config.providersConfig` from GET /api/config (masked). */\nexport function imageProviderCredRowsFromConfigRoot(\n config: unknown,\n imageProviderIds: string[],\n): Record<string, ImageProviderCredRow> {\n const pc = (() => {\n if (!config || typeof config !== 'object' || !('providersConfig' in config)) return undefined;\n const v = (config as { providersConfig?: unknown }).providersConfig;\n if (!v || typeof v !== 'object' || Array.isArray(v)) return undefined;\n return v as Record<string, SafeProviderAuthEntry>;\n })();\n\n const out: Record<string, ImageProviderCredRow> = {};\n for (const id of imageProviderIds) {\n const safe = pc?.[id];\n out[id] = {\n apiKey: maskedApiKeyDisplay(safe),\n region: safe?.region ?? '',\n baseUrl: safe?.baseUrl ?? '',\n imageBaseUrl: safe?.imageBaseUrl ?? '',\n };\n }\n return out;\n}\n\nfunction optionalStringField(\n draft: ImageProviderCredRow,\n baseline: ImageProviderCredRow,\n key: keyof Pick<ImageProviderCredRow, 'region' | 'baseUrl' | 'imageBaseUrl'>,\n): string | null | undefined {\n const d = draft[key].trim();\n const b = baseline[key].trim();\n if (d === b) return undefined;\n if (!d) return null;\n return d;\n}\n\nfunction apiKeyPatchValue(draftKey: string, baselineKey: string): string | null | undefined {\n const d = draftKey.trim();\n const b = baselineKey.trim();\n if (d === b) return undefined;\n if (isMaskedKey(d) && isMaskedKey(b)) return undefined;\n if (!d) {\n if (!b) return undefined;\n return null;\n }\n return d;\n}\n\n/**\n * Build `providersConfig` PATCH entries only for image providers whose row changed.\n * Omits `apiKey` when unchanged (still masked); sends `null` to clear stored key.\n */\nexport function buildImageProvidersConfigPatch(\n imageProviderIds: string[],\n draft: Record<string, ImageProviderCredRow>,\n baseline: Record<string, ImageProviderCredRow>,\n): Record<string, Record<string, unknown>> {\n const patch: Record<string, Record<string, unknown>> = {};\n for (const id of imageProviderIds) {\n const d = draft[id] ?? emptyImageProviderCredRow();\n const b = baseline[id] ?? emptyImageProviderCredRow();\n if (JSON.stringify(d) === JSON.stringify(b)) continue;\n\n const entry: Record<string, unknown> = {};\n const keyDelta = apiKeyPatchValue(d.apiKey, b.apiKey);\n if (keyDelta !== undefined) {\n entry.apiKey = keyDelta;\n }\n const region = optionalStringField(d, b, 'region');\n if (region !== undefined) entry.region = region;\n const baseUrl = optionalStringField(d, b, 'baseUrl');\n if (baseUrl !== undefined) entry.baseUrl = baseUrl;\n const imageBaseUrl = optionalStringField(d, b, 'imageBaseUrl');\n if (imageBaseUrl !== undefined) entry.imageBaseUrl = imageBaseUrl;\n\n if (Object.keys(entry).length > 0) {\n patch[id] = entry;\n }\n }\n return patch;\n}\n\nexport type RevealImageProviderApiKeyPayload = {\n id: string;\n apiKey: string | null;\n source: 'config' | 'none';\n};\n\n/** POST /api/image/providers/:id/reveal-api-key — plaintext only when stored in config file. */\nexport async function revealImageProviderConfigApiKey(providerId: string): Promise<RevealImageProviderApiKeyPayload> {\n const data = await fetchJson<{\n ok?: boolean;\n payload?: RevealImageProviderApiKeyPayload;\n error?: { message?: string };\n }>(apiUrl(`/api/image/providers/${encodeURIComponent(providerId)}/reveal-api-key`), {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: '{}',\n });\n if (!data.ok || !data.payload) {\n throw new Error(data.error?.message ?? 'Reveal failed');\n }\n return data.payload;\n}\n\nexport async function patchImageProvidersConfig(\n patch: Record<string, Record<string, unknown>>,\n): Promise<void> {\n if (Object.keys(patch).length === 0) return;\n await fetchJson(apiUrl('/api/config'), {\n method: 'PATCH',\n body: JSON.stringify({ providersConfig: patch }),\n });\n await revalidateGatewayConfig();\n}\n","import { CheckCircle2, Copy, ExternalLink, Eye, EyeOff, Loader2 } from 'lucide-react';\nimport { useCallback, useEffect, useState } from 'react';\n\nimport { revealImageProviderConfigApiKey } from '@/features/settings/image-providers-config-api';\nimport type { ApiKeyLinkKind } from '@/features/settings/provider-enrichment';\nimport { providerApiKeyLinkLabel } from '@/features/settings/provider-enrichment';\nimport { isMaskedKey } from '@/features/settings/providers-api';\nimport type { ProvidersSettingsMessages } from '@/i18n/messages';\nimport { settingsInputFocusClass } from '@/lib/form-field-width';\nimport { interaction } from '@/lib/interaction';\nimport { cn } from '@/lib/cn';\n\nexport type ImageProviderApiKeyFieldLabels = {\n apiKeyLabel: string;\n optionalPlaceholder: string;\n maskedHelp: string;\n copy: string;\n copied: string;\n show: string;\n hide: string;\n notInConfigFile: string;\n loadFailed: string;\n};\n\nexport function ImageProviderApiKeyField({\n providerId,\n value,\n onChange,\n labels,\n apiKeyLinks,\n apiKeyLinkLabels,\n}: {\n providerId: string;\n value: string;\n onChange: (next: string) => void;\n labels: ImageProviderApiKeyFieldLabels;\n apiKeyLinks: { href: string; kind: ApiKeyLinkKind }[];\n apiKeyLinkLabels: Pick<ProvidersSettingsMessages, 'getApiKey' | 'getApiKeyIntl' | 'getApiKeyCn'>;\n}) {\n const [showKey, setShowKey] = useState(false);\n /** `undefined` = not fetched; `null` = fetched, not in config file; string = plaintext from config */\n const [revealed, setRevealed] = useState<string | null | undefined>(undefined);\n const [revealLoading, setRevealLoading] = useState(false);\n const [revealErr, setRevealErr] = useState<string | null>(null);\n const [copied, setCopied] = useState(false);\n\n const masked = isMaskedKey(value);\n\n useEffect(() => {\n if (!masked) {\n setRevealed(undefined);\n setRevealErr(null);\n }\n }, [masked, value]);\n\n const inputValue = (() => {\n if (!masked) return value;\n if (showKey && typeof revealed === 'string') return revealed;\n return value;\n })();\n\n const inputType =\n !masked || (masked && showKey && typeof revealed === 'string') ? ('text' as const) : ('password' as const);\n\n const copyEnabled =\n (!masked && value.trim().length > 0 && !isMaskedKey(value)) ||\n (Boolean(showKey) && typeof revealed === 'string' && revealed.length > 0);\n\n const copyKey = useCallback(async () => {\n const text =\n !masked && value.trim() && !isMaskedKey(value)\n ? value.trim()\n : typeof revealed === 'string' && revealed.length > 0\n ? revealed\n : '';\n if (!text) return;\n try {\n await navigator.clipboard.writeText(text);\n setCopied(true);\n window.setTimeout(() => setCopied(false), 2000);\n } catch {\n /* ignore */\n }\n }, [masked, revealed, value]);\n\n const toggleEye = useCallback(async () => {\n setRevealErr(null);\n if (!masked) {\n setShowKey((s) => !s);\n return;\n }\n if (revealed !== undefined) {\n setShowKey((s) => !s);\n return;\n }\n setRevealLoading(true);\n try {\n const payload = await revealImageProviderConfigApiKey(providerId);\n setRevealed(payload.apiKey ?? null);\n setShowKey(true);\n } catch (e) {\n setRevealErr(e instanceof Error ? e.message : labels.loadFailed);\n setRevealed(null);\n } finally {\n setRevealLoading(false);\n }\n }, [masked, providerId, revealed, labels.loadFailed]);\n\n return (\n <div className=\"flex min-w-0 flex-col gap-1 sm:col-span-2\">\n <label className=\"text-xs font-medium text-fg-muted\" htmlFor={`img-cred-key-${providerId}`}>\n {labels.apiKeyLabel}\n </label>\n {apiKeyLinks.length > 0 ? (\n <div className=\"flex flex-col gap-1\">\n {apiKeyLinks.map((link) => (\n <a\n key={`${link.kind}-${link.href}`}\n href={link.href}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"inline-flex w-fit items-center gap-1 text-xs font-medium text-accent-fg hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent\"\n >\n {providerApiKeyLinkLabel(link.kind, apiKeyLinkLabels)}\n <ExternalLink className=\"size-3\" aria-hidden />\n </a>\n ))}\n </div>\n ) : null}\n {masked ? <p className=\"text-[11px] text-fg-subtle\">{labels.maskedHelp}</p> : null}\n <div className=\"relative min-w-0\">\n <input\n id={`img-cred-key-${providerId}`}\n type={inputType}\n autoComplete=\"off\"\n spellCheck={false}\n className={cn(\n 'w-full rounded-lg border border-edge bg-surface-panel py-2 pl-3 pr-24 font-mono text-sm text-fg',\n 'placeholder:text-fg-subtle',\n settingsInputFocusClass,\n )}\n value={inputValue}\n placeholder={masked ? '••••••••' : labels.optionalPlaceholder}\n onChange={(e) => {\n const next = e.target.value;\n if (masked && typeof revealed === 'string' && showKey && next !== revealed) {\n setRevealed(undefined);\n setShowKey(false);\n }\n onChange(next);\n }}\n />\n <div className=\"absolute right-1 top-1/2 flex -translate-y-1/2 gap-0.5\">\n {copyEnabled ? (\n <button\n type=\"button\"\n className={cn(\n 'rounded p-1.5 text-fg-subtle hover:bg-surface-hover hover:text-fg',\n interaction.transition,\n interaction.press,\n interaction.focusRingPanel,\n )}\n title={copied ? labels.copied : labels.copy}\n aria-label={copied ? labels.copied : labels.copy}\n onClick={() => void copyKey()}\n >\n {copied ? <CheckCircle2 className=\"size-4\" /> : <Copy className=\"size-4\" />}\n </button>\n ) : null}\n <button\n type=\"button\"\n className={cn(\n 'rounded p-1.5 text-fg-subtle hover:bg-surface-hover hover:text-fg disabled:opacity-40',\n interaction.transition,\n interaction.press,\n interaction.focusRingPanel,\n )}\n title={showKey ? labels.hide : labels.show}\n aria-label={showKey ? labels.hide : labels.show}\n disabled={revealLoading}\n onClick={() => void toggleEye()}\n >\n {revealLoading ? (\n <Loader2 className=\"size-4 animate-spin\" aria-hidden />\n ) : showKey ? (\n <EyeOff className=\"size-4\" aria-hidden />\n ) : (\n <Eye className=\"size-4\" aria-hidden />\n )}\n </button>\n </div>\n </div>\n {masked && showKey && revealed === null && !revealErr ? (\n <p className=\"text-xs text-amber-700 dark:text-amber-400/90\">{labels.notInConfigFile}</p>\n ) : null}\n {revealErr ? <p className=\"text-xs text-red-600 dark:text-red-400\">{revealErr}</p> : null}\n </div>\n );\n}\n","import { ExternalLink, Loader2, Save } from 'lucide-react';\nimport { Link } from 'react-router-dom';\n\nimport { Button } from '@/components/ui/button';\nimport { ImageProviderApiKeyField } from '@/features/settings/image-provider-api-key-field';\nimport { emptyImageProviderCredRow, type ImageProviderCredRow } from '@/features/settings/image-providers-config-api';\nimport { getOrderedApiKeyLinks } from '@/features/settings/provider-enrichment';\nimport type {\n ImageGenProviderCredentialSummary,\n ImageProviderUiMetadata,\n} from '@/features/settings/use-image-provider-credentials';\nimport type { ProvidersSettingsMessages } from '@/i18n/messages';\nimport { settingsInputFocusClass } from '@/lib/form-field-width';\nimport type { StoredLanguage } from '@/lib/storage';\nimport { cn } from '@/lib/cn';\n\nfunction inputClass(): string {\n return cn(\n 'w-full rounded-lg border border-edge bg-surface-panel px-3 py-2 text-sm text-fg',\n 'placeholder:text-fg-subtle',\n settingsInputFocusClass,\n );\n}\n\nfunction selectClass(): string {\n return cn(inputClass(), 'appearance-none bg-[length:1rem] bg-[right_0.5rem_center] bg-no-repeat pr-9');\n}\n\nconst CUSTOM_SENTINEL = '__custom__';\n\nfunction dashscopeSelectValue(\n row: ImageProviderCredRow,\n regions: NonNullable<ImageProviderUiMetadata['regions']>,\n): string {\n if (!row.region.trim() && !row.imageBaseUrl.trim()) return '';\n const r = row.region.trim().toLowerCase();\n if (regions.some((x) => x.value === r)) return r;\n return CUSTOM_SENTINEL;\n}\n\nfunction baseUrlSelectValue(\n row: ImageProviderCredRow,\n presets: NonNullable<ImageProviderUiMetadata['baseUrlPresets']>,\n): string {\n const b = row.baseUrl.trim().replace(/\\/+$/, '');\n if (!b) return '';\n const norm = presets.map((p) => p.value.replace(/\\/+$/, ''));\n const idx = norm.indexOf(b);\n if (idx >= 0) return presets[idx].value;\n return CUSTOM_SENTINEL;\n}\n\nexport type ImageProviderCredentialsPanelMessages = {\n credentialsIntro: string;\n regionHint: string;\n endpointPresetsHint: string;\n apiKeyLabel: string;\n optionalPlaceholder: string;\n regionLabel: string;\n baseUrlLabel: string;\n imageBaseUrlLabel: string;\n saveCredentials: string;\n savingCredentials: string;\n credentialsSaved: string;\n discardCredentials: string;\n credentialsNothingToSave: string;\n credentialsSaveError: string;\n regionPresetDefault: string;\n regionPresetCustom: string;\n baseUrlPresetDefault: string;\n baseUrlPresetCustom: string;\n openExtensionSettings: string;\n openImageModelsPage: string;\n extensionSettingsLinkTitle: string;\n imageModelsLinkTitle: string;\n configured: string;\n missingKey: string;\n defaultModel: string;\n modelsLabel: string;\n imageBaseUrlPresetHint: string;\n dashscopeRegion_beijing: string;\n dashscopeRegion_singapore: string;\n dashscopeRegion_us: string;\n apiKeyMaskedHelp: string;\n apiKeyCopy: string;\n apiKeyCopied: string;\n apiKeyShow: string;\n apiKeyHide: string;\n apiKeyNotInConfigFile: string;\n apiKeyRevealFailed: string;\n minimaxClusterLabel: string;\n minimaxClusterHint: string;\n falQueueBaseLabel: string;\n falQueueBaseHint: string;\n};\n\nfunction translateDashscopeRegion(m: ImageProviderCredentialsPanelMessages, value: string, serverLabel: string) {\n if (value === 'beijing') return m.dashscopeRegion_beijing;\n if (value === 'singapore') return m.dashscopeRegion_singapore;\n if (value === 'us') return m.dashscopeRegion_us;\n return serverLabel;\n}\n\nfunction baseUrlPresetBlockTitle(\n t: ImageProviderCredentialsPanelMessages,\n kind: ImageProviderUiMetadata['baseUrlPresetKind'],\n): string {\n if (kind === 'minimax') return t.minimaxClusterLabel;\n if (kind === 'fal') return t.falQueueBaseLabel;\n return t.baseUrlLabel;\n}\n\nfunction baseUrlPresetBlockHint(\n t: ImageProviderCredentialsPanelMessages,\n kind: ImageProviderUiMetadata['baseUrlPresetKind'],\n): string | null {\n if (kind === 'minimax') return t.minimaxClusterHint;\n if (kind === 'fal') return t.falQueueBaseHint;\n return null;\n}\n\nexport function ImageProviderCredentialsPanel({\n summaries,\n credDraft,\n credDirty,\n credSaving,\n credError,\n credSavedFlash,\n credNoopFlash,\n updateCredRow,\n onDiscardCredentials,\n onSaveCredentials,\n extensionIds,\n showExtensionLinks,\n showImageModelsLink,\n language,\n apiKeyLinkLabels,\n messages: t,\n}: {\n summaries: ImageGenProviderCredentialSummary[];\n credDraft: Record<string, ImageProviderCredRow>;\n credDirty: boolean;\n credSaving: boolean;\n credError: string | null;\n credSavedFlash: boolean;\n credNoopFlash: boolean;\n updateCredRow: (id: string, patch: Partial<ImageProviderCredRow>) => void;\n onDiscardCredentials: () => void;\n onSaveCredentials: () => void;\n /** Extension ids present in gateway discovery (for deep links). */\n extensionIds: Set<string>;\n showExtensionLinks: boolean;\n showImageModelsLink: boolean;\n language: StoredLanguage;\n apiKeyLinkLabels: Pick<ProvidersSettingsMessages, 'getApiKey' | 'getApiKeyIntl' | 'getApiKeyCn'>;\n messages: ImageProviderCredentialsPanelMessages;\n}) {\n const empty = summaries.length === 0;\n\n if (empty) {\n return null;\n }\n\n const anyRegionUi = summaries.some((s) => (s.ui?.regions?.length ?? 0) > 0);\n const anyBaseUrlPresets = summaries.some((s) => (s.ui?.baseUrlPresets?.length ?? 0) > 0);\n\n return (\n <div className=\"flex flex-col gap-4\">\n <div className=\"flex flex-col gap-1 text-xs leading-relaxed text-fg-muted\">\n <p>{t.credentialsIntro}</p>\n {anyRegionUi ? <p className=\"text-fg-subtle\">{t.regionHint}</p> : null}\n {anyBaseUrlPresets ? <p className=\"text-fg-subtle\">{t.endpointPresetsHint}</p> : null}\n {showImageModelsLink ? (\n <p>\n <Link\n to=\"/settings/image-models\"\n className=\"font-medium text-accent hover:underline\"\n title={t.imageModelsLinkTitle}\n >\n {t.openImageModelsPage}\n </Link>\n </p>\n ) : null}\n </div>\n {credError ? (\n <div className=\"rounded-lg border border-red-500/30 bg-red-500/10 px-3 py-2 text-sm text-red-700 dark:text-red-300\">\n {credError}\n </div>\n ) : null}\n <div className=\"flex flex-wrap items-center justify-end gap-2\">\n {credSavedFlash ? (\n <span className=\"text-sm text-fg-muted\">{t.credentialsSaved}</span>\n ) : null}\n {credNoopFlash ? (\n <span className=\"text-sm text-fg-muted\">{t.credentialsNothingToSave}</span>\n ) : null}\n <Button type=\"button\" variant=\"secondary\" onClick={onDiscardCredentials} disabled={!credDirty || credSaving}>\n {t.discardCredentials}\n </Button>\n <Button type=\"button\" variant=\"primary\" onClick={onSaveCredentials} disabled={!credDirty || credSaving}>\n {credSaving ? (\n <>\n <Loader2 className=\"size-3.5 animate-spin\" />\n <span className=\"ml-1.5\">{t.savingCredentials}</span>\n </>\n ) : (\n <>\n <Save className=\"size-3.5\" />\n <span className=\"ml-1.5\">{t.saveCredentials}</span>\n </>\n )}\n </Button>\n </div>\n <div className=\"flex flex-col gap-4\">\n {summaries.map((p) => {\n const row = credDraft[p.id] ?? emptyImageProviderCredRow();\n const ui = p.ui;\n const extPath =\n showExtensionLinks && extensionIds.has(p.id)\n ? `/settings/ext/${encodeURIComponent(p.id)}`\n : null;\n return (\n <div\n key={p.id}\n className=\"rounded-lg border border-edge bg-surface-panel px-4 py-3 shadow-sm dark:shadow-none\"\n >\n <div className=\"flex flex-wrap items-center justify-between gap-3\">\n <div className=\"flex min-w-0 flex-wrap items-center gap-2\">\n <span className=\"text-sm font-semibold text-fg\">{p.label ?? p.id}</span>\n <span className=\"text-xs text-fg-subtle\">({p.id})</span>\n {extPath ? (\n <Link\n to={extPath}\n className=\"inline-flex items-center gap-1 text-xs font-medium text-accent hover:underline\"\n title={t.extensionSettingsLinkTitle}\n >\n <ExternalLink className=\"size-3\" />\n {t.openExtensionSettings}\n </Link>\n ) : null}\n </div>\n {p.configured ? (\n <span className=\"rounded-full bg-accent-soft px-2 py-0.5 text-xs font-medium text-accent-fg\">\n {t.configured}\n </span>\n ) : (\n <span className=\"rounded-full border border-amber-500/40 bg-amber-500/10 px-2 py-0.5 text-xs font-medium text-amber-700 dark:text-amber-300\">\n {t.missingKey}\n </span>\n )}\n </div>\n {p.defaultModel ? (\n <p className=\"mt-1 text-xs text-fg-subtle\">\n <span className=\"text-fg-muted\">{t.defaultModel}:</span> {p.id}/{p.defaultModel}\n </p>\n ) : null}\n {p.models.length > 0 ? (\n <p className=\"mt-0.5 text-xs text-fg-subtle\">\n <span className=\"text-fg-muted\">{t.modelsLabel}:</span>{' '}\n {p.models.map((mm) => `${p.id}/${mm}`).join(', ')}\n </p>\n ) : null}\n <div className=\"mt-4 grid gap-3 sm:grid-cols-2\">\n <ImageProviderApiKeyField\n providerId={p.id}\n value={row.apiKey}\n onChange={(next) => updateCredRow(p.id, { apiKey: next })}\n apiKeyLinks={getOrderedApiKeyLinks(p.id, language)}\n apiKeyLinkLabels={apiKeyLinkLabels}\n labels={{\n apiKeyLabel: t.apiKeyLabel,\n optionalPlaceholder: t.optionalPlaceholder,\n maskedHelp: t.apiKeyMaskedHelp,\n copy: t.apiKeyCopy,\n copied: t.apiKeyCopied,\n show: t.apiKeyShow,\n hide: t.apiKeyHide,\n notInConfigFile: t.apiKeyNotInConfigFile,\n loadFailed: t.apiKeyRevealFailed,\n }}\n />\n\n {ui?.regions?.length ? (\n <div className=\"flex min-w-0 flex-col gap-1 sm:col-span-2\">\n <label className=\"text-xs font-medium text-fg-muted\" htmlFor={`img-cred-region-preset-${p.id}`}>\n {t.regionLabel}\n </label>\n <select\n id={`img-cred-region-preset-${p.id}`}\n className={selectClass()}\n value={dashscopeSelectValue(row, ui.regions)}\n onChange={(e) => {\n const v = e.target.value;\n if (v === '') {\n updateCredRow(p.id, { region: '', imageBaseUrl: '' });\n return;\n }\n if (v === CUSTOM_SENTINEL) {\n updateCredRow(p.id, { region: '', imageBaseUrl: '' });\n return;\n }\n const opt = ui.regions!.find((x) => x.value === v);\n if (opt) {\n updateCredRow(p.id, { region: opt.value, imageBaseUrl: opt.imageBaseUrl });\n }\n }}\n >\n <option value=\"\">{t.regionPresetDefault}</option>\n {ui.regions.map((r) => (\n <option key={r.value} value={r.value}>\n {translateDashscopeRegion(t, r.value, r.label)}\n </option>\n ))}\n <option value={CUSTOM_SENTINEL}>{t.regionPresetCustom}</option>\n </select>\n {dashscopeSelectValue(row, ui.regions) === CUSTOM_SENTINEL ? (\n <div className=\"mt-2 grid gap-2 sm:grid-cols-2\">\n <input\n type=\"text\"\n className={inputClass()}\n value={row.region}\n placeholder=\"region\"\n onChange={(e) => updateCredRow(p.id, { region: e.target.value })}\n />\n <input\n type=\"url\"\n className={inputClass()}\n value={row.imageBaseUrl}\n placeholder={t.imageBaseUrlLabel}\n onChange={(e) => updateCredRow(p.id, { imageBaseUrl: e.target.value })}\n />\n </div>\n ) : null}\n </div>\n ) : null}\n\n {ui?.baseUrlPresets?.length ? (\n <div className=\"flex min-w-0 flex-col gap-1 sm:col-span-2\">\n <label className=\"text-xs font-medium text-fg-muted\" htmlFor={`img-cred-base-preset-${p.id}`}>\n {baseUrlPresetBlockTitle(t, ui.baseUrlPresetKind)}\n </label>\n {baseUrlPresetBlockHint(t, ui.baseUrlPresetKind) ? (\n <p className=\"text-[11px] text-fg-subtle\">{baseUrlPresetBlockHint(t, ui.baseUrlPresetKind)}</p>\n ) : null}\n <select\n id={`img-cred-base-preset-${p.id}`}\n className={selectClass()}\n value={baseUrlSelectValue(row, ui.baseUrlPresets)}\n onChange={(e) => {\n const v = e.target.value;\n if (v === '') {\n updateCredRow(p.id, { baseUrl: '' });\n return;\n }\n if (v === CUSTOM_SENTINEL) {\n updateCredRow(p.id, { baseUrl: '' });\n return;\n }\n updateCredRow(p.id, { baseUrl: v.replace(/\\/+$/, '') });\n }}\n >\n <option value=\"\">{t.baseUrlPresetDefault}</option>\n {ui.baseUrlPresets.map((b) => (\n <option key={b.value} value={b.value}>\n {b.label}\n </option>\n ))}\n <option value={CUSTOM_SENTINEL}>{t.baseUrlPresetCustom}</option>\n </select>\n {baseUrlSelectValue(row, ui.baseUrlPresets) === CUSTOM_SENTINEL ? (\n <input\n type=\"url\"\n className={cn(inputClass(), 'mt-2')}\n value={row.baseUrl}\n placeholder=\"https://…\"\n onChange={(e) => updateCredRow(p.id, { baseUrl: e.target.value })}\n />\n ) : null}\n </div>\n ) : null}\n\n {ui?.regions?.length && dashscopeSelectValue(row, ui.regions) !== CUSTOM_SENTINEL ? (\n <div className=\"flex min-w-0 flex-col gap-1 sm:col-span-2\">\n <label className=\"text-xs font-medium text-fg-muted\" htmlFor={`img-cred-imgbase-ro-${p.id}`}>\n {t.imageBaseUrlLabel}\n </label>\n <input\n id={`img-cred-imgbase-ro-${p.id}`}\n type=\"url\"\n readOnly\n className={cn(inputClass(), 'cursor-not-allowed opacity-90')}\n value={row.imageBaseUrl}\n title={t.imageBaseUrlPresetHint}\n />\n <p className=\"text-[11px] text-fg-subtle\">{t.imageBaseUrlPresetHint}</p>\n </div>\n ) : null}\n </div>\n </div>\n );\n })}\n </div>\n </div>\n );\n}\n","import { useCallback, useEffect, useMemo, useState } from 'react';\nimport { mutate } from 'swr';\n\nimport { useGatewayConfigSwr } from '@/features/gateway/gateway-config-swr';\nimport {\n buildImageProvidersConfigPatch,\n emptyImageProviderCredRow,\n imageProviderCredRowsFromConfigRoot,\n patchImageProvidersConfig,\n type ImageProviderCredRow,\n} from '@/features/settings/image-providers-config-api';\nimport { IMAGE_PROVIDERS_SWR_KEY } from '@/features/settings/image-providers-swr-key';\nimport { apiUrl } from '@/lib/url';\nimport { useGatewayStore } from '@/stores/gateway-store';\n\nexport type ImageProviderUiRegionOption = {\n value: string;\n label: string;\n imageBaseUrl: string;\n};\n\nexport type ImageProviderUiBaseUrlPreset = {\n value: string;\n label: string;\n};\n\nexport type ImageProviderUiMetadata = {\n regions?: ImageProviderUiRegionOption[];\n baseUrlPresets?: ImageProviderUiBaseUrlPreset[];\n baseUrlPresetKind?: 'fal' | 'minimax' | 'google' | 'openai';\n};\n\nexport type ImageGenProviderCredentialSummary = {\n id: string;\n label?: string;\n defaultModel?: string;\n models: string[];\n configured?: boolean;\n ui?: ImageProviderUiMetadata;\n};\n\nexport function useImageProviderCredentials(summaries: ImageGenProviderCredentialSummary[]) {\n const hasToken = useGatewayStore((s) => Boolean(s.token));\n const gwSwr = useGatewayConfigSwr(hasToken);\n const gwCfg = gwSwr.data;\n\n const ids = useMemo(() => summaries.map((s) => s.id), [summaries]);\n\n const [credDraft, setCredDraft] = useState<Record<string, ImageProviderCredRow>>({});\n const [credBaseline, setCredBaseline] = useState<Record<string, ImageProviderCredRow>>({});\n const [credSaving, setCredSaving] = useState(false);\n const [credError, setCredError] = useState<string | null>(null);\n const [credSavedFlash, setCredSavedFlash] = useState(false);\n const [credNoopFlash, setCredNoopFlash] = useState(false);\n\n const credRowsFromServer = useMemo(\n () => imageProviderCredRowsFromConfigRoot(gwCfg?.payload?.config, ids),\n [gwCfg?.payload?.config, ids],\n );\n\n const credDirty = useMemo(\n () => JSON.stringify(credDraft) !== JSON.stringify(credBaseline),\n [credDraft, credBaseline],\n );\n\n useEffect(() => {\n if (!credDirty) {\n setCredDraft(structuredClone(credRowsFromServer));\n setCredBaseline(structuredClone(credRowsFromServer));\n }\n }, [credRowsFromServer, credDirty]);\n\n const updateCredRow = useCallback((id: string, patch: Partial<ImageProviderCredRow>) => {\n setCredDraft((prev) => {\n const base = prev[id] ?? emptyImageProviderCredRow();\n return { ...prev, [id]: { ...base, ...patch } };\n });\n }, []);\n\n const onDiscardCredentials = useCallback(() => {\n setCredDraft(structuredClone(credBaseline));\n setCredError(null);\n setCredSavedFlash(false);\n setCredNoopFlash(false);\n }, [credBaseline]);\n\n const saveCredentials = useCallback(\n async (errorFallback: string) => {\n const patch = buildImageProvidersConfigPatch(ids, credDraft, credBaseline);\n if (Object.keys(patch).length === 0) {\n setCredNoopFlash(true);\n window.setTimeout(() => setCredNoopFlash(false), 2200);\n return;\n }\n setCredSaving(true);\n setCredError(null);\n setCredSavedFlash(false);\n try {\n await patchImageProvidersConfig(patch);\n const updated = await gwSwr.mutate?.();\n void mutate(apiUrl(IMAGE_PROVIDERS_SWR_KEY));\n const nextRows = imageProviderCredRowsFromConfigRoot(updated?.payload?.config, ids);\n setCredDraft(structuredClone(nextRows));\n setCredBaseline(structuredClone(nextRows));\n setCredSavedFlash(true);\n window.setTimeout(() => setCredSavedFlash(false), 2000);\n } catch (e) {\n setCredError(e instanceof Error ? e.message : errorFallback);\n } finally {\n setCredSaving(false);\n }\n },\n [ids, credDraft, credBaseline, gwSwr],\n );\n\n return {\n gwSwr,\n credDraft,\n credBaseline,\n credDirty,\n credSaving,\n credError,\n credSavedFlash,\n credNoopFlash,\n updateCredRow,\n onDiscardCredentials,\n saveCredentials,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"use-image-provider-credentials-BPcW1K0N.js","names":[],"sources":["../../../../../web/src/features/settings/image-providers-swr-key.ts","../../../../../web/src/features/settings/fetch-image-providers.ts","../../../../../web/src/features/settings/image-providers-config-api.ts","../../../../../web/src/features/settings/image-provider-api-key-field.tsx","../../../../../web/src/features/settings/image-provider-credentials-panel.tsx","../../../../../web/src/features/settings/use-image-provider-credentials.ts"],"sourcesContent":["/** Shared SWR key for GET `/api/image/providers` (image settings + extension image pages). */\nexport const IMAGE_PROVIDERS_SWR_KEY = '/api/image/providers';\n","import { fetchJson } from '@/lib/fetch';\nimport { apiUrl } from '@/lib/url';\n\nimport { IMAGE_PROVIDERS_SWR_KEY } from '@/features/settings/image-providers-swr-key';\nimport type { ImageGenProviderCredentialSummary } from '@/features/settings/use-image-provider-credentials';\n\nexport async function fetchImageProvidersList(): Promise<ImageGenProviderCredentialSummary[]> {\n const res = await fetchJson<{\n ok?: boolean;\n payload?: { providers?: ImageGenProviderCredentialSummary[] };\n }>(apiUrl(IMAGE_PROVIDERS_SWR_KEY));\n return res?.payload?.providers ?? [];\n}\n","import { isMaskedKey } from '@/features/settings/providers-api';\nimport { revalidateGatewayConfig } from '@/features/gateway/gateway-config-swr';\nimport { fetchJson } from '@/lib/fetch';\nimport { apiUrl } from '@/lib/url';\n\n/** One row of image-provider credential fields (matches PATCH `providersConfig` subset). */\nexport type ImageProviderCredRow = {\n apiKey: string;\n region: string;\n baseUrl: string;\n imageBaseUrl: string;\n};\n\nexport function emptyImageProviderCredRow(): ImageProviderCredRow {\n return { apiKey: '', region: '', baseUrl: '', imageBaseUrl: '' };\n}\n\nexport type SafeProviderAuthEntry = {\n apiKey: string;\n region?: string;\n baseUrl?: string;\n imageBaseUrl?: string;\n};\n\nfunction maskedApiKeyDisplay(safe?: SafeProviderAuthEntry): string {\n if (!safe?.apiKey) return '';\n return '••••••••••••';\n}\n\n/** Read `payload.config.providersConfig` from GET /api/config (masked). */\nexport function imageProviderCredRowsFromConfigRoot(\n config: unknown,\n imageProviderIds: string[],\n): Record<string, ImageProviderCredRow> {\n const pc = (() => {\n if (!config || typeof config !== 'object' || !('providersConfig' in config)) return undefined;\n const v = (config as { providersConfig?: unknown }).providersConfig;\n if (!v || typeof v !== 'object' || Array.isArray(v)) return undefined;\n return v as Record<string, SafeProviderAuthEntry>;\n })();\n\n const out: Record<string, ImageProviderCredRow> = {};\n for (const id of imageProviderIds) {\n const safe = pc?.[id];\n out[id] = {\n apiKey: maskedApiKeyDisplay(safe),\n region: safe?.region ?? '',\n baseUrl: safe?.baseUrl ?? '',\n imageBaseUrl: safe?.imageBaseUrl ?? '',\n };\n }\n return out;\n}\n\nfunction optionalStringField(\n draft: ImageProviderCredRow,\n baseline: ImageProviderCredRow,\n key: keyof Pick<ImageProviderCredRow, 'region' | 'baseUrl' | 'imageBaseUrl'>,\n): string | null | undefined {\n const d = draft[key].trim();\n const b = baseline[key].trim();\n if (d === b) return undefined;\n if (!d) return null;\n return d;\n}\n\nfunction apiKeyPatchValue(draftKey: string, baselineKey: string): string | null | undefined {\n const d = draftKey.trim();\n const b = baselineKey.trim();\n if (d === b) return undefined;\n if (isMaskedKey(d) && isMaskedKey(b)) return undefined;\n if (!d) {\n if (!b) return undefined;\n return null;\n }\n return d;\n}\n\n/**\n * Build `providersConfig` PATCH entries only for image providers whose row changed.\n * Omits `apiKey` when unchanged (still masked); sends `null` to clear stored key.\n */\nexport function buildImageProvidersConfigPatch(\n imageProviderIds: string[],\n draft: Record<string, ImageProviderCredRow>,\n baseline: Record<string, ImageProviderCredRow>,\n): Record<string, Record<string, unknown>> {\n const patch: Record<string, Record<string, unknown>> = {};\n for (const id of imageProviderIds) {\n const d = draft[id] ?? emptyImageProviderCredRow();\n const b = baseline[id] ?? emptyImageProviderCredRow();\n if (JSON.stringify(d) === JSON.stringify(b)) continue;\n\n const entry: Record<string, unknown> = {};\n const keyDelta = apiKeyPatchValue(d.apiKey, b.apiKey);\n if (keyDelta !== undefined) {\n entry.apiKey = keyDelta;\n }\n const region = optionalStringField(d, b, 'region');\n if (region !== undefined) entry.region = region;\n const baseUrl = optionalStringField(d, b, 'baseUrl');\n if (baseUrl !== undefined) entry.baseUrl = baseUrl;\n const imageBaseUrl = optionalStringField(d, b, 'imageBaseUrl');\n if (imageBaseUrl !== undefined) entry.imageBaseUrl = imageBaseUrl;\n\n if (Object.keys(entry).length > 0) {\n patch[id] = entry;\n }\n }\n return patch;\n}\n\nexport type RevealImageProviderApiKeyPayload = {\n id: string;\n apiKey: string | null;\n source: 'config' | 'none';\n};\n\n/** POST /api/image/providers/:id/reveal-api-key — plaintext only when stored in config file. */\nexport async function revealImageProviderConfigApiKey(providerId: string): Promise<RevealImageProviderApiKeyPayload> {\n const data = await fetchJson<{\n ok?: boolean;\n payload?: RevealImageProviderApiKeyPayload;\n error?: { message?: string };\n }>(apiUrl(`/api/image/providers/${encodeURIComponent(providerId)}/reveal-api-key`), {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: '{}',\n });\n if (!data.ok || !data.payload) {\n throw new Error(data.error?.message ?? 'Reveal failed');\n }\n return data.payload;\n}\n\nexport async function patchImageProvidersConfig(\n patch: Record<string, Record<string, unknown>>,\n): Promise<void> {\n if (Object.keys(patch).length === 0) return;\n await fetchJson(apiUrl('/api/config'), {\n method: 'PATCH',\n body: JSON.stringify({ providersConfig: patch }),\n });\n await revalidateGatewayConfig();\n}\n","import { CheckCircle2, Copy, ExternalLink, Eye, EyeOff, Loader2 } from 'lucide-react';\nimport { useCallback, useEffect, useState } from 'react';\n\nimport { revealImageProviderConfigApiKey } from '@/features/settings/image-providers-config-api';\nimport type { ApiKeyLinkKind } from '@/features/settings/provider-enrichment';\nimport { providerApiKeyLinkLabel } from '@/features/settings/provider-enrichment';\nimport { isMaskedKey } from '@/features/settings/providers-api';\nimport type { ProvidersSettingsMessages } from '@/i18n/messages';\nimport { settingsInputFocusClass } from '@/lib/form-field-width';\nimport { interaction } from '@/lib/interaction';\nimport { cn } from '@/lib/cn';\n\nexport type ImageProviderApiKeyFieldLabels = {\n apiKeyLabel: string;\n optionalPlaceholder: string;\n maskedHelp: string;\n copy: string;\n copied: string;\n show: string;\n hide: string;\n notInConfigFile: string;\n loadFailed: string;\n};\n\nexport function ImageProviderApiKeyField({\n providerId,\n value,\n onChange,\n labels,\n apiKeyLinks,\n apiKeyLinkLabels,\n}: {\n providerId: string;\n value: string;\n onChange: (next: string) => void;\n labels: ImageProviderApiKeyFieldLabels;\n apiKeyLinks: { href: string; kind: ApiKeyLinkKind }[];\n apiKeyLinkLabels: Pick<ProvidersSettingsMessages, 'getApiKey' | 'getApiKeyIntl' | 'getApiKeyCn'>;\n}) {\n const [showKey, setShowKey] = useState(false);\n /** `undefined` = not fetched; `null` = fetched, not in config file; string = plaintext from config */\n const [revealed, setRevealed] = useState<string | null | undefined>(undefined);\n const [revealLoading, setRevealLoading] = useState(false);\n const [revealErr, setRevealErr] = useState<string | null>(null);\n const [copied, setCopied] = useState(false);\n\n const masked = isMaskedKey(value);\n\n useEffect(() => {\n if (!masked) {\n setRevealed(undefined);\n setRevealErr(null);\n }\n }, [masked, value]);\n\n const inputValue = (() => {\n if (!masked) return value;\n if (showKey && typeof revealed === 'string') return revealed;\n return value;\n })();\n\n const inputType =\n !masked || (masked && showKey && typeof revealed === 'string') ? ('text' as const) : ('password' as const);\n\n const copyEnabled =\n (!masked && value.trim().length > 0 && !isMaskedKey(value)) ||\n (Boolean(showKey) && typeof revealed === 'string' && revealed.length > 0);\n\n const copyKey = useCallback(async () => {\n const text =\n !masked && value.trim() && !isMaskedKey(value)\n ? value.trim()\n : typeof revealed === 'string' && revealed.length > 0\n ? revealed\n : '';\n if (!text) return;\n try {\n await navigator.clipboard.writeText(text);\n setCopied(true);\n window.setTimeout(() => setCopied(false), 2000);\n } catch {\n /* ignore */\n }\n }, [masked, revealed, value]);\n\n const toggleEye = useCallback(async () => {\n setRevealErr(null);\n if (!masked) {\n setShowKey((s) => !s);\n return;\n }\n if (revealed !== undefined) {\n setShowKey((s) => !s);\n return;\n }\n setRevealLoading(true);\n try {\n const payload = await revealImageProviderConfigApiKey(providerId);\n setRevealed(payload.apiKey ?? null);\n setShowKey(true);\n } catch (e) {\n setRevealErr(e instanceof Error ? e.message : labels.loadFailed);\n setRevealed(null);\n } finally {\n setRevealLoading(false);\n }\n }, [masked, providerId, revealed, labels.loadFailed]);\n\n return (\n <div className=\"flex min-w-0 flex-col gap-1 sm:col-span-2\">\n <label className=\"text-xs font-medium text-fg-muted\" htmlFor={`img-cred-key-${providerId}`}>\n {labels.apiKeyLabel}\n </label>\n {apiKeyLinks.length > 0 ? (\n <div className=\"flex flex-col gap-1\">\n {apiKeyLinks.map((link) => (\n <a\n key={`${link.kind}-${link.href}`}\n href={link.href}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"inline-flex w-fit items-center gap-1 text-xs font-medium text-accent-fg hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent\"\n >\n {providerApiKeyLinkLabel(link.kind, apiKeyLinkLabels)}\n <ExternalLink className=\"size-3\" aria-hidden />\n </a>\n ))}\n </div>\n ) : null}\n {masked ? <p className=\"text-[11px] text-fg-subtle\">{labels.maskedHelp}</p> : null}\n <div className=\"relative min-w-0\">\n <input\n id={`img-cred-key-${providerId}`}\n type={inputType}\n autoComplete=\"off\"\n spellCheck={false}\n className={cn(\n 'w-full rounded-lg border border-edge bg-surface-panel py-2 pl-3 pr-24 font-mono text-sm text-fg',\n 'placeholder:text-fg-subtle',\n settingsInputFocusClass,\n )}\n value={inputValue}\n placeholder={masked ? '••••••••' : labels.optionalPlaceholder}\n onChange={(e) => {\n const next = e.target.value;\n if (masked && typeof revealed === 'string' && showKey && next !== revealed) {\n setRevealed(undefined);\n setShowKey(false);\n }\n onChange(next);\n }}\n />\n <div className=\"absolute right-1 top-1/2 flex -translate-y-1/2 gap-0.5\">\n {copyEnabled ? (\n <button\n type=\"button\"\n className={cn(\n 'rounded p-1.5 text-fg-subtle hover:bg-surface-hover hover:text-fg',\n interaction.transition,\n interaction.press,\n interaction.focusRingPanel,\n )}\n title={copied ? labels.copied : labels.copy}\n aria-label={copied ? labels.copied : labels.copy}\n onClick={() => void copyKey()}\n >\n {copied ? <CheckCircle2 className=\"size-4\" /> : <Copy className=\"size-4\" />}\n </button>\n ) : null}\n <button\n type=\"button\"\n className={cn(\n 'rounded p-1.5 text-fg-subtle hover:bg-surface-hover hover:text-fg disabled:opacity-40',\n interaction.transition,\n interaction.press,\n interaction.focusRingPanel,\n )}\n title={showKey ? labels.hide : labels.show}\n aria-label={showKey ? labels.hide : labels.show}\n disabled={revealLoading}\n onClick={() => void toggleEye()}\n >\n {revealLoading ? (\n <Loader2 className=\"size-4 animate-spin\" aria-hidden />\n ) : showKey ? (\n <EyeOff className=\"size-4\" aria-hidden />\n ) : (\n <Eye className=\"size-4\" aria-hidden />\n )}\n </button>\n </div>\n </div>\n {masked && showKey && revealed === null && !revealErr ? (\n <p className=\"text-xs text-amber-700 dark:text-amber-400/90\">{labels.notInConfigFile}</p>\n ) : null}\n {revealErr ? <p className=\"text-xs text-red-600 dark:text-red-400\">{revealErr}</p> : null}\n </div>\n );\n}\n","import { ExternalLink, Loader2, Save } from 'lucide-react';\nimport { Link } from 'react-router-dom';\n\nimport { Button } from '@/components/ui/button';\nimport { ImageProviderApiKeyField } from '@/features/settings/image-provider-api-key-field';\nimport { emptyImageProviderCredRow, type ImageProviderCredRow } from '@/features/settings/image-providers-config-api';\nimport { getOrderedApiKeyLinks } from '@/features/settings/provider-enrichment';\nimport type {\n ImageGenProviderCredentialSummary,\n ImageProviderUiMetadata,\n} from '@/features/settings/use-image-provider-credentials';\nimport type { ProvidersSettingsMessages } from '@/i18n/messages';\nimport { settingsInputFocusClass } from '@/lib/form-field-width';\nimport type { StoredLanguage } from '@/lib/storage';\nimport { cn } from '@/lib/cn';\n\nfunction inputClass(): string {\n return cn(\n 'w-full rounded-lg border border-edge bg-surface-panel px-3 py-2 text-sm text-fg',\n 'placeholder:text-fg-subtle',\n settingsInputFocusClass,\n );\n}\n\nfunction selectClass(): string {\n return cn(inputClass(), 'appearance-none bg-[length:1rem] bg-[right_0.5rem_center] bg-no-repeat pr-9');\n}\n\nconst CUSTOM_SENTINEL = '__custom__';\n\nfunction dashscopeSelectValue(\n row: ImageProviderCredRow,\n regions: NonNullable<ImageProviderUiMetadata['regions']>,\n): string {\n if (!row.region.trim() && !row.imageBaseUrl.trim()) return '';\n const r = row.region.trim().toLowerCase();\n if (regions.some((x) => x.value === r)) return r;\n return CUSTOM_SENTINEL;\n}\n\nfunction baseUrlSelectValue(\n row: ImageProviderCredRow,\n presets: NonNullable<ImageProviderUiMetadata['baseUrlPresets']>,\n): string {\n const b = row.baseUrl.trim().replace(/\\/+$/, '');\n if (!b) return '';\n const norm = presets.map((p) => p.value.replace(/\\/+$/, ''));\n const idx = norm.indexOf(b);\n if (idx >= 0) return presets[idx].value;\n return CUSTOM_SENTINEL;\n}\n\nexport type ImageProviderCredentialsPanelMessages = {\n credentialsIntro: string;\n regionHint: string;\n endpointPresetsHint: string;\n apiKeyLabel: string;\n optionalPlaceholder: string;\n regionLabel: string;\n baseUrlLabel: string;\n imageBaseUrlLabel: string;\n saveCredentials: string;\n savingCredentials: string;\n credentialsSaved: string;\n discardCredentials: string;\n credentialsNothingToSave: string;\n credentialsSaveError: string;\n regionPresetDefault: string;\n regionPresetCustom: string;\n baseUrlPresetDefault: string;\n baseUrlPresetCustom: string;\n openExtensionSettings: string;\n openImageModelsPage: string;\n extensionSettingsLinkTitle: string;\n imageModelsLinkTitle: string;\n configured: string;\n missingKey: string;\n defaultModel: string;\n modelsLabel: string;\n imageBaseUrlPresetHint: string;\n dashscopeRegion_beijing: string;\n dashscopeRegion_singapore: string;\n dashscopeRegion_us: string;\n apiKeyMaskedHelp: string;\n apiKeyCopy: string;\n apiKeyCopied: string;\n apiKeyShow: string;\n apiKeyHide: string;\n apiKeyNotInConfigFile: string;\n apiKeyRevealFailed: string;\n minimaxClusterLabel: string;\n minimaxClusterHint: string;\n falQueueBaseLabel: string;\n falQueueBaseHint: string;\n};\n\nfunction translateDashscopeRegion(m: ImageProviderCredentialsPanelMessages, value: string, serverLabel: string) {\n if (value === 'beijing') return m.dashscopeRegion_beijing;\n if (value === 'singapore') return m.dashscopeRegion_singapore;\n if (value === 'us') return m.dashscopeRegion_us;\n return serverLabel;\n}\n\nfunction baseUrlPresetBlockTitle(\n t: ImageProviderCredentialsPanelMessages,\n kind: ImageProviderUiMetadata['baseUrlPresetKind'],\n): string {\n if (kind === 'minimax') return t.minimaxClusterLabel;\n if (kind === 'fal') return t.falQueueBaseLabel;\n return t.baseUrlLabel;\n}\n\nfunction baseUrlPresetBlockHint(\n t: ImageProviderCredentialsPanelMessages,\n kind: ImageProviderUiMetadata['baseUrlPresetKind'],\n): string | null {\n if (kind === 'minimax') return t.minimaxClusterHint;\n if (kind === 'fal') return t.falQueueBaseHint;\n return null;\n}\n\nexport function ImageProviderCredentialsPanel({\n summaries,\n credDraft,\n credDirty,\n credSaving,\n credError,\n credSavedFlash,\n credNoopFlash,\n updateCredRow,\n onDiscardCredentials,\n onSaveCredentials,\n extensionIds,\n showExtensionLinks,\n showImageModelsLink,\n language,\n apiKeyLinkLabels,\n messages: t,\n}: {\n summaries: ImageGenProviderCredentialSummary[];\n credDraft: Record<string, ImageProviderCredRow>;\n credDirty: boolean;\n credSaving: boolean;\n credError: string | null;\n credSavedFlash: boolean;\n credNoopFlash: boolean;\n updateCredRow: (id: string, patch: Partial<ImageProviderCredRow>) => void;\n onDiscardCredentials: () => void;\n onSaveCredentials: () => void;\n /** Extension ids present in gateway discovery (for deep links). */\n extensionIds: Set<string>;\n showExtensionLinks: boolean;\n showImageModelsLink: boolean;\n language: StoredLanguage;\n apiKeyLinkLabels: Pick<ProvidersSettingsMessages, 'getApiKey' | 'getApiKeyIntl' | 'getApiKeyCn'>;\n messages: ImageProviderCredentialsPanelMessages;\n}) {\n const empty = summaries.length === 0;\n\n if (empty) {\n return null;\n }\n\n const anyRegionUi = summaries.some((s) => (s.ui?.regions?.length ?? 0) > 0);\n const anyBaseUrlPresets = summaries.some((s) => (s.ui?.baseUrlPresets?.length ?? 0) > 0);\n\n return (\n <div className=\"flex flex-col gap-4\">\n <div className=\"flex flex-col gap-1 text-xs leading-relaxed text-fg-muted\">\n <p>{t.credentialsIntro}</p>\n {anyRegionUi ? <p className=\"text-fg-subtle\">{t.regionHint}</p> : null}\n {anyBaseUrlPresets ? <p className=\"text-fg-subtle\">{t.endpointPresetsHint}</p> : null}\n {showImageModelsLink ? (\n <p>\n <Link\n to=\"/settings/image-models\"\n className=\"font-medium text-accent hover:underline\"\n title={t.imageModelsLinkTitle}\n >\n {t.openImageModelsPage}\n </Link>\n </p>\n ) : null}\n </div>\n {credError ? (\n <div className=\"rounded-lg border border-red-500/30 bg-red-500/10 px-3 py-2 text-sm text-red-700 dark:text-red-300\">\n {credError}\n </div>\n ) : null}\n <div className=\"flex flex-wrap items-center justify-end gap-2\">\n {credSavedFlash ? (\n <span className=\"text-sm text-fg-muted\">{t.credentialsSaved}</span>\n ) : null}\n {credNoopFlash ? (\n <span className=\"text-sm text-fg-muted\">{t.credentialsNothingToSave}</span>\n ) : null}\n <Button type=\"button\" variant=\"secondary\" onClick={onDiscardCredentials} disabled={!credDirty || credSaving}>\n {t.discardCredentials}\n </Button>\n <Button type=\"button\" variant=\"primary\" onClick={onSaveCredentials} disabled={!credDirty || credSaving}>\n {credSaving ? (\n <>\n <Loader2 className=\"size-3.5 animate-spin\" />\n <span className=\"ml-1.5\">{t.savingCredentials}</span>\n </>\n ) : (\n <>\n <Save className=\"size-3.5\" />\n <span className=\"ml-1.5\">{t.saveCredentials}</span>\n </>\n )}\n </Button>\n </div>\n <div className=\"flex flex-col gap-4\">\n {summaries.map((p) => {\n const row = credDraft[p.id] ?? emptyImageProviderCredRow();\n const ui = p.ui;\n const extPath =\n showExtensionLinks && extensionIds.has(p.id)\n ? `/settings/ext/${encodeURIComponent(p.id)}`\n : null;\n return (\n <div\n key={p.id}\n className=\"rounded-lg border border-edge bg-surface-panel px-4 py-3 shadow-sm dark:shadow-none\"\n >\n <div className=\"flex flex-wrap items-center justify-between gap-3\">\n <div className=\"flex min-w-0 flex-wrap items-center gap-2\">\n <span className=\"text-sm font-semibold text-fg\">{p.label ?? p.id}</span>\n <span className=\"text-xs text-fg-subtle\">({p.id})</span>\n {extPath ? (\n <Link\n to={extPath}\n className=\"inline-flex items-center gap-1 text-xs font-medium text-accent hover:underline\"\n title={t.extensionSettingsLinkTitle}\n >\n <ExternalLink className=\"size-3\" />\n {t.openExtensionSettings}\n </Link>\n ) : null}\n </div>\n {p.configured ? (\n <span className=\"rounded-full bg-accent-soft px-2 py-0.5 text-xs font-medium text-accent-fg\">\n {t.configured}\n </span>\n ) : (\n <span className=\"rounded-full border border-amber-500/40 bg-amber-500/10 px-2 py-0.5 text-xs font-medium text-amber-700 dark:text-amber-300\">\n {t.missingKey}\n </span>\n )}\n </div>\n {p.defaultModel ? (\n <p className=\"mt-1 text-xs text-fg-subtle\">\n <span className=\"text-fg-muted\">{t.defaultModel}:</span> {p.id}/{p.defaultModel}\n </p>\n ) : null}\n {p.models.length > 0 ? (\n <p className=\"mt-0.5 text-xs text-fg-subtle\">\n <span className=\"text-fg-muted\">{t.modelsLabel}:</span>{' '}\n {p.models.map((mm) => `${p.id}/${mm}`).join(', ')}\n </p>\n ) : null}\n <div className=\"mt-4 grid gap-3 sm:grid-cols-2\">\n <ImageProviderApiKeyField\n providerId={p.id}\n value={row.apiKey}\n onChange={(next) => updateCredRow(p.id, { apiKey: next })}\n apiKeyLinks={getOrderedApiKeyLinks(p.id, language)}\n apiKeyLinkLabels={apiKeyLinkLabels}\n labels={{\n apiKeyLabel: t.apiKeyLabel,\n optionalPlaceholder: t.optionalPlaceholder,\n maskedHelp: t.apiKeyMaskedHelp,\n copy: t.apiKeyCopy,\n copied: t.apiKeyCopied,\n show: t.apiKeyShow,\n hide: t.apiKeyHide,\n notInConfigFile: t.apiKeyNotInConfigFile,\n loadFailed: t.apiKeyRevealFailed,\n }}\n />\n\n {ui?.regions?.length ? (\n <div className=\"flex min-w-0 flex-col gap-1 sm:col-span-2\">\n <label className=\"text-xs font-medium text-fg-muted\" htmlFor={`img-cred-region-preset-${p.id}`}>\n {t.regionLabel}\n </label>\n <select\n id={`img-cred-region-preset-${p.id}`}\n className={selectClass()}\n value={dashscopeSelectValue(row, ui.regions)}\n onChange={(e) => {\n const v = e.target.value;\n if (v === '') {\n updateCredRow(p.id, { region: '', imageBaseUrl: '' });\n return;\n }\n if (v === CUSTOM_SENTINEL) {\n updateCredRow(p.id, { region: '', imageBaseUrl: '' });\n return;\n }\n const opt = ui.regions!.find((x) => x.value === v);\n if (opt) {\n updateCredRow(p.id, { region: opt.value, imageBaseUrl: opt.imageBaseUrl });\n }\n }}\n >\n <option value=\"\">{t.regionPresetDefault}</option>\n {ui.regions.map((r) => (\n <option key={r.value} value={r.value}>\n {translateDashscopeRegion(t, r.value, r.label)}\n </option>\n ))}\n <option value={CUSTOM_SENTINEL}>{t.regionPresetCustom}</option>\n </select>\n {dashscopeSelectValue(row, ui.regions) === CUSTOM_SENTINEL ? (\n <div className=\"mt-2 grid gap-2 sm:grid-cols-2\">\n <input\n type=\"text\"\n className={inputClass()}\n value={row.region}\n placeholder=\"region\"\n onChange={(e) => updateCredRow(p.id, { region: e.target.value })}\n />\n <input\n type=\"url\"\n className={inputClass()}\n value={row.imageBaseUrl}\n placeholder={t.imageBaseUrlLabel}\n onChange={(e) => updateCredRow(p.id, { imageBaseUrl: e.target.value })}\n />\n </div>\n ) : null}\n </div>\n ) : null}\n\n {ui?.baseUrlPresets?.length ? (\n <div className=\"flex min-w-0 flex-col gap-1 sm:col-span-2\">\n <label className=\"text-xs font-medium text-fg-muted\" htmlFor={`img-cred-base-preset-${p.id}`}>\n {baseUrlPresetBlockTitle(t, ui.baseUrlPresetKind)}\n </label>\n {baseUrlPresetBlockHint(t, ui.baseUrlPresetKind) ? (\n <p className=\"text-[11px] text-fg-subtle\">{baseUrlPresetBlockHint(t, ui.baseUrlPresetKind)}</p>\n ) : null}\n <select\n id={`img-cred-base-preset-${p.id}`}\n className={selectClass()}\n value={baseUrlSelectValue(row, ui.baseUrlPresets)}\n onChange={(e) => {\n const v = e.target.value;\n if (v === '') {\n updateCredRow(p.id, { baseUrl: '' });\n return;\n }\n if (v === CUSTOM_SENTINEL) {\n updateCredRow(p.id, { baseUrl: '' });\n return;\n }\n updateCredRow(p.id, { baseUrl: v.replace(/\\/+$/, '') });\n }}\n >\n <option value=\"\">{t.baseUrlPresetDefault}</option>\n {ui.baseUrlPresets.map((b) => (\n <option key={b.value} value={b.value}>\n {b.label}\n </option>\n ))}\n <option value={CUSTOM_SENTINEL}>{t.baseUrlPresetCustom}</option>\n </select>\n {baseUrlSelectValue(row, ui.baseUrlPresets) === CUSTOM_SENTINEL ? (\n <input\n type=\"url\"\n className={cn(inputClass(), 'mt-2')}\n value={row.baseUrl}\n placeholder=\"https://…\"\n onChange={(e) => updateCredRow(p.id, { baseUrl: e.target.value })}\n />\n ) : null}\n </div>\n ) : null}\n\n {ui?.regions?.length && dashscopeSelectValue(row, ui.regions) !== CUSTOM_SENTINEL ? (\n <div className=\"flex min-w-0 flex-col gap-1 sm:col-span-2\">\n <label className=\"text-xs font-medium text-fg-muted\" htmlFor={`img-cred-imgbase-ro-${p.id}`}>\n {t.imageBaseUrlLabel}\n </label>\n <input\n id={`img-cred-imgbase-ro-${p.id}`}\n type=\"url\"\n readOnly\n className={cn(inputClass(), 'cursor-not-allowed opacity-90')}\n value={row.imageBaseUrl}\n title={t.imageBaseUrlPresetHint}\n />\n <p className=\"text-[11px] text-fg-subtle\">{t.imageBaseUrlPresetHint}</p>\n </div>\n ) : null}\n </div>\n </div>\n );\n })}\n </div>\n </div>\n );\n}\n","import { useCallback, useEffect, useMemo, useState } from 'react';\nimport { mutate } from 'swr';\n\nimport { useGatewayConfigSwr } from '@/features/gateway/gateway-config-swr';\nimport {\n buildImageProvidersConfigPatch,\n emptyImageProviderCredRow,\n imageProviderCredRowsFromConfigRoot,\n patchImageProvidersConfig,\n type ImageProviderCredRow,\n} from '@/features/settings/image-providers-config-api';\nimport { IMAGE_PROVIDERS_SWR_KEY } from '@/features/settings/image-providers-swr-key';\nimport { apiUrl } from '@/lib/url';\nimport { useGatewayStore } from '@/stores/gateway-store';\n\nexport type ImageProviderUiRegionOption = {\n value: string;\n label: string;\n imageBaseUrl: string;\n};\n\nexport type ImageProviderUiBaseUrlPreset = {\n value: string;\n label: string;\n};\n\nexport type ImageProviderUiMetadata = {\n regions?: ImageProviderUiRegionOption[];\n baseUrlPresets?: ImageProviderUiBaseUrlPreset[];\n baseUrlPresetKind?: 'fal' | 'minimax' | 'google' | 'openai';\n};\n\nexport type ImageGenProviderCredentialSummary = {\n id: string;\n label?: string;\n defaultModel?: string;\n models: string[];\n configured?: boolean;\n ui?: ImageProviderUiMetadata;\n};\n\nexport function useImageProviderCredentials(summaries: ImageGenProviderCredentialSummary[]) {\n const hasToken = useGatewayStore((s) => Boolean(s.token));\n const gwSwr = useGatewayConfigSwr(hasToken);\n const gwCfg = gwSwr.data;\n\n const ids = useMemo(() => summaries.map((s) => s.id), [summaries]);\n\n const [credDraft, setCredDraft] = useState<Record<string, ImageProviderCredRow>>({});\n const [credBaseline, setCredBaseline] = useState<Record<string, ImageProviderCredRow>>({});\n const [credSaving, setCredSaving] = useState(false);\n const [credError, setCredError] = useState<string | null>(null);\n const [credSavedFlash, setCredSavedFlash] = useState(false);\n const [credNoopFlash, setCredNoopFlash] = useState(false);\n\n const credRowsFromServer = useMemo(\n () => imageProviderCredRowsFromConfigRoot(gwCfg?.payload?.config, ids),\n [gwCfg?.payload?.config, ids],\n );\n\n const credDirty = useMemo(\n () => JSON.stringify(credDraft) !== JSON.stringify(credBaseline),\n [credDraft, credBaseline],\n );\n\n useEffect(() => {\n if (!credDirty) {\n setCredDraft(structuredClone(credRowsFromServer));\n setCredBaseline(structuredClone(credRowsFromServer));\n }\n }, [credRowsFromServer, credDirty]);\n\n const updateCredRow = useCallback((id: string, patch: Partial<ImageProviderCredRow>) => {\n setCredDraft((prev) => {\n const base = prev[id] ?? emptyImageProviderCredRow();\n return { ...prev, [id]: { ...base, ...patch } };\n });\n }, []);\n\n const onDiscardCredentials = useCallback(() => {\n setCredDraft(structuredClone(credBaseline));\n setCredError(null);\n setCredSavedFlash(false);\n setCredNoopFlash(false);\n }, [credBaseline]);\n\n const saveCredentials = useCallback(\n async (errorFallback: string) => {\n const patch = buildImageProvidersConfigPatch(ids, credDraft, credBaseline);\n if (Object.keys(patch).length === 0) {\n setCredNoopFlash(true);\n window.setTimeout(() => setCredNoopFlash(false), 2200);\n return;\n }\n setCredSaving(true);\n setCredError(null);\n setCredSavedFlash(false);\n try {\n await patchImageProvidersConfig(patch);\n const updated = await gwSwr.mutate?.();\n void mutate(apiUrl(IMAGE_PROVIDERS_SWR_KEY));\n const nextRows = imageProviderCredRowsFromConfigRoot(updated?.payload?.config, ids);\n setCredDraft(structuredClone(nextRows));\n setCredBaseline(structuredClone(nextRows));\n setCredSavedFlash(true);\n window.setTimeout(() => setCredSavedFlash(false), 2000);\n } catch (e) {\n setCredError(e instanceof Error ? e.message : errorFallback);\n } finally {\n setCredSaving(false);\n }\n },\n [ids, credDraft, credBaseline, gwSwr],\n );\n\n return {\n gwSwr,\n credDraft,\n credBaseline,\n credDirty,\n credSaving,\n credError,\n credSavedFlash,\n credNoopFlash,\n updateCredRow,\n onDiscardCredentials,\n saveCredentials,\n };\n}\n"],"mappings":"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"}
@@ -9,7 +9,7 @@
9
9
  <link rel="icon" type="image/svg+xml" href="/logo.svg" />
10
10
  <link rel="apple-touch-icon" href="/logo.svg" />
11
11
  <title>xopc</title>
12
- <script type="module" crossorigin src="/assets/index-DeELk--t.js"></script>
12
+ <script type="module" crossorigin src="/assets/index-DW6JvymK.js"></script>
13
13
  <link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-DWdDZTNf.js">
14
14
  <link rel="modulepreload" crossorigin href="/assets/vendor-codemirror-CXAvob9m.js">
15
15
  <link rel="modulepreload" crossorigin href="/assets/vendor-react-DbimaAId.js">
package/dist/package.js CHANGED
@@ -1,5 +1,5 @@
1
1
  //#region package.json
2
- var version = "0.0.47";
2
+ var version = "0.0.48";
3
3
  //#endregion
4
4
  export { version };
5
5
 
@@ -1,7 +1,7 @@
1
1
  import { init_providers, resolveModel } from "../../providers/index.js";
2
2
  import { JUDGE_REASON_EN } from "../../i18n/goals-bundle.js";
3
3
  import { goalUiLocaleOrFallback, judgeReasonText, judgeResponseLanguageNote, localizeJudgeReasonText } from "./goal-locale.js";
4
- import { DEFAULT_JUDGE_TIMEOUT_MS, truncateGoalText } from "./judge.js";
4
+ import { DEFAULT_JUDGE_TIMEOUT_MS, extractAssistantText, getAssistantMessageErrorReason, resolveGoalJudgeApiKey, stripCodeFences, truncateGoalText } from "./judge.js";
5
5
  import { complete } from "@earendil-works/pi-ai";
6
6
  //#region src/agent/goals/checklist-judge.ts
7
7
  init_providers();
@@ -13,12 +13,7 @@ const DECOMPOSE_USER = "Goal:\n{goal}\n\nReturn JSON only.";
13
13
  const EVALUATE_CHECKLIST_SYSTEM = "You evaluate an autonomous agent's progress on a goal with a numbered checklist.\nFor each pending item, decide if evidence in the agent snippet and/or history excerpt shows it is satisfied.\nFlip pending→completed only with clear evidence; pending→impossible only if truly unachievable here.\nDo not regress completed/impossible items — omit them from updates.\n\nReply ONLY with one JSON object on one line. Field \"updates\" is an array of {\"index\": <1-based number>, \"status\": \"completed\" or \"impossible\", \"evidence\": \"short citation\"}. Field \"new_items\" is an array of {\"text\": \"...\"} for missing criteria. Field \"reason\" is one sentence. updates and new_items may be empty arrays.";
14
14
  function extractJsonObject(raw) {
15
15
  if (!raw?.trim()) return null;
16
- let text = raw.trim();
17
- if (text.startsWith("```")) {
18
- text = text.replace(/^`+/, "");
19
- const nl = text.indexOf("\n");
20
- if (nl !== -1) text = text.slice(nl + 1);
21
- }
16
+ const text = stripCodeFences(raw);
22
17
  try {
23
18
  const data = JSON.parse(text);
24
19
  return data && typeof data === "object" && !Array.isArray(data) ? data : null;
@@ -62,16 +57,19 @@ async function decomposeGoalChecklist(opts) {
62
57
  content: `${DECOMPOSE_SYSTEM}\n\n${DECOMPOSE_USER.replace("{goal}", truncateGoalText(goal, GOAL_TRUNC))}` + judgeResponseLanguageNote(locale),
63
58
  timestamp: Date.now()
64
59
  };
60
+ const apiKey = await resolveGoalJudgeApiKey(model);
65
61
  const result = await complete(model, { messages: [user] }, {
62
+ apiKey,
66
63
  maxTokens: 2e3,
67
64
  temperature: 0,
68
65
  signal: merged
69
66
  });
70
- let text = "";
71
- if (Array.isArray(result.content)) {
72
- for (const c of result.content) if (c && typeof c === "object" && c.type === "text") text += String(c.text || "");
73
- }
74
- const data = extractJsonObject(text);
67
+ if (getAssistantMessageErrorReason(result)) return {
68
+ items: [],
69
+ parseFailed: false,
70
+ errorReason: judgeReasonText("decompose_call_failed", locale)
71
+ };
72
+ const data = extractJsonObject(extractAssistantText(result.content));
75
73
  if (!data) return {
76
74
  items: [],
77
75
  parseFailed: true,
@@ -136,16 +134,22 @@ async function evaluateGoalChecklistJudge(opts) {
136
134
  content: `${EVALUATE_CHECKLIST_SYSTEM}\n\n${userBody}` + judgeResponseLanguageNote(locale),
137
135
  timestamp: Date.now()
138
136
  };
137
+ const apiKey = await resolveGoalJudgeApiKey(model);
139
138
  const result = await complete(model, { messages: [user] }, {
139
+ apiKey,
140
140
  maxTokens: 1500,
141
141
  temperature: 0,
142
142
  signal: merged
143
143
  });
144
- let text = "";
145
- if (Array.isArray(result.content)) {
146
- for (const c of result.content) if (c && typeof c === "object" && c.type === "text") text += String(c.text || "");
147
- }
148
- const data = extractJsonObject(text);
144
+ if (getAssistantMessageErrorReason(result)) return {
145
+ parsed: {
146
+ updates: [],
147
+ newItems: [],
148
+ reason: judgeReasonText("judge_call_failed", locale)
149
+ },
150
+ parseFailed: false
151
+ };
152
+ const data = extractJsonObject(extractAssistantText(result.content));
149
153
  if (!data) return {
150
154
  parsed: {
151
155
  updates: [],
@@ -1 +1 @@
1
- {"version":3,"file":"checklist-judge.js","names":[],"sources":["../../../../src/agent/goals/checklist-judge.ts"],"sourcesContent":["import type { UserMessage } from '@earendil-works/pi-ai';\nimport { complete } from '@earendil-works/pi-ai';\n\nimport { resolveModel } from '../../providers/index.js';\n\nimport { DEFAULT_JUDGE_TIMEOUT_MS, truncateGoalText } from './judge.js';\nimport type { GoalUiLocale } from './goal-locale.js';\nimport {\n goalUiLocaleOrFallback,\n judgeReasonText,\n judgeResponseLanguageNote,\n JUDGE_REASON_EN,\n localizeJudgeReasonText,\n} from './goal-locale.js';\n\nconst GOAL_TRUNC = 4000;\nconst RESPONSE_SNIPPET = 4000;\nconst HISTORY_TRUNC = 24_000;\n\nconst DECOMPOSE_SYSTEM = (\n 'You break a user goal into an EXTREMELY detailed checklist of concrete, verifiable completion criteria. ' +\n 'Bias toward more items. Each item is one factual statement about finished work.\\n\\n' +\n 'Reply ONLY with a single JSON object on one line:\\n' +\n '{\"items\":[{\"text\":\"...\"},...]}\\n' +\n 'Use at least 3 items when the goal warrants it.'\n);\n\nconst DECOMPOSE_USER = 'Goal:\\n{goal}\\n\\nReturn JSON only.';\n\nconst EVALUATE_CHECKLIST_SYSTEM = (\n 'You evaluate an autonomous agent\\'s progress on a goal with a numbered checklist.\\n' +\n 'For each pending item, decide if evidence in the agent snippet and/or history excerpt shows it is satisfied.\\n' +\n 'Flip pending→completed only with clear evidence; pending→impossible only if truly unachievable here.\\n' +\n 'Do not regress completed/impossible items — omit them from updates.\\n\\n' +\n 'Reply ONLY with one JSON object on one line. Field \"updates\" is an array of ' +\n '{\"index\": <1-based number>, \"status\": \"completed\" or \"impossible\", \"evidence\": \"short citation\"}. ' +\n 'Field \"new_items\" is an array of {\"text\": \"...\"} for missing criteria. ' +\n 'Field \"reason\" is one sentence. updates and new_items may be empty arrays.'\n);\n\nfunction extractJsonObject(raw: string): Record<string, unknown> | null {\n if (!raw?.trim()) return null;\n let text = raw.trim();\n if (text.startsWith('```')) {\n text = text.replace(/^`+/, '');\n const nl = text.indexOf('\\n');\n if (nl !== -1) text = text.slice(nl + 1);\n }\n try {\n const data = JSON.parse(text) as unknown;\n return data && typeof data === 'object' && !Array.isArray(data) ? (data as Record<string, unknown>) : null;\n } catch {\n const start = text.indexOf('{');\n const end = text.lastIndexOf('}');\n if (start === -1 || end <= start) return null;\n try {\n const data = JSON.parse(text.slice(start, end + 1)) as unknown;\n return data && typeof data === 'object' && !Array.isArray(data) ? (data as Record<string, unknown>) : null;\n } catch {\n return null;\n }\n }\n}\n\nexport type DecomposeChecklistResult = {\n items: { text: string }[];\n parseFailed: boolean;\n errorReason?: string;\n};\n\nexport async function decomposeGoalChecklist(opts: {\n goal: string;\n judgeModelRef: string;\n signal?: AbortSignal;\n judgeTimeoutMs?: number;\n uiLocale?: GoalUiLocale;\n}): Promise<DecomposeChecklistResult> {\n const locale = goalUiLocaleOrFallback(opts.uiLocale);\n const goal = opts.goal.trim();\n if (!goal)\n return { items: [], parseFailed: false, errorReason: judgeReasonText('empty_goal', locale) };\n\n let model: ReturnType<typeof resolveModel>;\n try {\n model = resolveModel(opts.judgeModelRef);\n } catch {\n return {\n items: [],\n parseFailed: false,\n errorReason: judgeReasonText('judge_model_not_configured', locale),\n };\n }\n\n const timeoutMs =\n typeof opts.judgeTimeoutMs === 'number' && Number.isFinite(opts.judgeTimeoutMs)\n ? Math.max(5_000, Math.min(120_000, Math.floor(opts.judgeTimeoutMs)))\n : DEFAULT_JUDGE_TIMEOUT_MS;\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n const merged = opts.signal ? AbortSignal.any([opts.signal, controller.signal]) : controller.signal;\n\n try {\n const user: UserMessage = {\n role: 'user',\n content:\n `${DECOMPOSE_SYSTEM}\\n\\n${DECOMPOSE_USER.replace('{goal}', truncateGoalText(goal, GOAL_TRUNC))}` +\n judgeResponseLanguageNote(locale),\n timestamp: Date.now(),\n };\n const result = await complete(model, { messages: [user] }, { maxTokens: 2000, temperature: 0, signal: merged });\n let text = '';\n if (Array.isArray(result.content)) {\n for (const c of result.content) {\n if (c && typeof c === 'object' && (c as { type?: string }).type === 'text') {\n text += String((c as { text?: string }).text || '');\n }\n }\n }\n const data = extractJsonObject(text);\n if (!data)\n return {\n items: [],\n parseFailed: true,\n errorReason: judgeReasonText('decompose_reply_not_json', locale),\n };\n const rawItems = data.items ?? data.checklist;\n if (!Array.isArray(rawItems))\n return {\n items: [],\n parseFailed: true,\n errorReason: judgeReasonText('missing_items_array', locale),\n };\n const items: { text: string }[] = [];\n for (const row of rawItems) {\n if (typeof row === 'string') {\n const t = row.trim();\n if (t) items.push({ text: t });\n } else if (row && typeof row === 'object' && !Array.isArray(row)) {\n const t = String((row as { text?: string }).text ?? '').trim();\n if (t) items.push({ text: t });\n }\n }\n if (!items.length)\n return { items: [], parseFailed: true, errorReason: judgeReasonText('empty_checklist', locale) };\n return { items, parseFailed: false };\n } catch {\n return {\n items: [],\n parseFailed: false,\n errorReason: judgeReasonText('decompose_call_failed', locale),\n };\n } finally {\n clearTimeout(timer);\n }\n}\n\nexport type ChecklistJudgeUpdate = {\n index: number;\n status: 'completed' | 'impossible';\n evidence?: string | null;\n};\n\nexport type ChecklistEvaluateParsed = {\n updates: ChecklistJudgeUpdate[];\n newItems: { text: string }[];\n reason: string;\n};\n\nexport type EvaluateChecklistResult = {\n parsed: ChecklistEvaluateParsed;\n parseFailed: boolean;\n};\n\nexport async function evaluateGoalChecklistJudge(opts: {\n goal: string;\n numberedChecklist: string;\n lastResponse: string;\n historyExcerpt: string;\n judgeModelRef: string;\n signal?: AbortSignal;\n judgeTimeoutMs?: number;\n uiLocale?: GoalUiLocale;\n}): Promise<EvaluateChecklistResult> {\n const locale = goalUiLocaleOrFallback(opts.uiLocale);\n let model: ReturnType<typeof resolveModel>;\n try {\n model = resolveModel(opts.judgeModelRef);\n } catch {\n return {\n parsed: {\n updates: [],\n newItems: [],\n reason: judgeReasonText('judge_model_not_configured', locale),\n },\n parseFailed: false,\n };\n }\n\n const userBody =\n `Goal:\\n${truncateGoalText(opts.goal, 2000)}\\n\\n` +\n `Current checklist (1-based indices):\\n${opts.numberedChecklist}\\n\\n` +\n `Agent's most recent response (snippet):\\n${truncateGoalText(opts.lastResponse, RESPONSE_SNIPPET)}\\n\\n` +\n `Recent conversation excerpt (JSON or text, may be truncated):\\n` +\n `${truncateGoalText(opts.historyExcerpt, HISTORY_TRUNC) || '(none)'}`;\n\n const timeoutMs =\n typeof opts.judgeTimeoutMs === 'number' && Number.isFinite(opts.judgeTimeoutMs)\n ? Math.max(5_000, Math.min(120_000, Math.floor(opts.judgeTimeoutMs)))\n : DEFAULT_JUDGE_TIMEOUT_MS;\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n const merged = opts.signal ? AbortSignal.any([opts.signal, controller.signal]) : controller.signal;\n\n try {\n const user: UserMessage = {\n role: 'user',\n content: `${EVALUATE_CHECKLIST_SYSTEM}\\n\\n${userBody}` + judgeResponseLanguageNote(locale),\n timestamp: Date.now(),\n };\n const result = await complete(model, { messages: [user] }, { maxTokens: 1500, temperature: 0, signal: merged });\n let text = '';\n if (Array.isArray(result.content)) {\n for (const c of result.content) {\n if (c && typeof c === 'object' && (c as { type?: string }).type === 'text') {\n text += String((c as { text?: string }).text || '');\n }\n }\n }\n const data = extractJsonObject(text);\n if (!data) {\n return {\n parsed: {\n updates: [],\n newItems: [],\n reason: judgeReasonText('judge_reply_not_json', locale),\n },\n parseFailed: true,\n };\n }\n const updatesRaw = data.updates;\n const newRaw = data.new_items ?? data.newItems;\n const trimmed = typeof data.reason === 'string' ? data.reason.trim() : '';\n const inner = trimmed || JUDGE_REASON_EN.no_reason_provided;\n const reason = localizeJudgeReasonText(inner, locale);\n const updates: ChecklistJudgeUpdate[] = [];\n if (Array.isArray(updatesRaw)) {\n for (const u of updatesRaw) {\n if (!u || typeof u !== 'object' || Array.isArray(u)) continue;\n const rec = u as Record<string, unknown>;\n const idx1 = Number(rec.index);\n if (!Number.isFinite(idx1)) continue;\n const idx0 = Math.floor(idx1) - 1;\n const st = String(rec.status ?? '').trim().toLowerCase();\n if (st !== 'completed' && st !== 'impossible') continue;\n const evidence = typeof rec.evidence === 'string' ? rec.evidence : null;\n updates.push({ index: idx0, status: st, evidence });\n }\n }\n const newItems: { text: string }[] = [];\n if (Array.isArray(newRaw)) {\n for (const row of newRaw) {\n if (typeof row === 'string') {\n const t = row.trim();\n if (t) newItems.push({ text: t });\n } else if (row && typeof row === 'object' && !Array.isArray(row)) {\n const t = String((row as { text?: string }).text ?? '').trim();\n if (t) newItems.push({ text: t });\n }\n }\n }\n return {\n parsed: {\n updates,\n newItems,\n reason: reason || judgeReasonText('no_reason_provided', locale),\n },\n parseFailed: false,\n };\n } catch {\n return {\n parsed: {\n updates: [],\n newItems: [],\n reason: judgeReasonText('judge_call_failed', locale),\n },\n parseFailed: false,\n };\n } finally {\n clearTimeout(timer);\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"checklist-judge.js","names":[],"sources":["../../../../src/agent/goals/checklist-judge.ts"],"sourcesContent":["import type { UserMessage } from '@earendil-works/pi-ai';\nimport { complete } from '@earendil-works/pi-ai';\n\nimport { resolveModel } from '../../providers/index.js';\n\nimport {\n DEFAULT_JUDGE_TIMEOUT_MS,\n extractAssistantText,\n getAssistantMessageErrorReason,\n resolveGoalJudgeApiKey,\n stripCodeFences,\n truncateGoalText,\n} from './judge.js';\nimport type { GoalUiLocale } from './goal-locale.js';\nimport {\n goalUiLocaleOrFallback,\n judgeReasonText,\n judgeResponseLanguageNote,\n JUDGE_REASON_EN,\n localizeJudgeReasonText,\n} from './goal-locale.js';\n\nconst GOAL_TRUNC = 4000;\nconst RESPONSE_SNIPPET = 4000;\nconst HISTORY_TRUNC = 24_000;\n\nconst DECOMPOSE_SYSTEM = (\n 'You break a user goal into an EXTREMELY detailed checklist of concrete, verifiable completion criteria. ' +\n 'Bias toward more items. Each item is one factual statement about finished work.\\n\\n' +\n 'Reply ONLY with a single JSON object on one line:\\n' +\n '{\"items\":[{\"text\":\"...\"},...]}\\n' +\n 'Use at least 3 items when the goal warrants it.'\n);\n\nconst DECOMPOSE_USER = 'Goal:\\n{goal}\\n\\nReturn JSON only.';\n\nconst EVALUATE_CHECKLIST_SYSTEM = (\n 'You evaluate an autonomous agent\\'s progress on a goal with a numbered checklist.\\n' +\n 'For each pending item, decide if evidence in the agent snippet and/or history excerpt shows it is satisfied.\\n' +\n 'Flip pending→completed only with clear evidence; pending→impossible only if truly unachievable here.\\n' +\n 'Do not regress completed/impossible items — omit them from updates.\\n\\n' +\n 'Reply ONLY with one JSON object on one line. Field \"updates\" is an array of ' +\n '{\"index\": <1-based number>, \"status\": \"completed\" or \"impossible\", \"evidence\": \"short citation\"}. ' +\n 'Field \"new_items\" is an array of {\"text\": \"...\"} for missing criteria. ' +\n 'Field \"reason\" is one sentence. updates and new_items may be empty arrays.'\n);\n\nfunction extractJsonObject(raw: string): Record<string, unknown> | null {\n if (!raw?.trim()) return null;\n const text = stripCodeFences(raw);\n try {\n const data = JSON.parse(text) as unknown;\n return data && typeof data === 'object' && !Array.isArray(data) ? (data as Record<string, unknown>) : null;\n } catch {\n const start = text.indexOf('{');\n const end = text.lastIndexOf('}');\n if (start === -1 || end <= start) return null;\n try {\n const data = JSON.parse(text.slice(start, end + 1)) as unknown;\n return data && typeof data === 'object' && !Array.isArray(data) ? (data as Record<string, unknown>) : null;\n } catch {\n return null;\n }\n }\n}\n\nexport type DecomposeChecklistResult = {\n items: { text: string }[];\n parseFailed: boolean;\n errorReason?: string;\n};\n\nexport async function decomposeGoalChecklist(opts: {\n goal: string;\n judgeModelRef: string;\n signal?: AbortSignal;\n judgeTimeoutMs?: number;\n uiLocale?: GoalUiLocale;\n}): Promise<DecomposeChecklistResult> {\n const locale = goalUiLocaleOrFallback(opts.uiLocale);\n const goal = opts.goal.trim();\n if (!goal)\n return { items: [], parseFailed: false, errorReason: judgeReasonText('empty_goal', locale) };\n\n let model: ReturnType<typeof resolveModel>;\n try {\n model = resolveModel(opts.judgeModelRef);\n } catch {\n return {\n items: [],\n parseFailed: false,\n errorReason: judgeReasonText('judge_model_not_configured', locale),\n };\n }\n\n const timeoutMs =\n typeof opts.judgeTimeoutMs === 'number' && Number.isFinite(opts.judgeTimeoutMs)\n ? Math.max(5_000, Math.min(120_000, Math.floor(opts.judgeTimeoutMs)))\n : DEFAULT_JUDGE_TIMEOUT_MS;\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n const merged = opts.signal ? AbortSignal.any([opts.signal, controller.signal]) : controller.signal;\n\n try {\n const user: UserMessage = {\n role: 'user',\n content:\n `${DECOMPOSE_SYSTEM}\\n\\n${DECOMPOSE_USER.replace('{goal}', truncateGoalText(goal, GOAL_TRUNC))}` +\n judgeResponseLanguageNote(locale),\n timestamp: Date.now(),\n };\n const apiKey = await resolveGoalJudgeApiKey(model);\n const result = await complete(\n model,\n { messages: [user] },\n { apiKey, maxTokens: 2000, temperature: 0, signal: merged },\n );\n const errorReason = getAssistantMessageErrorReason(result);\n if (errorReason) {\n return {\n items: [],\n parseFailed: false,\n errorReason: judgeReasonText('decompose_call_failed', locale),\n };\n }\n\n const text = extractAssistantText(result.content);\n const data = extractJsonObject(text);\n if (!data)\n return {\n items: [],\n parseFailed: true,\n errorReason: judgeReasonText('decompose_reply_not_json', locale),\n };\n const rawItems = data.items ?? data.checklist;\n if (!Array.isArray(rawItems))\n return {\n items: [],\n parseFailed: true,\n errorReason: judgeReasonText('missing_items_array', locale),\n };\n const items: { text: string }[] = [];\n for (const row of rawItems) {\n if (typeof row === 'string') {\n const t = row.trim();\n if (t) items.push({ text: t });\n } else if (row && typeof row === 'object' && !Array.isArray(row)) {\n const t = String((row as { text?: string }).text ?? '').trim();\n if (t) items.push({ text: t });\n }\n }\n if (!items.length)\n return { items: [], parseFailed: true, errorReason: judgeReasonText('empty_checklist', locale) };\n return { items, parseFailed: false };\n } catch {\n return {\n items: [],\n parseFailed: false,\n errorReason: judgeReasonText('decompose_call_failed', locale),\n };\n } finally {\n clearTimeout(timer);\n }\n}\n\nexport type ChecklistJudgeUpdate = {\n index: number;\n status: 'completed' | 'impossible';\n evidence?: string | null;\n};\n\nexport type ChecklistEvaluateParsed = {\n updates: ChecklistJudgeUpdate[];\n newItems: { text: string }[];\n reason: string;\n};\n\nexport type EvaluateChecklistResult = {\n parsed: ChecklistEvaluateParsed;\n parseFailed: boolean;\n};\n\nexport async function evaluateGoalChecklistJudge(opts: {\n goal: string;\n numberedChecklist: string;\n lastResponse: string;\n historyExcerpt: string;\n judgeModelRef: string;\n signal?: AbortSignal;\n judgeTimeoutMs?: number;\n uiLocale?: GoalUiLocale;\n}): Promise<EvaluateChecklistResult> {\n const locale = goalUiLocaleOrFallback(opts.uiLocale);\n let model: ReturnType<typeof resolveModel>;\n try {\n model = resolveModel(opts.judgeModelRef);\n } catch {\n return {\n parsed: {\n updates: [],\n newItems: [],\n reason: judgeReasonText('judge_model_not_configured', locale),\n },\n parseFailed: false,\n };\n }\n\n const userBody =\n `Goal:\\n${truncateGoalText(opts.goal, 2000)}\\n\\n` +\n `Current checklist (1-based indices):\\n${opts.numberedChecklist}\\n\\n` +\n `Agent's most recent response (snippet):\\n${truncateGoalText(opts.lastResponse, RESPONSE_SNIPPET)}\\n\\n` +\n `Recent conversation excerpt (JSON or text, may be truncated):\\n` +\n `${truncateGoalText(opts.historyExcerpt, HISTORY_TRUNC) || '(none)'}`;\n\n const timeoutMs =\n typeof opts.judgeTimeoutMs === 'number' && Number.isFinite(opts.judgeTimeoutMs)\n ? Math.max(5_000, Math.min(120_000, Math.floor(opts.judgeTimeoutMs)))\n : DEFAULT_JUDGE_TIMEOUT_MS;\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n const merged = opts.signal ? AbortSignal.any([opts.signal, controller.signal]) : controller.signal;\n\n try {\n const user: UserMessage = {\n role: 'user',\n content: `${EVALUATE_CHECKLIST_SYSTEM}\\n\\n${userBody}` + judgeResponseLanguageNote(locale),\n timestamp: Date.now(),\n };\n const apiKey = await resolveGoalJudgeApiKey(model);\n const result = await complete(\n model,\n { messages: [user] },\n { apiKey, maxTokens: 1500, temperature: 0, signal: merged },\n );\n const errorReason = getAssistantMessageErrorReason(result);\n if (errorReason) {\n return {\n parsed: {\n updates: [],\n newItems: [],\n reason: judgeReasonText('judge_call_failed', locale),\n },\n parseFailed: false,\n };\n }\n\n const text = extractAssistantText(result.content);\n const data = extractJsonObject(text);\n if (!data) {\n return {\n parsed: {\n updates: [],\n newItems: [],\n reason: judgeReasonText('judge_reply_not_json', locale),\n },\n parseFailed: true,\n };\n }\n const updatesRaw = data.updates;\n const newRaw = data.new_items ?? data.newItems;\n const trimmed = typeof data.reason === 'string' ? data.reason.trim() : '';\n const inner = trimmed || JUDGE_REASON_EN.no_reason_provided;\n const reason = localizeJudgeReasonText(inner, locale);\n const updates: ChecklistJudgeUpdate[] = [];\n if (Array.isArray(updatesRaw)) {\n for (const u of updatesRaw) {\n if (!u || typeof u !== 'object' || Array.isArray(u)) continue;\n const rec = u as Record<string, unknown>;\n const idx1 = Number(rec.index);\n if (!Number.isFinite(idx1)) continue;\n const idx0 = Math.floor(idx1) - 1;\n const st = String(rec.status ?? '').trim().toLowerCase();\n if (st !== 'completed' && st !== 'impossible') continue;\n const evidence = typeof rec.evidence === 'string' ? rec.evidence : null;\n updates.push({ index: idx0, status: st, evidence });\n }\n }\n const newItems: { text: string }[] = [];\n if (Array.isArray(newRaw)) {\n for (const row of newRaw) {\n if (typeof row === 'string') {\n const t = row.trim();\n if (t) newItems.push({ text: t });\n } else if (row && typeof row === 'object' && !Array.isArray(row)) {\n const t = String((row as { text?: string }).text ?? '').trim();\n if (t) newItems.push({ text: t });\n }\n }\n }\n return {\n parsed: {\n updates,\n newItems,\n reason: reason || judgeReasonText('no_reason_provided', locale),\n },\n parseFailed: false,\n };\n } catch {\n return {\n parsed: {\n updates: [],\n newItems: [],\n reason: judgeReasonText('judge_call_failed', locale),\n },\n parseFailed: false,\n };\n } finally {\n clearTimeout(timer);\n }\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
- import { CHECKLIST_ITEM_PENDING, allChecklistTerminal, checklistCounts } from "./checklist-types.js";
2
- import { applyJudgeChecklistUpdates, renderChecklistNumbered } from "./state.js";
1
+ import { allChecklistTerminal, checklistCounts } from "./checklist-types.js";
2
+ import { applyJudgeChecklistUpdates, mergeDecomposedChecklistItems, renderChecklistNumbered } from "./state.js";
3
3
  import { CONTINUATION_PROMPT_TEMPLATE_EN, CONTINUATION_PROMPT_WITH_CHECKLIST_TEMPLATE_EN, buildContinuationPromptFromState, checklistProgressSuffix, goalEvaluateUserCopy, resolveGoalUiLocale } from "./goal-locale.js";
4
4
  import { judgeGoalHermesStyle } from "./judge.js";
5
5
  import { decomposeGoalChecklist, evaluateGoalChecklistJudge } from "./checklist-judge.js";
@@ -78,15 +78,10 @@ async function evaluateAfterTurnHermesLike(state, lastResponse, judgeModelRef, s
78
78
  });
79
79
  next.decomposed = true;
80
80
  if (!dec.parseFailed && dec.items.length > 0) {
81
- const now = Date.now();
82
- next.checklist = dec.items.map((it) => ({
83
- text: it.text,
84
- status: CHECKLIST_ITEM_PENDING,
85
- addedBy: "judge",
86
- addedAt: now
87
- }));
81
+ next.checklist = mergeDecomposedChecklistItems(next.checklist ?? [], dec.items);
82
+ const mergedCount = next.checklist.length;
88
83
  next.lastVerdict = "decompose";
89
- next.lastReason = copy.decomposedReason(dec.items.length);
84
+ next.lastReason = copy.decomposedReason(mergedCount);
90
85
  resetParse();
91
86
  const budgetEarly = pauseIfBudget(next.lastReason);
92
87
  if (budgetEarly) return budgetEarly;
@@ -96,7 +91,7 @@ async function evaluateAfterTurnHermesLike(state, lastResponse, judgeModelRef, s
96
91
  continuationPrompt: buildContinuationPromptFromState(next, locale),
97
92
  verdict: "decompose",
98
93
  reason: next.lastReason ?? "",
99
- message: copy.checklistReady(dec.items.length)
94
+ message: copy.checklistReady(mergedCount)
100
95
  };
101
96
  }
102
97
  next.lastReason = dec.errorReason ? copy.decomposeFallbackReason(dec.errorReason) : copy.noChecklistFallback();
@@ -1 +1 @@
1
- {"version":3,"file":"evaluate-turn.js","names":[],"sources":["../../../../src/agent/goals/evaluate-turn.ts"],"sourcesContent":["import type { GoalsConfig } from '../../config/schema.js';\n\nimport { decomposeGoalChecklist, evaluateGoalChecklistJudge } from './checklist-judge.js';\nimport { allChecklistTerminal, checklistCounts, CHECKLIST_ITEM_PENDING } from './checklist-types.js';\nimport {\n buildContinuationPromptFromState,\n checklistProgressSuffix,\n CONTINUATION_PROMPT_TEMPLATE_EN,\n CONTINUATION_PROMPT_WITH_CHECKLIST_TEMPLATE_EN,\n goalEvaluateUserCopy,\n resolveGoalUiLocale,\n type GoalUiLocale,\n} from './goal-locale.js';\nimport { judgeGoalHermesStyle } from './judge.js';\nimport {\n applyJudgeChecklistUpdates,\n renderChecklistNumbered,\n type PersistentGoalState,\n} from './state.js';\n\n/** Hermes `CONTINUATION_PROMPT_TEMPLATE` (English; parity / tests). */\nexport const CONTINUATION_PROMPT_TEMPLATE = CONTINUATION_PROMPT_TEMPLATE_EN;\n\n/** Hermes `CONTINUATION_PROMPT_WITH_CHECKLIST_TEMPLATE` (English; parity / tests). */\nexport const CONTINUATION_PROMPT_WITH_CHECKLIST_TEMPLATE = CONTINUATION_PROMPT_WITH_CHECKLIST_TEMPLATE_EN;\n\nexport type GoalsEvaluateConfigSlice = Pick<\n GoalsConfig,\n 'checklistMode' | 'maxConsecutiveParseFailures' | 'judgeTimeoutMs' | 'checklistHistoryChars'\n>;\n\nexport type GoalPostTurnDecision = {\n newState: PersistentGoalState | null;\n shouldContinue: boolean;\n continuationPrompt: string | null;\n verdict: 'done' | 'continue' | 'skipped' | 'inactive' | 'decompose';\n reason: string;\n message: string;\n};\n\nfunction cloneState(s: PersistentGoalState): PersistentGoalState {\n return {\n ...s,\n checklist: s.checklist?.map((it) => ({ ...it })),\n };\n}\n\n/**\n * Hermes `GoalManager.evaluate_after_turn` equivalent (minus SessionDB I/O).\n */\nexport async function evaluateAfterTurnHermesLike(\n state: PersistentGoalState,\n lastResponse: string,\n judgeModelRef: string,\n signal?: AbortSignal,\n opts?: { goalsSlice?: Partial<GoalsEvaluateConfigSlice>; historyExcerpt?: string; uiLocale?: GoalUiLocale },\n): Promise<GoalPostTurnDecision> {\n const locale = opts?.uiLocale ?? resolveGoalUiLocale(state);\n const copy = goalEvaluateUserCopy(locale);\n\n if (state.status !== 'active') {\n return {\n newState: state,\n shouldContinue: false,\n continuationPrompt: null,\n verdict: 'inactive',\n reason: 'no active goal',\n message: '',\n };\n }\n\n const slice = opts?.goalsSlice;\n const checklistMode = slice?.checklistMode !== false;\n const maxParseFailures = slice?.maxConsecutiveParseFailures ?? 3;\n const judgeTimeoutMs = slice?.judgeTimeoutMs;\n\n const next = cloneState(state);\n next.turnsUsed += 1;\n next.lastTurnAt = Date.now();\n\n const bumpParse = () => {\n next.consecutiveParseFailures = (next.consecutiveParseFailures ?? 0) + 1;\n };\n const resetParse = () => {\n next.consecutiveParseFailures = 0;\n };\n\n const pauseIfParseStorm = (reason: string): GoalPostTurnDecision | null => {\n const n = next.consecutiveParseFailures ?? 0;\n if (n < maxParseFailures) return null;\n next.status = 'paused';\n next.pausedReason = `judge parse failures (${n} consecutive)`;\n return {\n newState: next,\n shouldContinue: false,\n continuationPrompt: null,\n verdict: 'continue',\n reason,\n message: copy.pauseParse(n),\n };\n };\n\n const pauseIfBudget = (reason: string): GoalPostTurnDecision | null => {\n if (next.turnsUsed < next.maxTurns) return null;\n next.status = 'paused';\n next.pausedReason = `turn budget exhausted (${next.turnsUsed}/${next.maxTurns})`;\n return {\n newState: next,\n shouldContinue: false,\n continuationPrompt: null,\n verdict: 'continue',\n reason,\n message: copy.pauseBudget(next.turnsUsed, next.maxTurns),\n };\n };\n\n // ── Phase A: decompose (once per goal when checklist mode is on) ──\n if (checklistMode && !next.decomposed) {\n const dec = await decomposeGoalChecklist({\n goal: next.goal,\n judgeModelRef,\n signal,\n judgeTimeoutMs,\n uiLocale: locale,\n });\n next.decomposed = true;\n if (!dec.parseFailed && dec.items.length > 0) {\n const now = Date.now();\n next.checklist = dec.items.map((it) => ({\n text: it.text,\n status: CHECKLIST_ITEM_PENDING,\n addedBy: 'judge' as const,\n addedAt: now,\n }));\n next.lastVerdict = 'decompose';\n next.lastReason = copy.decomposedReason(dec.items.length);\n resetParse();\n const budgetEarly = pauseIfBudget(next.lastReason);\n if (budgetEarly) return budgetEarly;\n return {\n newState: next,\n shouldContinue: true,\n continuationPrompt: buildContinuationPromptFromState(next, locale),\n verdict: 'decompose',\n reason: next.lastReason ?? '',\n message: copy.checklistReady(dec.items.length),\n };\n }\n next.lastReason = dec.errorReason\n ? copy.decomposeFallbackReason(dec.errorReason)\n : copy.noChecklistFallback();\n }\n\n // ── Phase B: checklist judge ──\n const checklist = next.checklist ?? [];\n if (checklist.length > 0) {\n const evalResult = await evaluateGoalChecklistJudge({\n goal: next.goal,\n numberedChecklist: renderChecklistNumbered(checklist),\n lastResponse,\n historyExcerpt: opts?.historyExcerpt ?? '',\n judgeModelRef,\n signal,\n judgeTimeoutMs,\n uiLocale: locale,\n });\n\n if (evalResult.parseFailed) {\n bumpParse();\n } else {\n resetParse();\n next.checklist = applyJudgeChecklistUpdates(checklist, {\n updates: evalResult.parsed.updates,\n newItems: evalResult.parsed.newItems,\n });\n }\n\n next.lastVerdict = 'continue';\n next.lastReason = evalResult.parsed.reason;\n\n if (!evalResult.parseFailed && allChecklistTerminal(next.checklist ?? [])) {\n next.status = 'done';\n next.lastVerdict = 'done';\n return {\n newState: next,\n shouldContinue: false,\n continuationPrompt: null,\n verdict: 'done',\n reason: evalResult.parsed.reason,\n message: copy.goalAchieved(evalResult.parsed.reason),\n };\n }\n\n const parseStorm = pauseIfParseStorm(evalResult.parsed.reason);\n if (parseStorm) return parseStorm;\n\n const budget = pauseIfBudget(evalResult.parsed.reason);\n if (budget) return budget;\n\n const { total, completed, impossible } = checklistCounts(next.checklist ?? []);\n const done = completed + impossible;\n const progressSuffix = checklistProgressSuffix(done, total, locale);\n\n return {\n newState: next,\n shouldContinue: true,\n continuationPrompt: buildContinuationPromptFromState(next, locale),\n verdict: 'continue',\n reason: evalResult.parsed.reason,\n message: copy.continuingWithProgress(\n next.turnsUsed,\n next.maxTurns,\n progressSuffix,\n evalResult.parsed.reason,\n ),\n };\n }\n\n // ── Freeform judge (no checklist) ──\n const j = await judgeGoalHermesStyle(state.goal, lastResponse, judgeModelRef, signal, {\n judgeTimeoutMs,\n uiLocale: locale,\n });\n next.lastVerdict = j.verdict === 'skipped' ? 'skipped' : j.verdict;\n next.lastReason = j.reason;\n\n if (j.parseFailed) bumpParse();\n else resetParse();\n\n if (j.verdict === 'done') {\n next.status = 'done';\n return {\n newState: next,\n shouldContinue: false,\n continuationPrompt: null,\n verdict: 'done',\n reason: j.reason,\n message: copy.goalAchieved(j.reason),\n };\n }\n\n const parseStorm = pauseIfParseStorm(j.reason);\n if (parseStorm) return parseStorm;\n\n const budget = pauseIfBudget(j.reason);\n if (budget) return budget;\n\n return {\n newState: next,\n shouldContinue: true,\n continuationPrompt: buildContinuationPromptFromState(next, locale),\n verdict: 'continue',\n reason: j.reason,\n message: copy.continuing(next.turnsUsed, next.maxTurns, j.reason),\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"evaluate-turn.js","names":[],"sources":["../../../../src/agent/goals/evaluate-turn.ts"],"sourcesContent":["import type { GoalsConfig } from '../../config/schema.js';\n\nimport { decomposeGoalChecklist, evaluateGoalChecklistJudge } from './checklist-judge.js';\nimport { allChecklistTerminal, checklistCounts } from './checklist-types.js';\nimport {\n buildContinuationPromptFromState,\n checklistProgressSuffix,\n CONTINUATION_PROMPT_TEMPLATE_EN,\n CONTINUATION_PROMPT_WITH_CHECKLIST_TEMPLATE_EN,\n goalEvaluateUserCopy,\n resolveGoalUiLocale,\n type GoalUiLocale,\n} from './goal-locale.js';\nimport { judgeGoalHermesStyle } from './judge.js';\nimport {\n applyJudgeChecklistUpdates,\n mergeDecomposedChecklistItems,\n renderChecklistNumbered,\n type PersistentGoalState,\n} from './state.js';\n\n/** Hermes `CONTINUATION_PROMPT_TEMPLATE` (English; parity / tests). */\nexport const CONTINUATION_PROMPT_TEMPLATE = CONTINUATION_PROMPT_TEMPLATE_EN;\n\n/** Hermes `CONTINUATION_PROMPT_WITH_CHECKLIST_TEMPLATE` (English; parity / tests). */\nexport const CONTINUATION_PROMPT_WITH_CHECKLIST_TEMPLATE = CONTINUATION_PROMPT_WITH_CHECKLIST_TEMPLATE_EN;\n\nexport type GoalsEvaluateConfigSlice = Pick<\n GoalsConfig,\n 'checklistMode' | 'maxConsecutiveParseFailures' | 'judgeTimeoutMs' | 'checklistHistoryChars'\n>;\n\nexport type GoalPostTurnDecision = {\n newState: PersistentGoalState | null;\n shouldContinue: boolean;\n continuationPrompt: string | null;\n verdict: 'done' | 'continue' | 'skipped' | 'inactive' | 'decompose';\n reason: string;\n message: string;\n};\n\nfunction cloneState(s: PersistentGoalState): PersistentGoalState {\n return {\n ...s,\n checklist: s.checklist?.map((it) => ({ ...it })),\n };\n}\n\n/**\n * Hermes `GoalManager.evaluate_after_turn` equivalent (minus SessionDB I/O).\n */\nexport async function evaluateAfterTurnHermesLike(\n state: PersistentGoalState,\n lastResponse: string,\n judgeModelRef: string,\n signal?: AbortSignal,\n opts?: { goalsSlice?: Partial<GoalsEvaluateConfigSlice>; historyExcerpt?: string; uiLocale?: GoalUiLocale },\n): Promise<GoalPostTurnDecision> {\n const locale = opts?.uiLocale ?? resolveGoalUiLocale(state);\n const copy = goalEvaluateUserCopy(locale);\n\n if (state.status !== 'active') {\n return {\n newState: state,\n shouldContinue: false,\n continuationPrompt: null,\n verdict: 'inactive',\n reason: 'no active goal',\n message: '',\n };\n }\n\n const slice = opts?.goalsSlice;\n const checklistMode = slice?.checklistMode !== false;\n const maxParseFailures = slice?.maxConsecutiveParseFailures ?? 3;\n const judgeTimeoutMs = slice?.judgeTimeoutMs;\n\n const next = cloneState(state);\n next.turnsUsed += 1;\n next.lastTurnAt = Date.now();\n\n const bumpParse = () => {\n next.consecutiveParseFailures = (next.consecutiveParseFailures ?? 0) + 1;\n };\n const resetParse = () => {\n next.consecutiveParseFailures = 0;\n };\n\n const pauseIfParseStorm = (reason: string): GoalPostTurnDecision | null => {\n const n = next.consecutiveParseFailures ?? 0;\n if (n < maxParseFailures) return null;\n next.status = 'paused';\n next.pausedReason = `judge parse failures (${n} consecutive)`;\n return {\n newState: next,\n shouldContinue: false,\n continuationPrompt: null,\n verdict: 'continue',\n reason,\n message: copy.pauseParse(n),\n };\n };\n\n const pauseIfBudget = (reason: string): GoalPostTurnDecision | null => {\n if (next.turnsUsed < next.maxTurns) return null;\n next.status = 'paused';\n next.pausedReason = `turn budget exhausted (${next.turnsUsed}/${next.maxTurns})`;\n return {\n newState: next,\n shouldContinue: false,\n continuationPrompt: null,\n verdict: 'continue',\n reason,\n message: copy.pauseBudget(next.turnsUsed, next.maxTurns),\n };\n };\n\n // ── Phase A: decompose (once per goal when checklist mode is on) ──\n if (checklistMode && !next.decomposed) {\n const dec = await decomposeGoalChecklist({\n goal: next.goal,\n judgeModelRef,\n signal,\n judgeTimeoutMs,\n uiLocale: locale,\n });\n next.decomposed = true;\n if (!dec.parseFailed && dec.items.length > 0) {\n const prior = next.checklist ?? [];\n next.checklist = mergeDecomposedChecklistItems(prior, dec.items);\n const mergedCount = next.checklist.length;\n next.lastVerdict = 'decompose';\n next.lastReason = copy.decomposedReason(mergedCount);\n resetParse();\n const budgetEarly = pauseIfBudget(next.lastReason);\n if (budgetEarly) return budgetEarly;\n return {\n newState: next,\n shouldContinue: true,\n continuationPrompt: buildContinuationPromptFromState(next, locale),\n verdict: 'decompose',\n reason: next.lastReason ?? '',\n message: copy.checklistReady(mergedCount),\n };\n }\n next.lastReason = dec.errorReason\n ? copy.decomposeFallbackReason(dec.errorReason)\n : copy.noChecklistFallback();\n }\n\n // ── Phase B: checklist judge ──\n const checklist = next.checklist ?? [];\n if (checklist.length > 0) {\n const evalResult = await evaluateGoalChecklistJudge({\n goal: next.goal,\n numberedChecklist: renderChecklistNumbered(checklist),\n lastResponse,\n historyExcerpt: opts?.historyExcerpt ?? '',\n judgeModelRef,\n signal,\n judgeTimeoutMs,\n uiLocale: locale,\n });\n\n if (evalResult.parseFailed) {\n bumpParse();\n } else {\n resetParse();\n next.checklist = applyJudgeChecklistUpdates(checklist, {\n updates: evalResult.parsed.updates,\n newItems: evalResult.parsed.newItems,\n });\n }\n\n next.lastVerdict = 'continue';\n next.lastReason = evalResult.parsed.reason;\n\n if (!evalResult.parseFailed && allChecklistTerminal(next.checklist ?? [])) {\n next.status = 'done';\n next.lastVerdict = 'done';\n return {\n newState: next,\n shouldContinue: false,\n continuationPrompt: null,\n verdict: 'done',\n reason: evalResult.parsed.reason,\n message: copy.goalAchieved(evalResult.parsed.reason),\n };\n }\n\n const parseStorm = pauseIfParseStorm(evalResult.parsed.reason);\n if (parseStorm) return parseStorm;\n\n const budget = pauseIfBudget(evalResult.parsed.reason);\n if (budget) return budget;\n\n const { total, completed, impossible } = checklistCounts(next.checklist ?? []);\n const done = completed + impossible;\n const progressSuffix = checklistProgressSuffix(done, total, locale);\n\n return {\n newState: next,\n shouldContinue: true,\n continuationPrompt: buildContinuationPromptFromState(next, locale),\n verdict: 'continue',\n reason: evalResult.parsed.reason,\n message: copy.continuingWithProgress(\n next.turnsUsed,\n next.maxTurns,\n progressSuffix,\n evalResult.parsed.reason,\n ),\n };\n }\n\n // ── Freeform judge (no checklist) ──\n const j = await judgeGoalHermesStyle(state.goal, lastResponse, judgeModelRef, signal, {\n judgeTimeoutMs,\n uiLocale: locale,\n });\n next.lastVerdict = j.verdict === 'skipped' ? 'skipped' : j.verdict;\n next.lastReason = j.reason;\n\n if (j.parseFailed) bumpParse();\n else resetParse();\n\n if (j.verdict === 'done') {\n next.status = 'done';\n return {\n newState: next,\n shouldContinue: false,\n continuationPrompt: null,\n verdict: 'done',\n reason: j.reason,\n message: copy.goalAchieved(j.reason),\n };\n }\n\n const parseStorm = pauseIfParseStorm(j.reason);\n if (parseStorm) return parseStorm;\n\n const budget = pauseIfBudget(j.reason);\n if (budget) return budget;\n\n return {\n newState: next,\n shouldContinue: true,\n continuationPrompt: buildContinuationPromptFromState(next, locale),\n verdict: 'continue',\n reason: j.reason,\n message: copy.continuing(next.turnsUsed, next.maxTurns, j.reason),\n };\n}\n"],"mappings":"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"}
@@ -1,9 +1,25 @@
1
+ import { resolveModel } from '../../providers/index.js';
1
2
  import type { GoalUiLocale } from './goal-locale.js';
3
+ /**
4
+ * Extract visible text from a pi-ai `AssistantMessage.content` array.
5
+ * Handles `TextContent` (`type: 'text'`) and falls back to `ThinkingContent`
6
+ * (`type: 'thinking'`) when the text blocks are empty — reasoning models
7
+ * (DeepSeek-R1, Qwen-thinking, etc.) may place the entire response inside
8
+ * thinking blocks, leaving the text portion blank.
9
+ */
10
+ export declare function extractAssistantText(content: unknown): string;
11
+ /**
12
+ * Strip markdown code fences (opening AND closing) from raw model output.
13
+ * Handles `` ```json ``, `` ``` ``, and trailing `` ``` `` with optional whitespace.
14
+ */
15
+ export declare function stripCodeFences(raw: string): string;
2
16
  /** Mirrors `hermes_cli/goals.py` — strict judge, JSON-only reply. */
3
17
  export declare const JUDGE_SYSTEM_PROMPT: string;
4
18
  export declare const JUDGE_USER_PROMPT_TEMPLATE: string;
5
19
  export declare const DEFAULT_JUDGE_TIMEOUT_MS = 60000;
6
20
  export declare function truncateGoalText(text: string, limit: number): string;
21
+ export declare function resolveGoalJudgeApiKey(model: ReturnType<typeof resolveModel>): Promise<string | undefined>;
22
+ export declare function getAssistantMessageErrorReason(message: unknown): string | null;
7
23
  /** Parse judge JSON — fail-open to **continue** (Hermes semantics). */
8
24
  export declare function parseJudgeResponseFailOpen(raw: string): {
9
25
  done: boolean;