dsh-model-extension 1.2.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -3,7 +3,7 @@ window.__ModuleLoader__.load({
3
3
  factory: (require) => {
4
4
  var module = { exports: {} };
5
5
  var exports = module.exports;
6
- var __extCss = ".J84-9a_section{max-width:720px;color:var(--dsw-alias-label-primary);flex-direction:column;gap:12px;display:flex}.J84-9a_titleRow{align-items:center;gap:10px;display:flex}.J84-9a_title{color:var(--dsw-alias-label-primary);margin:0;font-size:16px;font-weight:500;line-height:24px}.J84-9a_intro{color:var(--dsw-alias-label-tertiary);margin:0;font-size:14px;line-height:22px}.J84-9a_savedNotice{color:var(--dsw-alias-state-success-primary);margin:0;font-size:12px;line-height:18px}.J84-9a_error{color:var(--dsw-alias-state-error-primary);margin:0;font-size:12px;line-height:18px}.J84-9a_rows{flex-direction:column;gap:8px;margin:12px 0 0;padding:0;list-style:none;display:flex}.J84-9a_rowCard{border:.5px solid var(--dsw-alias-border-l4);border-radius:16px;flex-direction:column;gap:12px;padding:12px 14px;display:flex}.J84-9a_rowHead{align-items:center;gap:10px;display:flex}.J84-9a_rowIdentity{align-items:center;gap:6px;min-width:0;display:inline-flex}.J84-9a_rowName{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:500;line-height:22px}.J84-9a_rowTag{border:.5px solid var(--dsw-alias-border-l3);color:var(--dsw-alias-label-secondary);border-radius:4px;flex:none;padding:1px 6px;font-size:11px;line-height:16px}.J84-9a_credentialDot{box-sizing:border-box;border-radius:50%;flex:none;width:8px;height:8px;display:inline-block}.J84-9a_credentialDotConfigured{background:var(--dsw-alias-state-success-primary)}.J84-9a_credentialDotMissing{background:var(--dsw-alias-state-error-primary)}.J84-9a_rowActions{align-items:center;gap:4px;margin-left:auto;display:inline-flex}.J84-9a_primaryButton,.J84-9a_secondaryButton,.J84-9a_addButton{box-sizing:border-box;height:36px;font:inherit;cursor:pointer;border:none;border-radius:18px;justify-content:center;align-items:center;gap:4px;padding:0 14px;font-size:14px;line-height:22px;display:inline-flex}.J84-9a_primaryButton{background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground)}.J84-9a_primaryButton:hover:not(:disabled){background:var(--dsw-alias-button-primary-hover)}.J84-9a_secondaryButton,.J84-9a_addButton{border:.5px solid var(--dsw-alias-border-l3);color:var(--dsw-alias-label-primary);background:0 0}.J84-9a_secondaryButton:hover:not(:disabled),.J84-9a_addButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.J84-9a_secondaryButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-solid)}.J84-9a_dangerButton{box-sizing:border-box;height:36px;color:var(--dsw-alias-state-error-primary);font:inherit;cursor:pointer;background:0 0;border:none;border-radius:18px;justify-content:center;align-items:center;padding:0 14px;font-size:14px;line-height:22px;display:inline-flex}.J84-9a_dangerButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-danger)}.J84-9a_rowActions .J84-9a_secondaryButton,.J84-9a_rowActions .J84-9a_dangerButton{border-radius:14px;height:28px;padding:0 10px;font-size:12px;line-height:18px}.J84-9a_primaryButton:disabled,.J84-9a_secondaryButton:disabled,.J84-9a_dangerButton:disabled,.J84-9a_addButton:disabled,.J84-9a_linkButton:disabled,.J84-9a_addModelButton:disabled{opacity:.4;cursor:default}.J84-9a_primaryButton:focus-visible,.J84-9a_secondaryButton:focus-visible,.J84-9a_dangerButton:focus-visible,.J84-9a_addButton:focus-visible,.J84-9a_linkButton:focus-visible,.J84-9a_addModelButton:focus-visible,.J84-9a_iconButton:focus-visible,.J84-9a_customizedSummary:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-border-l3);outline:none}.J84-9a_editor{background:var(--dsw-alias-bg-module-platform);border-radius:12px;flex-direction:column;gap:14px;padding:14px 16px;display:flex}.J84-9a_field{flex-direction:column;gap:6px;display:flex}.J84-9a_fieldLabel{color:var(--dsw-alias-label-secondary);align-items:center;gap:10px;font-size:12px;font-weight:500;line-height:18px;display:inline-flex}.J84-9a_linkButton{box-sizing:border-box;height:28px;color:var(--dsw-alias-label-tertiary);font:inherit;cursor:pointer;background:0 0;border:none;border-radius:14px;align-items:center;padding:0 10px;font-size:12px;line-height:18px;display:inline-flex}.J84-9a_linkButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary)}.J84-9a_advancedHint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:18px}.J84-9a_editorActions{justify-content:flex-end;gap:8px;display:flex}.J84-9a_addBlock{flex-direction:column;gap:12px;display:flex}.J84-9a_addActions{flex-wrap:wrap;gap:10px;display:flex}.J84-9a_addButton{border:1px dashed var(--dsw-alias-border-l3);border-radius:16px;flex:1 1 0;gap:6px;min-width:180px;height:44px}.J84-9a_addCard{background:var(--dsw-alias-bg-module-platform);border-radius:12px;flex-direction:column;gap:14px;padding:14px 16px;list-style:none;display:flex}.J84-9a_addCard .J84-9a_editor,.J84-9a_setupCard .J84-9a_editor{background:0 0;padding:0}.J84-9a_tabs{flex-direction:column;gap:12px;display:flex}.J84-9a_tabBar{border-bottom:.5px solid var(--dsw-alias-border-l2);gap:4px;display:flex}.J84-9a_tabButton{appearance:none;font:inherit;color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:none;padding:6px 12px 8px;font-size:13px;line-height:20px;position:relative}.J84-9a_tabButton:hover{color:var(--dsw-alias-label-primary)}.J84-9a_tabButton:disabled{opacity:.4;cursor:default}.J84-9a_tabButtonActive{color:var(--dsw-alias-label-primary);font-weight:500}.J84-9a_tabButtonActive:after{content:\"\";background:var(--dsw-alias-brand-primary);border-radius:2px;height:2px;position:absolute;bottom:-1px;left:10px;right:10px}.J84-9a_tabPanel{flex-direction:column;gap:12px;display:flex}.J84-9a_modelCatalog{flex-direction:column;gap:10px;display:flex}.J84-9a_modelCatalogHeading{flex-direction:column;gap:2px;display:flex}.J84-9a_modelCatalogTitle{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:500;line-height:18px}.J84-9a_modelCatalogMeta,.J84-9a_modelEmpty{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:18px}.J84-9a_modelList{flex-direction:column;gap:8px;display:flex}.J84-9a_modelListHead{justify-content:space-between;align-items:flex-start;gap:12px;display:flex}.J84-9a_modelEntry{border:.5px solid var(--dsw-alias-border-l4);border-radius:10px;padding:6px}.J84-9a_modelRow{grid-template-columns:minmax(0,1.4fr) minmax(0,1fr) auto auto;align-items:center;gap:6px;display:grid}.J84-9a_iconButton{box-sizing:border-box;width:28px;height:28px;color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:none;border-radius:6px;justify-content:center;align-items:center;display:inline-flex}.J84-9a_iconButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.J84-9a_iconButton:disabled{cursor:default;opacity:.4}.J84-9a_iconButtonDanger:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-danger);color:var(--dsw-alias-state-error-primary)}.J84-9a_modelAdvanced{grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:8px;padding:8px 4px 2px;display:grid}.J84-9a_modelField{flex-direction:column;gap:4px;display:flex}.J84-9a_modelFieldLabel{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}.J84-9a_modelEmpty{border:1px dashed var(--dsw-alias-border-l3);text-align:center;border-radius:8px;padding:12px}.J84-9a_addModelButton{box-sizing:border-box;border:.5px solid var(--dsw-alias-border-l3);height:28px;color:var(--dsw-alias-label-primary);font:inherit;cursor:pointer;background:0 0;border-radius:14px;align-self:flex-start;align-items:center;gap:4px;padding:0 10px;font-size:12px;line-height:18px;display:inline-flex}.J84-9a_addModelButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.J84-9a_input{box-sizing:border-box;border:.5px solid var(--dsw-alias-border-l4);width:100%;height:32px;font:inherit;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 10px;font-size:14px;line-height:22px}select.J84-9a_input{cursor:pointer;max-width:240px}.J84-9a_input:focus{border-color:var(--dsw-alias-brand-primary);outline:none}.J84-9a_input::placeholder{color:var(--dsw-alias-label-dimmed)}.J84-9a_input:disabled{opacity:.6;cursor:default}.J84-9a_selectInput{appearance:none;background-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12' fill='none'%3E%3Cpath d='M3 4.5L6 7.5L9 4.5' stroke='%2381858C' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E\");background-position:right 12px center;background-repeat:no-repeat;background-size:12px 12px;padding-right:32px}.J84-9a_deleteDialog{width:min(480px,100%)}.J84-9a_deleteConfirm:not(:disabled){border-color:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary)}.J84-9a_deleteConfirm:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-danger)}.J84-9a_fetchDialog{max-width:520px}.J84-9a_candidateToolbar{align-items:center;gap:8px;margin-bottom:6px;display:flex}.J84-9a_candidateSearch{flex:240px;min-width:0}.J84-9a_candidateList{flex-direction:column;gap:2px;max-height:320px;margin:0;padding:0;list-style:none;display:flex;overflow-y:auto}.J84-9a_candidate{border-radius:6px}.J84-9a_candidateLabel{cursor:pointer;align-items:center;gap:8px;padding:6px 8px;display:flex}.J84-9a_candidateId{overflow-wrap:anywhere;flex:auto;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:13px}.J84-9a_candidateEmpty{color:var(--dsw-alias-label-secondary);text-align:center;margin:24px 0;font-size:13px;line-height:20px}[hidden]{display:none!important}.J84-9a_extBlock{border:.5px solid var(--dsw-alias-border-l3);background:var(--dsw-alias-bg-layer-1);border-radius:10px;flex-direction:column;gap:10px;margin:8px 4px 2px;padding:10px;display:flex}.J84-9a_extGroup{border-top:.5px solid var(--dsw-alias-border-l2);padding-top:12px}.J84-9a_extGroupFirst{border-top:none;padding-top:0}.J84-9a_extCheck{cursor:pointer;align-items:flex-start;gap:8px;display:flex}.J84-9a_extCheckLabel{color:var(--dsw-alias-label-primary);font-size:14px;line-height:22px}.J84-9a_extHint{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}.J84-9a_extGated{flex-direction:column;gap:12px;padding-left:2px;display:flex}.J84-9a_extField{flex-direction:column;gap:6px;display:flex}.J84-9a_extLabel{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}.J84-9a_extTable{border:.5px solid var(--dsw-alias-border-l4);border-radius:8px;overflow:hidden}.J84-9a_extTHead{color:var(--dsw-alias-label-tertiary);background:var(--dsw-alias-bg-module-platform);grid-template-columns:64px minmax(0,1fr) minmax(0,1.2fr);font-size:12px;line-height:18px;display:grid}.J84-9a_extTHead>div{padding:6px 10px}.J84-9a_extTHead>div+div{border-left:.5px solid var(--dsw-alias-border-l2)}.J84-9a_extTRow{border-top:.5px solid var(--dsw-alias-border-l2);grid-template-columns:64px minmax(0,1fr) minmax(0,1.2fr);align-items:center;display:grid}.J84-9a_extTRow>div{padding:6px 10px;font-size:14px}.J84-9a_extTRow>div+div{border-left:.5px solid var(--dsw-alias-border-l2)}.J84-9a_extTRow input[type=text]{box-sizing:border-box;border:.5px solid var(--dsw-alias-border-l4);width:100%;height:32px;font:inherit;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 10px;font-size:14px}.J84-9a_extTRow input[type=text]:disabled{opacity:.6;cursor:default}.J84-9a_extCheckbox{accent-color:var(--dsw-alias-brand-primary);flex:none;width:15px;height:15px}.J84-9a_switchRow{cursor:pointer;align-items:center;gap:8px;font-size:14px;line-height:22px;display:flex}.J84-9a_extTag{background:var(--dsw-static-deepseek-100);color:var(--dsw-static-deepseek-600);border:.5px solid #4176e659;border-radius:4px;flex:none;padding:1px 6px;font-size:11px;font-weight:500;line-height:16px}.J84-9a_modalMask{background:var(--dsw-alias-bg-mask-1);z-index:100;justify-content:center;align-items:center;padding:24px;display:flex;position:fixed;inset:0}.J84-9a_modal{background:var(--dsw-specific-menu);border-radius:16px;width:100%;padding:20px;box-shadow:0 12px 40px #0000002e}.J84-9a_modalTitle{color:var(--dsw-alias-label-primary);margin-bottom:8px;font-size:16px;font-weight:500;line-height:24px}.J84-9a_modalDesc{color:var(--dsw-alias-label-secondary);margin-bottom:16px;font-size:14px;line-height:22px}.J84-9a_modalFoot{justify-content:flex-end;gap:8px;display:flex}.J84-9a_quickWrap{flex-direction:column;gap:6px;display:flex;position:relative}.J84-9a_quickList{z-index:20;border:.5px solid var(--dsw-alias-border-l3);background:var(--dsw-specific-menu);border-radius:8px;max-height:264px;margin-top:2px;padding:4px;position:absolute;top:100%;left:0;right:0;overflow-y:auto;box-shadow:0 8px 24px #0000001f}.J84-9a_quickItem{width:100%;color:var(--dsw-alias-label-primary);text-align:left;cursor:pointer;overflow-wrap:anywhere;background:0 0;border:none;border-radius:6px;padding:6px 8px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:13px;display:block}.J84-9a_quickItem:hover{background:var(--dsw-alias-interactive-bg-hover)}.J84-9a_quickEmpty{color:var(--dsw-alias-label-tertiary);padding:6px 8px;font-size:12px;line-height:18px}@media (prefers-reduced-motion:reduce){.J84-9a_customizedSummary:before{transition:none}}\n";
6
+ var __extCss = ".J84-9a_section{max-width:720px;color:var(--dsw-alias-label-primary);flex-direction:column;gap:12px;display:flex}.J84-9a_titleRow{align-items:center;gap:10px;display:flex}.J84-9a_title{color:var(--dsw-alias-label-primary);margin:0;font-size:16px;font-weight:500;line-height:24px}.J84-9a_intro{color:var(--dsw-alias-label-tertiary);margin:0;font-size:14px;line-height:22px}.J84-9a_savedNotice{color:var(--dsw-alias-state-success-primary);margin:0;font-size:12px;line-height:18px}.J84-9a_error{color:var(--dsw-alias-state-error-primary);margin:0;font-size:12px;line-height:18px}.J84-9a_rows{flex-direction:column;gap:8px;margin:12px 0 0;padding:0;list-style:none;display:flex}.J84-9a_rowCard{border:.5px solid var(--dsw-alias-border-l4);border-radius:16px;flex-direction:column;gap:12px;padding:12px 14px;display:flex}.J84-9a_rowHead{align-items:center;gap:10px;display:flex}.J84-9a_rowIdentity{align-items:center;gap:6px;min-width:0;display:inline-flex}.J84-9a_rowName{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:500;line-height:22px}.J84-9a_rowTag{border:.5px solid var(--dsw-alias-border-l3);color:var(--dsw-alias-label-secondary);border-radius:4px;flex:none;padding:1px 6px;font-size:11px;line-height:16px}.J84-9a_credentialDot{box-sizing:border-box;border-radius:50%;flex:none;width:8px;height:8px;display:inline-block}.J84-9a_credentialDotConfigured{background:var(--dsw-alias-state-success-primary)}.J84-9a_credentialDotMissing{background:var(--dsw-alias-state-error-primary)}.J84-9a_rowActions{align-items:center;gap:4px;margin-left:auto;display:inline-flex}.J84-9a_primaryButton,.J84-9a_secondaryButton,.J84-9a_addButton{box-sizing:border-box;height:36px;font:inherit;cursor:pointer;border:none;border-radius:18px;justify-content:center;align-items:center;gap:4px;padding:0 14px;font-size:14px;line-height:22px;display:inline-flex}.J84-9a_primaryButton{background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground)}.J84-9a_primaryButton:hover:not(:disabled){background:var(--dsw-alias-button-primary-hover)}.J84-9a_secondaryButton,.J84-9a_addButton{border:.5px solid var(--dsw-alias-border-l3);color:var(--dsw-alias-label-primary);background:0 0}.J84-9a_secondaryButton:hover:not(:disabled),.J84-9a_addButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.J84-9a_secondaryButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-solid)}.J84-9a_dangerButton{box-sizing:border-box;height:36px;color:var(--dsw-alias-state-error-primary);font:inherit;cursor:pointer;background:0 0;border:none;border-radius:18px;justify-content:center;align-items:center;padding:0 14px;font-size:14px;line-height:22px;display:inline-flex}.J84-9a_dangerButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-danger)}.J84-9a_rowActions .J84-9a_secondaryButton,.J84-9a_rowActions .J84-9a_dangerButton{border-radius:14px;height:28px;padding:0 10px;font-size:12px;line-height:18px}.J84-9a_primaryButton:disabled,.J84-9a_secondaryButton:disabled,.J84-9a_dangerButton:disabled,.J84-9a_addButton:disabled,.J84-9a_linkButton:disabled,.J84-9a_addModelButton:disabled{opacity:.4;cursor:default}.J84-9a_primaryButton:focus-visible,.J84-9a_secondaryButton:focus-visible,.J84-9a_dangerButton:focus-visible,.J84-9a_addButton:focus-visible,.J84-9a_linkButton:focus-visible,.J84-9a_addModelButton:focus-visible,.J84-9a_iconButton:focus-visible,.J84-9a_customizedSummary:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-border-l3);outline:none}.J84-9a_editor{background:var(--dsw-alias-bg-module-platform);border-radius:12px;flex-direction:column;gap:14px;padding:14px 16px;display:flex}.J84-9a_field{flex-direction:column;gap:6px;display:flex}.J84-9a_fieldLabel{color:var(--dsw-alias-label-secondary);align-items:center;gap:10px;font-size:12px;font-weight:500;line-height:18px;display:inline-flex}.J84-9a_linkButton{box-sizing:border-box;height:28px;color:var(--dsw-alias-label-tertiary);font:inherit;cursor:pointer;background:0 0;border:none;border-radius:14px;align-items:center;padding:0 10px;font-size:12px;line-height:18px;display:inline-flex}.J84-9a_linkButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary)}.J84-9a_advancedHint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:18px}.J84-9a_editorActions{justify-content:flex-end;gap:8px;display:flex}.J84-9a_addBlock{flex-direction:column;gap:12px;display:flex}.J84-9a_addActions{flex-wrap:wrap;gap:10px;display:flex}.J84-9a_addButton{border:1px dashed var(--dsw-alias-border-l3);border-radius:16px;flex:1 1 0;gap:6px;min-width:180px;height:44px}.J84-9a_addCard{background:var(--dsw-alias-bg-module-platform);border-radius:12px;flex-direction:column;gap:14px;padding:14px 16px;list-style:none;display:flex}.J84-9a_addCard .J84-9a_editor,.J84-9a_setupCard .J84-9a_editor{background:0 0;padding:0}.J84-9a_tabs{flex-direction:column;gap:12px;display:flex}.J84-9a_tabBar{border-bottom:.5px solid var(--dsw-alias-border-l2);gap:4px;display:flex}.J84-9a_tabButton{appearance:none;font:inherit;color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:none;padding:6px 12px 8px;font-size:13px;line-height:20px;position:relative}.J84-9a_tabButton:hover{color:var(--dsw-alias-label-primary)}.J84-9a_tabButton:disabled{opacity:.4;cursor:default}.J84-9a_tabButtonActive{color:var(--dsw-alias-label-primary);font-weight:500}.J84-9a_tabButtonActive:after{content:\"\";background:var(--dsw-alias-brand-primary);border-radius:2px;height:2px;position:absolute;bottom:-1px;left:10px;right:10px}.J84-9a_tabPanel{flex-direction:column;gap:12px;display:flex}.J84-9a_modelCatalog{flex-direction:column;gap:10px;display:flex}.J84-9a_modelCatalogHeading{flex-direction:column;gap:2px;display:flex}.J84-9a_modelCatalogTitle{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:500;line-height:18px}.J84-9a_modelCatalogMeta,.J84-9a_modelEmpty{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:18px}.J84-9a_modelList{flex-direction:column;gap:8px;display:flex}.J84-9a_modelListHead{justify-content:space-between;align-items:flex-start;gap:12px;display:flex}.J84-9a_modelEntry{border:.5px solid var(--dsw-alias-border-l4);border-radius:10px;padding:6px}.J84-9a_modelRow{grid-template-columns:minmax(0,1.4fr) minmax(0,1fr) auto auto;align-items:center;gap:6px;display:grid}.J84-9a_iconButton{box-sizing:border-box;width:28px;height:28px;color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:none;border-radius:6px;justify-content:center;align-items:center;display:inline-flex}.J84-9a_iconButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.J84-9a_iconButton:disabled{cursor:default;opacity:.4}.J84-9a_iconButtonDanger:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-danger);color:var(--dsw-alias-state-error-primary)}.J84-9a_modelAdvanced{grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:8px;padding:8px 4px 2px;display:grid}.J84-9a_modelField{flex-direction:column;gap:4px;display:flex}.J84-9a_modelFieldLabel{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}.J84-9a_modelEmpty{border:1px dashed var(--dsw-alias-border-l3);text-align:center;border-radius:8px;padding:12px}.J84-9a_addModelButton{box-sizing:border-box;border:.5px solid var(--dsw-alias-border-l3);height:28px;color:var(--dsw-alias-label-primary);font:inherit;cursor:pointer;background:0 0;border-radius:14px;align-self:flex-start;align-items:center;gap:4px;padding:0 10px;font-size:12px;line-height:18px;display:inline-flex}.J84-9a_addModelButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.J84-9a_input{box-sizing:border-box;border:.5px solid var(--dsw-alias-border-l4);width:100%;height:32px;font:inherit;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 10px;font-size:14px;line-height:22px}select.J84-9a_input{cursor:pointer;max-width:240px}.J84-9a_input:focus{border-color:var(--dsw-alias-brand-primary);outline:none}.J84-9a_input::placeholder{color:var(--dsw-alias-label-dimmed)}.J84-9a_input:disabled{opacity:.6;cursor:default}.J84-9a_selectInput{appearance:none;background-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12' fill='none'%3E%3Cpath d='M3 4.5L6 7.5L9 4.5' stroke='%2381858C' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E\");background-position:right 12px center;background-repeat:no-repeat;background-size:12px 12px;padding-right:32px}.J84-9a_deleteDialog{width:min(480px,100%)}.J84-9a_deleteConfirm:not(:disabled){border-color:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary)}.J84-9a_deleteConfirm:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-danger)}.J84-9a_fetchDialog{max-width:520px}.J84-9a_candidateToolbar{align-items:center;gap:8px;margin-bottom:6px;display:flex}.J84-9a_candidateSearch{flex:240px;min-width:0}.J84-9a_candidateList{flex-direction:column;gap:2px;max-height:320px;margin:0;padding:0;list-style:none;display:flex;overflow-y:auto}.J84-9a_candidate{border-radius:6px}.J84-9a_candidateLabel{cursor:pointer;align-items:center;gap:8px;padding:6px 8px;display:flex}.J84-9a_candidateId{overflow-wrap:anywhere;flex:auto;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:13px}.J84-9a_candidateEmpty{color:var(--dsw-alias-label-secondary);text-align:center;margin:24px 0;font-size:13px;line-height:20px}[hidden]{display:none!important}.J84-9a_extBlock{border:.5px solid var(--dsw-alias-border-l3);background:var(--dsw-alias-bg-layer-1);border-radius:10px;flex-direction:column;gap:10px;margin:8px 4px 2px;padding:10px;display:flex}.J84-9a_extGroup{border-top:.5px solid var(--dsw-alias-border-l2);padding-top:12px}.J84-9a_extGroupFirst{border-top:none;padding-top:0}.J84-9a_extCheck{cursor:pointer;align-items:flex-start;gap:8px;display:flex}.J84-9a_extCheckLabel{color:var(--dsw-alias-label-primary);font-size:14px;line-height:22px}.J84-9a_extHint{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}.J84-9a_extGated{flex-direction:column;gap:12px;padding-left:2px;display:flex}.J84-9a_extField{flex-direction:column;gap:6px;display:flex}.J84-9a_extLabel{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}.J84-9a_extTable{border:.5px solid var(--dsw-alias-border-l4);border-radius:8px;overflow:hidden}.J84-9a_extTHead{color:var(--dsw-alias-label-tertiary);background:var(--dsw-alias-bg-module-platform);grid-template-columns:64px minmax(0,1fr) minmax(0,1.2fr);font-size:12px;line-height:18px;display:grid}.J84-9a_extTHead>div{padding:6px 10px}.J84-9a_extTHead>div+div{border-left:.5px solid var(--dsw-alias-border-l2)}.J84-9a_extTRow{border-top:.5px solid var(--dsw-alias-border-l2);grid-template-columns:64px minmax(0,1fr) minmax(0,1.2fr);align-items:center;display:grid}.J84-9a_extTRow>div{padding:6px 10px;font-size:14px}.J84-9a_extTRow>div+div{border-left:.5px solid var(--dsw-alias-border-l2)}.J84-9a_extTRow input[type=text]{box-sizing:border-box;border:.5px solid var(--dsw-alias-border-l4);width:100%;height:32px;font:inherit;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 10px;font-size:14px}.J84-9a_extTRow input[type=text]:disabled{opacity:.6;cursor:default}.J84-9a_extCheckbox{accent-color:var(--dsw-alias-brand-primary);flex:none;width:15px;height:15px}.J84-9a_switchRow{cursor:pointer;align-items:center;gap:8px;font-size:14px;line-height:22px;display:flex}.J84-9a_extTag{background:var(--dsw-static-deepseek-100);color:var(--dsw-static-deepseek-600);border:.5px solid #4176e659;border-radius:4px;flex:none;padding:1px 6px;font-size:11px;font-weight:500;line-height:16px}.J84-9a_modalMask{background:var(--dsw-alias-bg-mask-1);z-index:100;justify-content:center;align-items:center;padding:24px;display:flex;position:fixed;inset:0}.J84-9a_modal{background:var(--dsw-specific-menu);border-radius:16px;width:100%;padding:20px;box-shadow:0 12px 40px #0000002e}.J84-9a_modalTitle{color:var(--dsw-alias-label-primary);margin-bottom:8px;font-size:16px;font-weight:500;line-height:24px}.J84-9a_modalDesc{color:var(--dsw-alias-label-secondary);margin-bottom:16px;font-size:14px;line-height:22px}.J84-9a_modalFoot{justify-content:flex-end;gap:8px;display:flex}.J84-9a_quickWrap{flex-direction:column;gap:6px;display:flex;position:relative}.J84-9a_quickList{z-index:20;border:.5px solid var(--dsw-alias-border-l3);background:var(--dsw-specific-menu);border-radius:8px;max-height:264px;margin-top:2px;padding:4px;position:absolute;top:100%;left:0;right:0;overflow-y:auto;box-shadow:0 8px 24px #0000001f}.J84-9a_quickItem{width:100%;color:var(--dsw-alias-label-primary);text-align:left;cursor:pointer;background:0 0;border:none;border-radius:6px;flex-direction:column;gap:1px;padding:6px 8px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:13px;display:flex}.J84-9a_quickItemId{overflow-wrap:anywhere}.J84-9a_quickItemApi{color:var(--dsw-alias-label-tertiary);letter-spacing:.2px;font-size:11px;line-height:14px}.J84-9a_quickItem:hover{background:var(--dsw-alias-interactive-bg-hover)}.J84-9a_quickEmpty{color:var(--dsw-alias-label-tertiary);padding:6px 8px;font-size:12px;line-height:18px}@media (prefers-reduced-motion:reduce){.J84-9a_customizedSummary:before{transition:none}}\n";
7
7
  if (typeof document !== 'undefined' && document.querySelector('style[data-plugin-css="dsh-model-extension"]') === null) {
8
8
  var __tag = document.createElement('style');
9
9
  __tag.dataset.plugin = "dsh-model-extension";
@@ -369,6 +369,29 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
369
369
  return cachedIndex;
370
370
  }
371
371
  /**
372
+ * The one wire protocol one catalog provider's entries agree on.
373
+ *
374
+ * This mirrors the host's own `sharedCatalogApi`: a route whose installed
375
+ * entries all speak the same protocol needs no `api` in configuration, because
376
+ * the host resolves every model — including ones the catalog does not describe
377
+ * — to that shared protocol. Reading it here is what lets the panel judge a
378
+ * row's compat switches the way the adapter will. A provider whose entries
379
+ * disagree (an OpenAI-style catalog spanning Responses and Chat Completions)
380
+ * has no such answer, and neither does an index that has not loaded yet.
381
+ * @param index - the fetched index, or undefined while the page still loads.
382
+ * @param provider - the route key to inspect.
383
+ * @returns the shared protocol, or undefined when none settles it.
384
+ */
385
+ function sharedCatalogApi(index, provider) {
386
+ if (index === void 0 || provider === void 0 || provider.length === 0) return void 0;
387
+ const apis = /* @__PURE__ */ new Set();
388
+ for (const entry of index.piAi) {
389
+ if (entry.provider !== provider || entry.api === void 0) continue;
390
+ apis.add(entry.api);
391
+ }
392
+ return apis.size === 1 ? [...apis][0] : void 0;
393
+ }
394
+ /**
372
395
  * Download/refresh the models.dev file through the server half (user-triggered
373
396
  * from the title-row button). A success also refreshes the local cache.
374
397
  */
@@ -447,181 +470,6 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
447
470
  };
448
471
  }
449
472
  //#endregion
450
- //#region src/client/models-plus/compat.ts
451
- /**
452
- * The minimal path ops carrying `after` over `before`, both as the card sees
453
- * them. Only keys the card observed are named; fields absent from both sides
454
- * produce no op, which is why edits are path-addressed rather than a rebuilt
455
- * section.
456
- * @param base - path of the edited subtree inside the user section.
457
- * @param before - the subtree as loaded, or undefined when it is new.
458
- * @param after - the subtree as edited.
459
- * @returns ordered set/unset ops; empty when nothing changed.
460
- */
461
- function pathOps(base, before, after) {
462
- const previous = typeof before === "object" && before !== null && !Array.isArray(before) ? before : {};
463
- const ops = [];
464
- for (const [key, value] of Object.entries(after)) {
465
- if (JSON.stringify(previous[key]) === JSON.stringify(value)) continue;
466
- ops.push({
467
- op: "set",
468
- path: [...base, key],
469
- value
470
- });
471
- }
472
- for (const key of Object.keys(previous)) if (!(key in after)) ops.push({
473
- op: "unset",
474
- path: [...base, key]
475
- });
476
- return ops;
477
- }
478
- /** Accepted capacity spellings: a decimal count with an optional K/M suffix. */
479
- const CAPACITY_PATTERN = /^(\d+(?:\.\d+)?)([km])?$/i;
480
- /** Decimal suffix scales — `1M` is 1000K, matching how model capacities are quoted. */
481
- const CAPACITY_SCALE = {
482
- k: 1e3,
483
- m: 1e6
484
- };
485
- /**
486
- * Read a typed capacity, so a user can write `256K` or `1M` instead of counting
487
- * zeroes. The stored value stays a plain token count.
488
- * @param text - raw field text.
489
- * @returns the count; `undefined` when blank (inherit), `NaN` when unreadable
490
- * (rejected by {@link validateModelRows} before any write).
491
- */
492
- function parseCapacity(text) {
493
- const trimmed = text.trim();
494
- if (trimmed.length === 0) return void 0;
495
- const match = CAPACITY_PATTERN.exec(trimmed);
496
- if (match === null) return NaN;
497
- const suffix = match[2]?.toLowerCase();
498
- const scale = suffix === "k" || suffix === "m" ? CAPACITY_SCALE[suffix] : 1;
499
- const scaled = Number(match[1]) * scale;
500
- const rounded = Math.round(scaled);
501
- return Math.abs(scaled - rounded) < 1e-6 ? rounded : scaled;
502
- }
503
- /**
504
- * Spell a stored count back in the shortest form that survives a round trip
505
- * through {@link parseCapacity}; a count that is not a whole number of
506
- * thousands stays written out.
507
- * @param value - stored capacity.
508
- * @returns the field text.
509
- */
510
- function formatCapacity(value) {
511
- if (!Number.isInteger(value) || value <= 0) return String(value);
512
- if (value % CAPACITY_SCALE.m === 0) return `${String(value / CAPACITY_SCALE.m)}M`;
513
- if (value % CAPACITY_SCALE.k === 0) return `${String(value / CAPACITY_SCALE.k)}K`;
514
- return String(value);
515
- }
516
- /** Convert a schema-validated catalog value into records without dropping hidden fields. */
517
- function modelDrafts(value) {
518
- if (!Array.isArray(value)) return [];
519
- return value.map((entry) => typeof entry === "object" && entry !== null && !Array.isArray(entry) ? entry : {});
520
- }
521
- /** Canonical key order for one models[] row (plugin-known fields first). */
522
- const ROW_KEY_ORDER = [
523
- "id",
524
- "name",
525
- "contextWindow",
526
- "maxTokens",
527
- "input",
528
- "reasoningEfforts",
529
- "compat"
530
- ];
531
- /** Canonical key order for the row's compat sub-object. */
532
- const COMPAT_KEY_ORDER = [
533
- "supportsDeveloperRole",
534
- "supportsReasoningEffort",
535
- "thinkingFormat"
536
- ];
537
- /**
538
- * Re-key one model row into the canonical order (plugin-known fields in
539
- * order, unknown/future fields kept at the tail so nothing is dropped).
540
- * YAML emits keys in object insertion order, and interactive edits append
541
- * keys at the tail — without this, rows saved after quick-load or gated-key
542
- * removals come out with scrambled key orders (purely cosmetic, but noisy
543
- * in settings.yaml diffs).
544
- * @param row - the drafted row.
545
- * @returns the re-keyed row (shallow copy).
546
- */
547
- function normalizeModelRow(row) {
548
- const next = {};
549
- for (const key of ROW_KEY_ORDER) if (key in row) next[key] = row[key];
550
- for (const key of Object.keys(row)) if (!ROW_KEY_ORDER.includes(key)) next[key] = row[key];
551
- const compat = next["compat"];
552
- if (typeof compat === "object" && compat !== null && !Array.isArray(compat)) {
553
- const source = compat;
554
- const normalized = {};
555
- for (const key of COMPAT_KEY_ORDER) if (key in source) normalized[key] = source[key];
556
- for (const key of Object.keys(source)) if (!COMPAT_KEY_ORDER.includes(key)) normalized[key] = source[key];
557
- next["compat"] = normalized;
558
- }
559
- return next;
560
- }
561
- /**
562
- * Validate adapter constraints that the serialized schema cannot express.
563
- * @param value - user-owned `models` value, or undefined while inherited.
564
- * @returns the first invalid row, or undefined when the adapter will accept it.
565
- */
566
- function validateModelRows(value) {
567
- if (value === void 0) return void 0;
568
- const models = modelDrafts(value);
569
- const seen = /* @__PURE__ */ new Set();
570
- for (const [index, model] of models.entries()) {
571
- const id = model["id"];
572
- const trimmed = typeof id === "string" ? id.trim() : void 0;
573
- if (trimmed === void 0 || trimmed.length === 0) return {
574
- index,
575
- key: "modelIdRequired"
576
- };
577
- if (seen.has(trimmed)) return {
578
- index,
579
- key: "modelIdDuplicate"
580
- };
581
- seen.add(trimmed);
582
- const name = model["name"];
583
- if (name !== void 0 && (typeof name !== "string" || name.length === 0)) return {
584
- index,
585
- key: "modelNameInvalid"
586
- };
587
- const contextWindow = model["contextWindow"];
588
- if (contextWindow !== void 0 && (typeof contextWindow !== "number" || !Number.isInteger(contextWindow) || contextWindow <= 0)) return {
589
- index,
590
- key: "modelContextInvalid"
591
- };
592
- const maxTokens = model["maxTokens"];
593
- if (maxTokens !== void 0 && (typeof maxTokens !== "number" || !Number.isInteger(maxTokens) || maxTokens <= 0)) return {
594
- index,
595
- key: "modelMaxTokensInvalid"
596
- };
597
- }
598
- }
599
- //#endregion
600
- //#region src/client/models-plus/presets.ts
601
- /** The tri-state `compat.supportsDeveloperRole` seed: absent (reads checked). */
602
- const DEVELOPER_ROLE_SEED = {};
603
- /** Draft seed for one new model row. */
604
- function newModelDraft() {
605
- return {
606
- id: "",
607
- input: ["text", "image"],
608
- reasoningEfforts: {
609
- off: null,
610
- minimal: "minimal",
611
- low: "low",
612
- medium: "medium",
613
- high: "high",
614
- xhigh: "xhigh",
615
- max: "max"
616
- },
617
- compat: {
618
- ...DEVELOPER_ROLE_SEED,
619
- supportsReasoningEffort: true,
620
- thinkingFormat: "openai"
621
- }
622
- };
623
- }
624
- //#endregion
625
473
  //#region src/client/extension-meta.ts
626
474
  /** Thinking levels offered by llm-pi-ai (THINKING_LEVEL_GATE @ 0.1.2-alpha.4). Only `off` may carry an empty wire value. */
627
475
  const THINKING_LEVELS = [
@@ -726,6 +574,20 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
726
574
  const fields = COMPAT_FIELDS[api];
727
575
  return fields === void 0 ? [] : fields;
728
576
  }
577
+ /**
578
+ * Whether one protocol takes one compat switch.
579
+ *
580
+ * The adapter *refuses* a switch its protocol does not declare rather than
581
+ * ignoring it, so this is the difference between a control that can be offered
582
+ * and one that would fail the save. An unknown protocol takes nothing: its
583
+ * field set cannot be read, and the adapter resolves it to the same refusal.
584
+ * @param api - the model's wire protocol, when known.
585
+ * @param field - the compat field name.
586
+ * @returns true when the protocol offers the field.
587
+ */
588
+ function protocolOffers(api, field) {
589
+ return offeredCompatFields(api).includes(field);
590
+ }
729
591
  /** Extra copy for the extension, keyed like the official dictionaries (en is authoritative). */
730
592
  const en = {
731
593
  nav: "Models+",
@@ -951,6 +813,245 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
951
813
  thinkingFormat: "思考格式"
952
814
  };
953
815
  //#endregion
816
+ //#region src/client/models-plus/compat.ts
817
+ /**
818
+ * The minimal path ops carrying `after` over `before`, both as the card sees
819
+ * them. Only keys the card observed are named; fields absent from both sides
820
+ * produce no op, which is why edits are path-addressed rather than a rebuilt
821
+ * section.
822
+ * @param base - path of the edited subtree inside the user section.
823
+ * @param before - the subtree as loaded, or undefined when it is new.
824
+ * @param after - the subtree as edited.
825
+ * @returns ordered set/unset ops; empty when nothing changed.
826
+ */
827
+ function pathOps(base, before, after) {
828
+ const previous = typeof before === "object" && before !== null && !Array.isArray(before) ? before : {};
829
+ const ops = [];
830
+ for (const [key, value] of Object.entries(after)) {
831
+ if (JSON.stringify(previous[key]) === JSON.stringify(value)) continue;
832
+ ops.push({
833
+ op: "set",
834
+ path: [...base, key],
835
+ value
836
+ });
837
+ }
838
+ for (const key of Object.keys(previous)) if (!(key in after)) ops.push({
839
+ op: "unset",
840
+ path: [...base, key]
841
+ });
842
+ return ops;
843
+ }
844
+ /** Accepted capacity spellings: a decimal count with an optional K/M suffix. */
845
+ const CAPACITY_PATTERN = /^(\d+(?:\.\d+)?)([km])?$/i;
846
+ /** Decimal suffix scales — `1M` is 1000K, matching how model capacities are quoted. */
847
+ const CAPACITY_SCALE = {
848
+ k: 1e3,
849
+ m: 1e6
850
+ };
851
+ /**
852
+ * Read a typed capacity, so a user can write `256K` or `1M` instead of counting
853
+ * zeroes. The stored value stays a plain token count.
854
+ * @param text - raw field text.
855
+ * @returns the count; `undefined` when blank (inherit), `NaN` when unreadable
856
+ * (rejected by {@link validateModelRows} before any write).
857
+ */
858
+ function parseCapacity(text) {
859
+ const trimmed = text.trim();
860
+ if (trimmed.length === 0) return void 0;
861
+ const match = CAPACITY_PATTERN.exec(trimmed);
862
+ if (match === null) return NaN;
863
+ const suffix = match[2]?.toLowerCase();
864
+ const scale = suffix === "k" || suffix === "m" ? CAPACITY_SCALE[suffix] : 1;
865
+ const scaled = Number(match[1]) * scale;
866
+ const rounded = Math.round(scaled);
867
+ return Math.abs(scaled - rounded) < 1e-6 ? rounded : scaled;
868
+ }
869
+ /**
870
+ * Spell a stored count back in the shortest form that survives a round trip
871
+ * through {@link parseCapacity}; a count that is not a whole number of
872
+ * thousands stays written out.
873
+ * @param value - stored capacity.
874
+ * @returns the field text.
875
+ */
876
+ function formatCapacity(value) {
877
+ if (!Number.isInteger(value) || value <= 0) return String(value);
878
+ if (value % CAPACITY_SCALE.m === 0) return `${String(value / CAPACITY_SCALE.m)}M`;
879
+ if (value % CAPACITY_SCALE.k === 0) return `${String(value / CAPACITY_SCALE.k)}K`;
880
+ return String(value);
881
+ }
882
+ /** Convert a schema-validated catalog value into records without dropping hidden fields. */
883
+ function modelDrafts(value) {
884
+ if (!Array.isArray(value)) return [];
885
+ return value.map((entry) => typeof entry === "object" && entry !== null && !Array.isArray(entry) ? entry : {});
886
+ }
887
+ /** Canonical key order for one models[] row (plugin-known fields first). */
888
+ const ROW_KEY_ORDER = [
889
+ "id",
890
+ "name",
891
+ "contextWindow",
892
+ "maxTokens",
893
+ "input",
894
+ "reasoningEfforts",
895
+ "compat"
896
+ ];
897
+ /** Canonical key order for the row's compat sub-object. */
898
+ const COMPAT_KEY_ORDER = [
899
+ "supportsDeveloperRole",
900
+ "supportsReasoningEffort",
901
+ "thinkingFormat"
902
+ ];
903
+ /**
904
+ * Re-key one model row into the canonical order (plugin-known fields in
905
+ * order, unknown/future fields kept at the tail so nothing is dropped).
906
+ * YAML emits keys in object insertion order, and interactive edits append
907
+ * keys at the tail — without this, rows saved after quick-load or gated-key
908
+ * removals come out with scrambled key orders (purely cosmetic, but noisy
909
+ * in settings.yaml diffs).
910
+ * @param row - the drafted row.
911
+ * @returns the re-keyed row (shallow copy).
912
+ */
913
+ function normalizeModelRow(row) {
914
+ const next = {};
915
+ for (const key of ROW_KEY_ORDER) if (key in row) next[key] = row[key];
916
+ for (const key of Object.keys(row)) if (!ROW_KEY_ORDER.includes(key)) next[key] = row[key];
917
+ const compat = next["compat"];
918
+ if (typeof compat === "object" && compat !== null && !Array.isArray(compat)) {
919
+ const source = compat;
920
+ const normalized = {};
921
+ for (const key of COMPAT_KEY_ORDER) if (key in source) normalized[key] = source[key];
922
+ for (const key of Object.keys(source)) if (!COMPAT_KEY_ORDER.includes(key)) normalized[key] = source[key];
923
+ next["compat"] = normalized;
924
+ }
925
+ return next;
926
+ }
927
+ /**
928
+ * The message for a refused compat switch, shared by both provider cards so the
929
+ * same failure reads the same way wherever it is hit.
930
+ * @param failure - a `modelCompatNotOffered` failure from {@link validateModelRows}.
931
+ * @returns the localised sentence.
932
+ */
933
+ function compatFailureMessage(failure) {
934
+ const offered = failure.offered ?? [];
935
+ const available = offered.length === 0 ? ",该协议不接受任何可配置开关" : `,该协议可用:${offered.join("、")}`;
936
+ return `模型 ${String(failure.index + 1)}:compat 开关 ${String(failure.field)} 不适用于当前协议(${String(failure.api)})${available}。请在展开的「其他兼容开关」中清空该项以移除,或回到供应商页把协议改成端点真正使用的协议。`;
937
+ }
938
+ /**
939
+ * Validate adapter constraints that the serialized schema cannot express.
940
+ *
941
+ * The compat half of the check exists because the adapter *refuses* a switch a
942
+ * model's protocol does not declare rather than ignoring it — and the protocol
943
+ * a row resolves to is `route.api` first, the installed catalog entry second.
944
+ * A row can therefore hold switches that were legal when it was written (a
945
+ * protocol change on the route, or a prefill taken from another protocol's
946
+ * catalog entry) and only fail at save time, in English, from the host. This
947
+ * turns that into a positioned, named, localised refusal.
948
+ *
949
+ * An unknown protocol is skipped on purpose: the route's protocol could not be
950
+ * resolved, so the row's real one is unknowable here, and a guess would refuse
951
+ * a configuration the host accepts. The host's own diagnostic still covers it.
952
+ * @param value - user-owned `models` value, or undefined while inherited.
953
+ * @param api - the protocol every row resolves to, when the route settles one.
954
+ * @returns the first invalid row, or undefined when the adapter will accept it.
955
+ */
956
+ function validateModelRows(value, api) {
957
+ if (value === void 0) return void 0;
958
+ const models = modelDrafts(value);
959
+ const seen = /* @__PURE__ */ new Set();
960
+ for (const [index, model] of models.entries()) {
961
+ const id = model["id"];
962
+ const trimmed = typeof id === "string" ? id.trim() : void 0;
963
+ if (trimmed === void 0 || trimmed.length === 0) return {
964
+ index,
965
+ key: "modelIdRequired"
966
+ };
967
+ if (seen.has(trimmed)) return {
968
+ index,
969
+ key: "modelIdDuplicate"
970
+ };
971
+ seen.add(trimmed);
972
+ const name = model["name"];
973
+ if (name !== void 0 && (typeof name !== "string" || name.length === 0)) return {
974
+ index,
975
+ key: "modelNameInvalid"
976
+ };
977
+ const contextWindow = model["contextWindow"];
978
+ if (contextWindow !== void 0 && (typeof contextWindow !== "number" || !Number.isInteger(contextWindow) || contextWindow <= 0)) return {
979
+ index,
980
+ key: "modelContextInvalid"
981
+ };
982
+ const maxTokens = model["maxTokens"];
983
+ if (maxTokens !== void 0 && (typeof maxTokens !== "number" || !Number.isInteger(maxTokens) || maxTokens <= 0)) return {
984
+ index,
985
+ key: "modelMaxTokensInvalid"
986
+ };
987
+ const compat = model["compat"];
988
+ if (api !== void 0 && typeof compat === "object" && compat !== null && !Array.isArray(compat)) for (const field of Object.keys(compat)) {
989
+ if (protocolOffers(api, field)) continue;
990
+ return {
991
+ index,
992
+ key: "modelCompatNotOffered",
993
+ field,
994
+ api,
995
+ offered: offeredCompatFields(api)
996
+ };
997
+ }
998
+ }
999
+ }
1000
+ //#endregion
1001
+ //#region src/client/models-plus/presets.ts
1002
+ /**
1003
+ * Draft defaults for a NEW model row (added by hand or adopted from a fetch),
1004
+ * per the v1.0.0 plan: reasoning effort ON with every level declared, thinking
1005
+ * format `openai`, both input modalities checked. These are draft seeds that
1006
+ * land in settings.yaml when the row is saved — they are not inherited-state
1007
+ * sentinels, except `supportsDeveloperRole`, which stays UNWRITTEN (its
1008
+ * tri-state reads an absent key as checked, so only an explicit user uncheck
1009
+ * ever writes `false`).
1010
+ *
1011
+ * The compat half of the seed is PROTOCOL-DEPENDENT, and that is load-bearing:
1012
+ * `supportsReasoningEffort` and `thinkingFormat` belong to `openai-completions`
1013
+ * alone, and the adapter refuses a switch its protocol does not declare rather
1014
+ * than ignoring it. Seeding them onto an `anthropic-messages` route — which is
1015
+ * what a fixed vocabulary here did — made every hand-added model on such a
1016
+ * route fail the save over a switch the user never wrote. So the seed asks
1017
+ * {@link protocolOffers} first and writes nothing its protocol would refuse.
1018
+ * `reasoningEfforts` is unaffected: the levels travel through each protocol's
1019
+ * own thinking parameter, so every protocol takes them.
1020
+ */
1021
+ /** The tri-state `compat.supportsDeveloperRole` seed: absent (reads checked). */
1022
+ const DEVELOPER_ROLE_SEED = {};
1023
+ /**
1024
+ * Draft seed for one new model row.
1025
+ *
1026
+ * An UNKNOWN protocol (no route-level `api`, and the installed catalog does not
1027
+ * describe the route) seeds no compat at all: nothing can say which switches
1028
+ * its endpoint would take, and a guessed `supportsReasoningEffort` on an
1029
+ * Anthropic-shaped route is refused rather than dropped. The levels stay seeded
1030
+ * — they are protocol-independent — so the row still opens with its effort
1031
+ * table populated.
1032
+ * @param api - the wire protocol this row will resolve to, when known.
1033
+ * @returns the draft row.
1034
+ */
1035
+ function newModelDraft(api) {
1036
+ const compat = { ...DEVELOPER_ROLE_SEED };
1037
+ if (protocolOffers(api, "supportsReasoningEffort")) compat["supportsReasoningEffort"] = true;
1038
+ if (protocolOffers(api, "thinkingFormat")) compat["thinkingFormat"] = "openai";
1039
+ return {
1040
+ id: "",
1041
+ input: ["text", "image"],
1042
+ reasoningEfforts: {
1043
+ off: null,
1044
+ minimal: "minimal",
1045
+ low: "low",
1046
+ medium: "medium",
1047
+ high: "high",
1048
+ xhigh: "xhigh",
1049
+ max: "max"
1050
+ },
1051
+ ...Object.keys(compat).length === 0 ? {} : { compat }
1052
+ };
1053
+ }
1054
+ //#endregion
954
1055
  //#region src/client/models-plus/prefill.ts
955
1056
  /**
956
1057
  * Mapping pi-ai catalog values onto a Models+ row.
@@ -1033,12 +1134,16 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
1033
1134
  * Project a catalog `compat` block onto the switches the host offers for one
1034
1135
  * protocol.
1035
1136
  *
1036
- * The filter is load-bearing: pi-ai's catalog also records withheld switches
1037
- * (routing preferences, session affinity, deferred tools, grammar tools), and
1038
- * the adapter *refuses* a switch no protocol offers instead of ignoring it — a
1039
- * verbatim copy would make the route fail to resolve.
1137
+ * The filter is load-bearing twice over. It drops pi-ai's withheld switches
1138
+ * (routing preferences, session affinity, deferred tools, grammar tools), which
1139
+ * the adapter *refuses* rather than ignores — a verbatim copy would make the
1140
+ * route fail to resolve. And it filters by the protocol the row will be JUDGED
1141
+ * by, which is not always the catalog entry's own: a hand-declared route states
1142
+ * its `api`, that value overrides the catalog for every model on the route, and
1143
+ * a block taken from another protocol's entry (`supportsStore` on a gateway
1144
+ * that speaks `anthropic-messages`) is refused at save time.
1040
1145
  * @param compat - the catalog's compat record, when it carries one.
1041
- * @param api - the model's wire protocol.
1146
+ * @param api - the protocol the row resolves to, as the adapter resolves it.
1042
1147
  * @returns the writable subset; empty when nothing survives.
1043
1148
  */
1044
1149
  function compatFrom(compat, api) {
@@ -1116,6 +1221,8 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
1116
1221
  "primaryButton": "J84-9a_primaryButton",
1117
1222
  "quickEmpty": "J84-9a_quickEmpty",
1118
1223
  "quickItem": "J84-9a_quickItem",
1224
+ "quickItemApi": "J84-9a_quickItemApi",
1225
+ "quickItemId": "J84-9a_quickItemId",
1119
1226
  "quickList": "J84-9a_quickList",
1120
1227
  "quickWrap": "J84-9a_quickWrap",
1121
1228
  "rowActions": "J84-9a_rowActions",
@@ -1284,14 +1391,20 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
1284
1391
  className: models_plus_module_default["quickEmpty"],
1285
1392
  children: "没有匹配的模型"
1286
1393
  });
1287
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [hits.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1394
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [hits.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1288
1395
  type: "button",
1289
1396
  className: models_plus_module_default["quickItem"],
1290
1397
  onMouseDown: (event) => {
1291
1398
  event.preventDefault();
1292
1399
  pick(entry);
1293
1400
  },
1294
- children: entry.id
1401
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1402
+ className: models_plus_module_default["quickItemId"],
1403
+ children: entry.id
1404
+ }), entry.api === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1405
+ className: models_plus_module_default["quickItemApi"],
1406
+ children: entry.api
1407
+ })]
1295
1408
  }, entry.key)), total > hits.length ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1296
1409
  className: models_plus_module_default["quickEmpty"],
1297
1410
  children: [
@@ -1406,6 +1519,13 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
1406
1519
  * rides the card's existing path-ops save with no extra wire calls. Clearing
1407
1520
  * the effort gate drops `reasoningEfforts` AND `compat.thinkingFormat` in ONE
1408
1521
  * commit so partial updates never overwrite each other (no orphan settings).
1522
+ *
1523
+ * Which compat switches this panel may write at all is decided by `api` (see
1524
+ * {@link ModelEntryPanelProps.api}): the adapter refuses a switch the model's
1525
+ * protocol does not declare instead of ignoring it, so a control offered on the
1526
+ * wrong protocol is a save that cannot succeed. An UNKNOWN protocol hides
1527
+ * nothing — nothing can be judged — and the host's own diagnostic still covers
1528
+ * that case.
1409
1529
  */
1410
1530
  /**
1411
1531
  * Whether a draft field is absent or whitespace-only.
@@ -1438,7 +1558,18 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
1438
1558
  * @returns the expanded panel.
1439
1559
  */
1440
1560
  function ModelEntryPanel(props) {
1441
- const { model, onChange, disabled } = props;
1561
+ const { model, onChange, disabled, api } = props;
1562
+ /**
1563
+ * Whether this panel may OFFER a control for one compat switch.
1564
+ *
1565
+ * Not the same question as `protocolOffers`, and deliberately so: an
1566
+ * unresolvable protocol answers `true` throughout, because with no protocol to
1567
+ * read nothing can be judged — hiding a control on a guess would remove a
1568
+ * switch the route may well take, while the host still names anything it
1569
+ * refuses. A resolved protocol that offers nothing (one pi-ai gives no compat
1570
+ * type) answers `false` throughout, which is exactly what the adapter says.
1571
+ */
1572
+ const mayOffer = (field) => api === void 0 || offeredCompatFields(api).includes(field);
1442
1573
  const [capacityText, setCapacityText] = (0, react.useState)(() => /* @__PURE__ */ new Map());
1443
1574
  /** Patch one key onto the row; `undefined` clears it. */
1444
1575
  const setKey = (key, value) => {
@@ -1479,6 +1610,7 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
1479
1610
  */
1480
1611
  const applyMetadata = (entry) => {
1481
1612
  const next = { ...model };
1613
+ const judgedBy = props.api ?? entry.api;
1482
1614
  if (entry.context === void 0) Reflect.deleteProperty(next, "contextWindow");
1483
1615
  else next["contextWindow"] = entry.context;
1484
1616
  if (entry.output === void 0) Reflect.deleteProperty(next, "maxTokens");
@@ -1489,13 +1621,13 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
1489
1621
  if (entry.source === "pi-ai") {
1490
1622
  if (isBlank(next["id"])) next["id"] = entry.id;
1491
1623
  if (isBlank(next["name"]) && entry.name !== void 0) next["name"] = entry.name;
1492
- next["reasoningEfforts"] = reasoningEffortsFrom(entry.thinkingLevelMap, entry.api, entry.reasoning);
1493
- const compat = compatFrom(entry.compat, entry.api);
1624
+ next["reasoningEfforts"] = reasoningEffortsFrom(entry.thinkingLevelMap, judgedBy, entry.reasoning);
1625
+ const compat = compatFrom(entry.compat, judgedBy);
1494
1626
  if (Object.keys(compat).length === 0) Reflect.deleteProperty(next, "compat");
1495
1627
  else next["compat"] = compat;
1496
1628
  } else {
1497
1629
  Reflect.deleteProperty(next, "reasoningEfforts");
1498
- if (entry.reasoning) next["compat"] = { supportsReasoningEffort: true };
1630
+ if (entry.reasoning && mayOffer("supportsReasoningEffort")) next["compat"] = { supportsReasoningEffort: true };
1499
1631
  else Reflect.deleteProperty(next, "compat");
1500
1632
  }
1501
1633
  onChange(next);
@@ -1538,13 +1670,20 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
1538
1670
  const compat = compatOf(model);
1539
1671
  const developerRole = compat["supportsDeveloperRole"] !== false;
1540
1672
  /**
1541
- * Switches a prefill wrote that the block above has no control for. Listing
1542
- * them keeps a prefilled value from becoming invisible state: the pi-ai
1543
- * catalog records far more of the compat surface than this panel models
1544
- * explicitly, and that API refuses an unknown switch rather than ignoring
1545
- * it — so a value the user cannot see is a value the user cannot fix.
1673
+ * Switches a prefill wrote that no control above stands for. Listing them
1674
+ * keeps a prefilled value from becoming invisible state: the pi-ai catalog
1675
+ * records far more of the compat surface than this panel models explicitly,
1676
+ * and that API refuses an unknown switch rather than ignoring it — so a value
1677
+ * the user cannot see is a value the user cannot fix.
1678
+ *
1679
+ * The set is the panel's base list narrowed to what this protocol takes. A
1680
+ * switch the protocol refuses keeps NO control — writing it would fail the
1681
+ * save — so it lands here instead, where it is named as refused and can be
1682
+ * cleared. That is the only way out of a row written before a protocol change
1683
+ * on the route, which no amount of hiding would repair.
1546
1684
  */
1547
- const extraCompat = Object.entries(compat).filter(([key]) => !COMPAT_BASE_FIELDS.includes(key)).sort(([a], [b]) => a.localeCompare(b));
1685
+ const dedicated = COMPAT_BASE_FIELDS.filter((field) => mayOffer(field));
1686
+ const extraCompat = Object.entries(compat).filter(([key]) => !dedicated.includes(key)).sort(([a], [b]) => a.localeCompare(b));
1548
1687
  /**
1549
1688
  * Read an edited switch back as the type it already had.
1550
1689
  * @param previous - the value the switch carried when rendered.
@@ -1564,8 +1703,14 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
1564
1703
  return previous;
1565
1704
  }
1566
1705
  };
1567
- /** The switch as a wire field: whether the endpoint takes `reasoning_effort`. */
1568
- const gateOn = compat["supportsReasoningEffort"] === true;
1706
+ /**
1707
+ * The switch as a wire field: whether the endpoint takes `reasoning_effort`.
1708
+ *
1709
+ * Gated on the protocol as well as on the stored value: on a protocol that
1710
+ * does not take the switch a leftover `true` describes nothing this route can
1711
+ * act on, and the field's real home is the refused-switch list below.
1712
+ */
1713
+ const gateOn = mayOffer("supportsReasoningEffort") && compat["supportsReasoningEffort"] === true;
1569
1714
  const efforts = effortsOf(model);
1570
1715
  /**
1571
1716
  * Whether the reasoning area is worth showing.
@@ -1578,7 +1723,7 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
1578
1723
  * through the protocol's own thinking parameter either way. So the area keys
1579
1724
  * off either signal, not the switch alone.
1580
1725
  */
1581
- const reasoningAreaShown = gateOn || efforts !== void 0 || compat["thinkingFormat"] !== void 0;
1726
+ const reasoningAreaShown = gateOn || efforts !== void 0 || mayOffer("thinkingFormat") && compat["thinkingFormat"] !== void 0;
1582
1727
  const toggleGate = (checked) => {
1583
1728
  if (checked) {
1584
1729
  setCompatKey("supportsReasoningEffort", true);
@@ -1657,7 +1802,7 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
1657
1802
  }, modality))
1658
1803
  })]
1659
1804
  }),
1660
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1805
+ mayOffer("supportsDeveloperRole") ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1661
1806
  className: models_plus_module_default["extGroup"],
1662
1807
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1663
1808
  className: models_plus_module_default["extCheck"],
@@ -1685,10 +1830,10 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
1685
1830
  })]
1686
1831
  })]
1687
1832
  })
1688
- }),
1833
+ }) : null,
1689
1834
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1690
1835
  className: models_plus_module_default["extGroup"],
1691
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1836
+ children: [mayOffer("supportsReasoningEffort") ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1692
1837
  className: models_plus_module_default["switchRow"],
1693
1838
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1694
1839
  type: "checkbox",
@@ -1699,7 +1844,14 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
1699
1844
  toggleGate(event.target.checked);
1700
1845
  }
1701
1846
  }), "端点接受推理挡位参数(reasoning_effort)"]
1702
- }), !gateOn && reasoningAreaShown ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1847
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
1848
+ className: models_plus_module_default["extHint"],
1849
+ children: [
1850
+ "当前协议(",
1851
+ String(api),
1852
+ ")不接受 reasoning_effort 开关,故此处不提供;下面的挡位仍通过该协议自身的 thinking 参数生效。"
1853
+ ]
1854
+ }), mayOffer("supportsReasoningEffort") && !gateOn && reasoningAreaShown ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1703
1855
  className: models_plus_module_default["extHint"],
1704
1856
  style: { marginTop: 6 },
1705
1857
  children: "此项未设置:由 llm-pi-ai 按其 baseURL 检测结果决定是否发送 reasoning_effort; 下面的挡位不受影响,仍通过各协议自己的 thinking 参数生效。"
@@ -1708,7 +1860,7 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
1708
1860
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1709
1861
  className: models_plus_module_default["extGated"],
1710
1862
  hidden: !reasoningAreaShown,
1711
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1863
+ children: [mayOffer("thinkingFormat") ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1712
1864
  className: models_plus_module_default["extField"],
1713
1865
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1714
1866
  className: models_plus_module_default["extLabel"],
@@ -1728,7 +1880,7 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
1728
1880
  children: format
1729
1881
  }, format))]
1730
1882
  })]
1731
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1883
+ }) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1732
1884
  className: models_plus_module_default["extField"],
1733
1885
  children: [
1734
1886
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
@@ -1816,27 +1968,30 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
1816
1968
  flexDirection: "column",
1817
1969
  gap: 6
1818
1970
  },
1819
- children: extraCompat.map(([key, value]) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1820
- style: {
1821
- display: "flex",
1822
- flexDirection: "column",
1823
- gap: 2
1824
- },
1825
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1826
- className: models_plus_module_default["extHint"],
1827
- children: key
1828
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1829
- className: models_plus_module_default["input"],
1830
- type: "text",
1831
- defaultValue: typeof value === "string" ? value : JSON.stringify(value) ?? "",
1832
- "aria-label": key,
1833
- disabled,
1834
- onBlur: (event) => {
1835
- const raw = event.target.value.trim();
1836
- setCompatKey(key, raw.length === 0 ? void 0 : readCompatValue(value, raw));
1837
- }
1838
- })]
1839
- }, key))
1971
+ children: extraCompat.map(([key, value]) => {
1972
+ const refused = !mayOffer(key);
1973
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1974
+ style: {
1975
+ display: "flex",
1976
+ flexDirection: "column",
1977
+ gap: 2
1978
+ },
1979
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1980
+ className: refused ? models_plus_module_default["error"] : models_plus_module_default["extHint"],
1981
+ children: refused ? `${key} —— 当前协议(${String(api)})不接受此开关,请清空输入框以移除` : key
1982
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1983
+ className: models_plus_module_default["input"],
1984
+ type: "text",
1985
+ defaultValue: typeof value === "string" ? value : JSON.stringify(value) ?? "",
1986
+ "aria-label": key,
1987
+ disabled,
1988
+ onBlur: (event) => {
1989
+ const raw = event.target.value.trim();
1990
+ setCompatKey(key, raw.length === 0 ? void 0 : readCompatValue(value, raw));
1991
+ }
1992
+ })]
1993
+ }, key);
1994
+ })
1840
1995
  }),
1841
1996
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1842
1997
  className: models_plus_module_default["extHint"],
@@ -1862,8 +2017,8 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
1862
2017
  return typeof value === "string" ? value : "";
1863
2018
  }
1864
2019
  /** Adopt one candidate, v1.0.0 defaults + the endpoint's disclosed capacities. */
1865
- function adopt(candidate) {
1866
- const row = newModelDraft();
2020
+ function adopt(candidate, routeApi) {
2021
+ const row = newModelDraft(routeApi);
1867
2022
  row["id"] = candidate.id;
1868
2023
  row["name"] = candidate.name ?? candidate.id;
1869
2024
  if (candidate.contextWindow !== void 0) row["contextWindow"] = candidate.contextWindow;
@@ -1946,7 +2101,7 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
1946
2101
  const byId = new Map(models.map((model) => [textOf(model, "id"), model]));
1947
2102
  for (const candidate of candidates) {
1948
2103
  if (!picked.has(candidate.id)) continue;
1949
- byId.set(candidate.id, byId.get(candidate.id) ?? adopt(candidate));
2104
+ byId.set(candidate.id, byId.get(candidate.id) ?? adopt(candidate, props.routeApi));
1950
2105
  }
1951
2106
  onChange([...byId.values()]);
1952
2107
  closePicker();
@@ -2089,6 +2244,7 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
2089
2244
  ]
2090
2245
  }), expanded.has(index) ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelEntryPanel, {
2091
2246
  model,
2247
+ api: props.routeApi,
2092
2248
  disabled,
2093
2249
  onChange: (next) => {
2094
2250
  onChange(models.map((row, at) => at === index ? next : row));
@@ -2101,7 +2257,7 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
2101
2257
  className: models_plus_module_default["addModelButton"],
2102
2258
  disabled,
2103
2259
  onClick: () => {
2104
- onChange([...models, newModelDraft()]);
2260
+ onChange([...models, newModelDraft(props.routeApi)]);
2105
2261
  },
2106
2262
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
2107
2263
  width: "14",
@@ -2251,6 +2407,22 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
2251
2407
  namespace,
2252
2408
  schema
2253
2409
  ]);
2410
+ /**
2411
+ * The pi-ai catalog, loaded only on the family that needs it: it answers the
2412
+ * protocol of a route that states none (a catalog route), which is what the
2413
+ * model panel judges compat switches by.
2414
+ */
2415
+ const [prefillIndex, setPrefillIndex] = (0, react.useState)(void 0);
2416
+ (0, react.useEffect)(() => {
2417
+ if (layout !== "pi-ai") return;
2418
+ let stale = false;
2419
+ getPrefillIndex().then((loaded) => {
2420
+ if (!stale) setPrefillIndex(loaded);
2421
+ });
2422
+ return () => {
2423
+ stale = true;
2424
+ };
2425
+ }, [layout]);
2254
2426
  (0, react.useEffect)(() => {
2255
2427
  let stale = false;
2256
2428
  setKeyState(void 0);
@@ -2270,7 +2442,18 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
2270
2442
  const value = next === void 0 || next.trim().length === 0 ? void 0 : next;
2271
2443
  setDraft((current) => value === void 0 ? schema.deletePath(current, [key]) : schema.setPath(current, [key], value));
2272
2444
  };
2273
- const modelFailure = validateModelRows(schema.getPath(draft, ["models"]));
2445
+ /**
2446
+ * The protocol every row of this route resolves to, resolved the way the
2447
+ * adapter resolves it: a hand-declared route's own `api` first — it overrides
2448
+ * the catalog for every model on the route — then the protocol the installed
2449
+ * catalog's entries for this route agree on, then nothing.
2450
+ *
2451
+ * The model panel needs it because the adapter judges each row's compat
2452
+ * switches by THIS protocol, not by the catalog entry the row was prefilled
2453
+ * from; without it a row can carry switches the route will refuse.
2454
+ */
2455
+ const routeApi = layout === "pi-ai" ? (props.declared === true ? stringAt(draft, "api") ?? stringAt(fallback, "api") : void 0) ?? sharedCatalogApi(prefillIndex, props.provider) : void 0;
2456
+ const modelFailure = validateModelRows(schema.getPath(draft, ["models"]), routeApi);
2274
2457
  const keyFailure = (() => {
2275
2458
  if (keyDraft.length === 0) return void 0;
2276
2459
  const value = keyDraft.trim();
@@ -2298,8 +2481,8 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
2298
2481
  const applyOnce = async () => {
2299
2482
  const ns = namespace.ns;
2300
2483
  let next = layout === "pi-ai" && stringAt(draft, "apiKeyEnv") === void 0 && stringAt(fallback, "apiKeyEnv") === void 0 && keyValue.length > 0 ? schema.setPath(draft, ["apiKeyEnv"], keyRef) : draft;
2301
- const rowFailure = validateModelRows(schema.getPath(next, ["models"]));
2302
- if (rowFailure !== void 0) return `模型 ${String(rowFailure.index + 1)}:${{
2484
+ const rowFailure = validateModelRows(schema.getPath(next, ["models"]), routeApi);
2485
+ if (rowFailure !== void 0) return rowFailure.key === "modelCompatNotOffered" ? compatFailureMessage(rowFailure) : `模型 ${String(rowFailure.index + 1)}:${{
2303
2486
  modelIdRequired: "模型 ID 不能为空。",
2304
2487
  modelIdDuplicate: "模型 ID 不能重复。",
2305
2488
  modelNameInvalid: "显示名称不能为空。",
@@ -2494,6 +2677,7 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
2494
2677
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelCatalog, {
2495
2678
  models,
2496
2679
  overridden: modelsOverridden,
2680
+ routeApi,
2497
2681
  defaultContextWindow: typeof defaultContextWindow === "number" ? defaultContextWindow : void 0,
2498
2682
  defaultMaxTokens: typeof defaultMaxTokens === "number" ? defaultMaxTokens : void 0,
2499
2683
  probe,
@@ -2516,7 +2700,7 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
2516
2700
  }) : null,
2517
2701
  modelFailure === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2518
2702
  className: models_plus_module_default["advancedHint"],
2519
- children: `模型 ${String(modelFailure.index + 1)}:${modelFailure.key}`
2703
+ children: modelFailure.key === "modelCompatNotOffered" ? compatFailureMessage(modelFailure) : `模型 ${String(modelFailure.index + 1)}:${modelFailure.key}`
2520
2704
  }),
2521
2705
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2522
2706
  className: models_plus_module_default["editorActions"],
@@ -2580,7 +2764,7 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
2580
2764
  const profileDisabled = disabled || committed;
2581
2765
  const routeInvalid = route.length > 0 && !ROUTE_PATTERN.test(route);
2582
2766
  const routeTaken = taken.includes(route);
2583
- const modelFailure = validateModelRows(models);
2767
+ const modelFailure = validateModelRows(models, protocol);
2584
2768
  const keyFailure = (() => {
2585
2769
  if (keyDraft.length === 0) return void 0;
2586
2770
  const value = keyDraft.trim();
@@ -2592,7 +2776,7 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
2592
2776
  })();
2593
2777
  const keyValue = keyDraft.trim();
2594
2778
  const ready = route.length > 0 && !routeInvalid && !routeTaken && baseURL.length > 0 && models.length > 0 && modelFailure === void 0 && keyFailure === void 0;
2595
- const hint = failure !== void 0 || ready || keyFailure !== void 0 || route.length === 0 || routeInvalid || routeTaken ? void 0 : baseURL.length === 0 ? "自定义提供方需要填写 API 地址。" : modelFailure !== void 0 ? `模型 ${String(modelFailure.index + 1)}:${modelFailure.key}` : "自定义提供方至少需要一个模型。";
2779
+ const hint = failure !== void 0 || ready || keyFailure !== void 0 || route.length === 0 || routeInvalid || routeTaken ? void 0 : baseURL.length === 0 ? "自定义提供方需要填写 API 地址。" : modelFailure !== void 0 ? modelFailure.key === "modelCompatNotOffered" ? compatFailureMessage(modelFailure) : `模型 ${String(modelFailure.index + 1)}:${modelFailure.key}` : "自定义提供方至少需要一个模型。";
2596
2780
  /** Perform the create, returning a failure message or undefined. */
2597
2781
  const createOnce = async () => {
2598
2782
  const keyRef = deriveKeyRef(route);
@@ -2784,6 +2968,7 @@ if (typeof document !== 'undefined' && document.querySelector('style[data-plugin
2784
2968
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelCatalog, {
2785
2969
  models,
2786
2970
  overridden: true,
2971
+ routeApi: protocol,
2787
2972
  defaultContextWindow: void 0,
2788
2973
  defaultMaxTokens: void 0,
2789
2974
  probe: {