From f7ed250df23c1ed1b31d50e5e5f09653bef39041 Mon Sep 17 00:00:00 2001 From: jidan745le <420511176@qq.com> Date: Sat, 4 Apr 2026 15:33:37 +0800 Subject: [PATCH 01/29] feat: add provider catalog baseline Signed-off-by: jidan745le <420511176@qq.com> --- packages/provider-catalog/README.md | 196 ++++ packages/provider-catalog/data/models.pb | Bin 0 -> 792407 bytes .../provider-catalog/data/provider-models.pb | Bin 0 -> 102905 bytes packages/provider-catalog/data/providers.pb | Bin 0 -> 8539 bytes packages/provider-catalog/package.json | 68 ++ .../provider-catalog/src/catalog-reader.ts | 35 + .../provider-catalog/src/gen/v1/common_pb.ts | 456 ++++++++++ .../provider-catalog/src/gen/v1/model_pb.ts | 387 ++++++++ .../src/gen/v1/provider_models_pb.ts | 208 +++++ .../src/gen/v1/provider_pb.ts | 689 ++++++++++++++ packages/provider-catalog/src/index.ts | 45 + .../provider-catalog/src/schemas/common.ts | 58 ++ .../provider-catalog/src/schemas/enums.ts | 70 ++ .../provider-catalog/src/schemas/index.ts | 19 + .../provider-catalog/src/schemas/model.ts | 179 ++++ .../src/schemas/provider-models.ts | 88 ++ .../provider-catalog/src/schemas/provider.ts | 231 +++++ .../utils/importers/base/base-transformer.ts | 857 ++++++++++++++++++ packages/shared/data/api/schemas/models.ts | 155 ++++ packages/shared/data/api/schemas/providers.ts | 166 ++++ packages/shared/data/types/model.ts | 274 ++++++ packages/shared/data/types/provider.ts | 239 +++++ packages/shared/data/utils/modelMerger.ts | 446 +++++++++ 23 files changed, 4866 insertions(+) create mode 100644 packages/provider-catalog/README.md create mode 100644 packages/provider-catalog/data/models.pb create mode 100644 packages/provider-catalog/data/provider-models.pb create mode 100644 packages/provider-catalog/data/providers.pb create mode 100644 packages/provider-catalog/package.json create mode 100644 packages/provider-catalog/src/catalog-reader.ts create mode 100644 packages/provider-catalog/src/gen/v1/common_pb.ts create mode 100644 packages/provider-catalog/src/gen/v1/model_pb.ts create mode 100644 packages/provider-catalog/src/gen/v1/provider_models_pb.ts create mode 100644 packages/provider-catalog/src/gen/v1/provider_pb.ts create mode 100644 packages/provider-catalog/src/index.ts create mode 100644 packages/provider-catalog/src/schemas/common.ts create mode 100644 packages/provider-catalog/src/schemas/enums.ts create mode 100644 packages/provider-catalog/src/schemas/index.ts create mode 100644 packages/provider-catalog/src/schemas/model.ts create mode 100644 packages/provider-catalog/src/schemas/provider-models.ts create mode 100644 packages/provider-catalog/src/schemas/provider.ts create mode 100644 packages/provider-catalog/src/utils/importers/base/base-transformer.ts create mode 100644 packages/shared/data/api/schemas/models.ts create mode 100644 packages/shared/data/api/schemas/providers.ts create mode 100644 packages/shared/data/types/model.ts create mode 100644 packages/shared/data/types/provider.ts create mode 100644 packages/shared/data/utils/modelMerger.ts diff --git a/packages/provider-catalog/README.md b/packages/provider-catalog/README.md new file mode 100644 index 00000000000..ae6a307b1e8 --- /dev/null +++ b/packages/provider-catalog/README.md @@ -0,0 +1,196 @@ +# Cherry Studio Catalog + +Comprehensive AI model catalog with provider information, pricing, capabilities, and automatic synchronization. + +## Quick Start + +### 1. Setup API Keys + +Most providers require API keys to list models: + +```bash +# Copy example file +cp .env.example .env + +# Edit .env and add your API keys +# OPENAI_API_KEY=sk-... +# GROQ_API_KEY=gsk_... +# DEEPSEEK_API_KEY=... +``` + +### 2. Sync Provider Models + +**Option A: Sync all providers (batch)** +```bash +npm run sync:all +``` + +**Option B: Import authoritative sources** +```bash +# OpenRouter (360+ models) +npm run import:openrouter + +# AIHubMix (600+ models) +npm run import:aihubmix +``` + +**Option C: Use Web UI** +```bash +cd web +npm run dev +# Open http://localhost:3000/providers +# Click "Sync" button on any provider +``` + +## Features + +### Provider Management +- ✅ 51 providers configured with API endpoints +- ✅ Automatic model discovery via `models_api` +- ✅ Support for multiple API formats (OpenAI, Anthropic, Gemini) +- ✅ Custom transformers for aggregators + +### Model Catalog +- ✅ 1000+ models from various providers +- ✅ Comprehensive metadata (pricing, capabilities, limits) +- ✅ Input/output modalities +- ✅ Case-insensitive model IDs + +### Override System +- ✅ Provider-specific model overrides +- ✅ Tracks all provider-supported models (even if identical) +- ✅ Smart merging (preserves manual edits) +- ✅ Priority system (auto < 100 < manual) +- ✅ Automatic deduplication + +### Synchronization +- ✅ Batch sync all providers +- ✅ Per-provider sync via Web UI +- ✅ API key management +- ✅ Rate limiting and error handling + +## Data Files + +``` +data/ +├── models.json # Base model catalog (authoritative) +├── providers.json # Provider configurations with models_api +└── overrides.json # Provider-specific model overrides +``` + +## Scripts + +| Command | Description | +|---------|-------------| +| `npm run sync:all` | Sync all providers (except OpenRouter/AIHubMix) | +| `npm run import:openrouter` | Import models from OpenRouter | +| `npm run import:aihubmix` | Import models from AIHubMix | +| `npm run build` | Build TypeScript package | +| `npm run test` | Run test suite | + +## Architecture + +### Transformers + +Transform provider API responses to internal format: + +- **OpenAI-compatible** (default): Standard `/v1/models` format +- **OpenRouter**: Custom aggregator format with advanced capabilities +- **AIHubMix**: CSV-based format with type/feature parsing + +### Data Flow + +``` +Provider API → Transformer → ModelConfig[] + ↓ + Compare with models.json + ↓ + ┌──────────────────┴─────────────────┐ + ↓ ↓ + New Model Existing Model + ↓ ↓ + Add to models.json Generate Override + ↓ + Merge with existing + ↓ + Save to overrides.json +``` + +## Documentation + +- [Sync Guide](./docs/SYNC_GUIDE.md) - Detailed synchronization documentation +- [Schema Documentation](./src/schemas/README.md) - Data schemas and validation + +## Development + +### Prerequisites + +- Node.js 18+ +- Yarn 4+ + +### Setup + +```bash +# Install dependencies +yarn install + +# Run tests +npm run test + +# Build package +npm run build + +# Watch mode +npm run dev +``` + +### Adding a Provider + +1. Add provider config to `data/providers.json`: +```json +{ + "id": "new-provider", + "name": "New Provider", + "models_api": { + "endpoints": [ + { + "url": "https://api.provider.com/v1/models", + "endpoint_type": "CHAT_COMPLETIONS", + "format": "OPENAI" + } + ], + "enabled": true, + "update_frequency": "daily" + } +} +``` + +2. Add API key mapping in `scripts/sync-all-providers.ts`: +```typescript +const PROVIDER_ENV_MAP: Record = { + // ... + 'new-provider': 'NEW_PROVIDER_API_KEY' +} +``` + +3. Add to `.env.example`: +```bash +NEW_PROVIDER_API_KEY= +``` + +4. Run sync: +```bash +npm run sync:all +``` + +### Adding a Custom Transformer + +See [Transformers Guide](./docs/SYNC_GUIDE.md#transformers) for details. + +## License + +MIT + +## Contributing + +Contributions welcome! Please read the [Sync Guide](./docs/SYNC_GUIDE.md) first. diff --git a/packages/provider-catalog/data/models.pb b/packages/provider-catalog/data/models.pb new file mode 100644 index 0000000000000000000000000000000000000000..4a9bc11654ec5db486506a5a703810e9511f4d53 GIT binary patch literal 792407 zcmeFa3z#I=RVJu&FZ1FH)gBOMYX3fZ8V|zYe_&lB&-WZR-3w)l1#XQFTJg|TT#ux)L z7{eO#*!}-=?!9p%G9s(0)dtIp{8_4u$c(sgT4%W)H(K5G&c?!e_^NxV(HVXvKCR!0PV)=VJ*)He#YG(I zMw1iMC&mUw?id&-4-Cw14d1mj{P1XS?f##<PZj3cV8t##U$8><*fb2BPc&qb?^ z%lM)h3-8Q#7p=E1)LY&0sIhJzp^FFTv~iuadS|KKS*^7e>QSQ=VO;eN9;1VqZu3Ut z_G0>AF6b!cGp+Wq?Br*P)6;c#$ZS8njK;6+%H^$wQP#rop8l}76V9v~dX4yyNAgtF4Ii;ycY zF3;pGLVN1EVB})0)2Ox9!=d$hJ!l5Q>$T->xcK-O-_D(Qf7rpVZDYNQgM2sLv9H1N>|4P4SZ;SV!|R$g$_?C2 z7IbhpxY6#@`c(uM><0|}D1+wmFY8Bqvy4YB3)Q-xHW865#jpI{B zI>+}uIGevDbH!V!Tqxe;RQRU@!J!ND@uihU7yDRMK^WYBB6_1985`WQ|BitnZpUYb z?s|4e9{22cQ!H4rhxLs%es=XQ)!nX|Oi=$F%Qf_m+w{hqZF)`k++c7hwfN$TmGFAA_@atJ zkMttkigvaL_qNySty+T<{awqimML<%B?`Q)b7l7Tm6Va*l zsD^_VxhbRSMCpV8AQ@kmXoU9xE&|%Ab@@Pxe6IG|I&{p_7{z)!>TKXCTf|h*+Rb|O z`qJcNbl-!IC|ok&?aWu+``}dc)CRO}<7wczc55O!w*pW{kQ#M2*4Cgqy98HDjpdC_ zZ5|hL4~BdiuBqWm{kmaq22|W;@HgHsBHO=fLNlwF);jCbr&97 z0Md9g-)UdsnDwWLDvMNW8}KD#9UB}>lob#C++e)7t%9mZYrnb_H%vSPO*RHk^1kY^VHsbMkvYH2$?5V!kEQ_S>HiGS+ zDU#YThEi-aK8D@2%|;z7Ku7@*lHM0HcX5)WD91c$S^EZtR$OsAj0}cmXC{SFIJ#BoT2@zq|mu zGOEE~TC3L=Rw9g~)m`W`AQalIas3D-PeCKbmfU-zwOH?9TrJvTxQ-0P3=4v$(^!Bs z!8Kn==|Q=(+Q2r&CAxJ8g~jf~*ub7U28Yx}_K-q?m|!I*AI^`M+3t+2>ReHEeWCy> zOr-W3ZY{G5%f-BkSmrs#>yg)%zxi@7BX&`xKu_6IC(WLDjj=vtO5Dy#gi?|{8YjCm}T!?C-~c`FDwDcB_X{v&?JH<^RcJp|0hl;Ze>@Ug|J0>=tCD!P!tfy; zNc?5d?1|A&4G}tIPZ%fxe-Pvvq$WH;*46;@frNqO*x59Q12UB7d5vhP23fN?PI0!- zSfWV{?@bD4QM*LW<0euKGLK%0Xua0G02l?Y1$;Q>-mMk<0W`xqP+E#$q}$5#6{f`S zc+`aRCzji!1f@cg1PFC6Kq>-%Ex~ux1vJB#MbPr%L{bW>vNL1G)v3c{)rAJZNP!{c zQK3$1ix;hG!MwF}lxxrm+dZ|9uH?J)hc)UKYt3=`-3Z{^XE`rQMQ&>}1*aA6l`g=r z!(sDqd*JY@_|+!Hh6YBQWOx?q{#kVsq}F}%;j!z3k$qeH4sE?}FdQBr?8Y#fRp2ev)g^V|8zq@*Y9rc0z!Hf0! z7n^adl8BY?fXsFD=;^2etLm9O@-cq&H0J3Hrtmd9B88% zGt3{Rkr^<(43@&K1LQG+g1Z|#z8PO?cbbbwyPGY@5xRGc-w;nV9+mGtTDcn%3%ehC zf4M_R;I@zm6Wn_>aTc#)>#xA+EC{Uz-(vkD2e{B)UBk7r{`L8KYhh)z*13RzbRo3x z{P*HfjCpA+tMv$wbg_v!7P%J7q?x~A*0%gP!H8udBiM~_+PCTvw&8M%a2BWTd*oD9 zLx2H}a~&TnxwQXYEa2{i`uf5O?gyiDu|B`CybQ?r$SKG?3JedXM!1lCaj6N%?n-0L z=n4^$xL zE2psAD|u71O0wcohqfD^w^czoAw6>{{F?!|!|JPa_lMWs$3Ng`EYF-68y+}taOe); zdWk;$(a2p}cgen}$Uh&{C)N+Y!6)7$SLy6sZnxn@qP6ypf+xN?^~BejyCdo|AG|yj zU%C#UkB%+w`?%I9@_-|7S>zspm$zKt9^ok(ItH;4@N~Em!WYSVk5BRd-#%3q?3)AD zzTs4uV8@>@4)^K*sB`gpF>rn@|Ly-k!CYiKMY(qxwW_)m!>a-2hQT}s2m+GP2Ybcy{{8F!`X^O>yykZwcBLH;>ryg9RcYL+r>K7C0O}vEyLEM&O$)u!$L}E3k>@io+5-6@F-cFk;3L z9GP5hjS_wY?T8Y5-o{(K z-d=}2=0bdMu#+=XDMw%*i91{534(E%$eNWINo0+-qcyl3eMxM;B&KCtHw?qsfR__F zYOaVMMombVH4~1?WS#g7%TrPz7FJt~;M7`%U|ouw0SIde4+HCS1Cs}F1xpo|Fzg<6)t%XBw@x-;E!vkL!A}NL7>!IVp7uTNaw*xo+g*527O~~m zq(q@?sAvkgFeQ{EH|K_@r&xr2T8XWt-S;pQy@%_ZO`fmT_u4>zxzsu($b zXGV_qF)aH7t(^$(+p@wtOk~tiGlyC8v0R?6l&j?e!F_Lz`B=6SKE881G9~I~Fh#wh z9!GsqPqx&60DyleQSS}wYK!`rxFr0e-qiy4XK5;Y`<~#%ZjEnr=G$(54&HB6;W-{2 z{m{TU_}g>8YT%bB0pUz2>H=qz4lxQm^Rsc4*L2)>LDDe4^Tc_rn|yU1!Bl?J&;|6G zEBMBI9T-k99yk_YYLLEBnzTZSqF;lW6kk(0#}VsH*aiu0Q(yLwVt&=@uk`ggHGZC6 z|E&Ty_iZUbl0V5;`)zNd*n=-OvSCKKQIenvgZ2N~y6mu74v; zwSgFyeKQ(8bjZO+U+}|Upiu%WB%ba_yxZU*fzd@QV*pX&I2JHx$myW3!&8hwwxcGY zq=$_x4iY++@4gn;(%qP+8=C%o;4a)AVgsxSAfHWQmzDyOPGlb1m`WUDO#+i3mtU%b zQ$lAqg%IGRu&fOLHMo}UZ;d{Z$0byD^>8=a?KNCXpAX#IdEu6ThHswM#4t`GfKY=i zOHpP<0^+g08L_1idZp(dLQxPV7+}J}Z>;i!@LBM76oq=wHQ|#YkmC_YbU+||Hw>L0 z%neF?eR~ct)}qoG6}Q%ehFotgwlB%^co!E&jM(EKc@xzVVq)Cj7cj~q>)f$`mgov0ZG`lI13A0dzmEWKe(j3NGQ*!6~q@stXiAIRQjQ$ADbp zfIi=h4VoY}WYF;fV>=|+6z>I#ZgoungLg;+YDbr_*lf7Wy$YEySrG_8&DU3I7aLee ze11H+Xuk#eWN^%`O(;|0>8Y6o-G?QNrK_=*iLrg~gdQB)6I^%4FftQ_3%#{<7XXbi z>QwIio^QMEQz59S2|q+3cqrJr@0vqf&klscgNX6Ibm07fWt9~oDVz@({=(lVNLI*5 zMDz2EsDT62w`%t-H9D){Lhd>#LHG@&+@u71s;_wM8l`x_kH_4(1m@PAvO@2a*qS2^ z8Kb5!Wg%HsIiw<8H`_#T<_c(dw{lP1DZ8g&c)?5-hrdc4{zvm)LCl1sQW=w%ajI13 zn8q+$fX%-m2b)h@SwDWMKEHE(9^8=FQ+qY)7k=>C;1I|R$P0I_C!T8rZ~)d^1WcGa_L9V!MejEwiiO zACwSr7h)d{0!)B#nxS2=?&?q!&_hM%`0V!{kQnF`Vi_v)8R|h0NIx(lluAb%vu79`eGFSt zSU#T}QkD-jz=F3QJNu!_bdxb?^j!r?Amd5#ufQE$DE?p3n;1#AHay|wxLgoQM8ZO; zfN8C#`f7lAqX8cCHNe#o_`2b@5%~Q{q<+YLDl6Y_bFbp4nP7-UBQ{02Pa=Mh5$=;! zMC~g@!u@JFUcPT%aDy2VqBIb9cp;$j2&mcp!ojEvXYG5*8LpGTAn=0Xd16&j2Ta~0w84%m=u?PjC9g1zlnGLl6o zm*MajsR6JDX5<|y^u>;$ok6ko&~XvqRvtR2io}nxy$}V1{bT_lpOXa>z7b5SfRhE+ z;Y^+^IGB2?8Da2)1;XIVQr?i>g|S;{pwVIheK|?02qfp?LHnz|(%?{nNcM=n3tl+# zp_-KsugsAT)3Mo*5D?Roz36YIRGBKz%}o}dzpEu6e(GRwy;(}_wT-TbhwF`acmM|g z@}@^WGh+Wti3ZWRK-y5mF5A&V0J1qGu3^pY)OJr8f&h&3l85d*5)4)V?I9+G?GIR_ z#xph|ve`ZSfs>#i^Sj8Q0DwRU1GiXRBjGM_5kSX-^eiq008fp2%P<8dIw6AJSh>=v zF9Bq>J2)hmRf-!h0BSgS#40ktjTVTIiy$jAHJ*qLM7_jRUx7abzo^q2YK&Lr>u0Qe zGl3lr9YL5?{DR|XcR}D|t_eqgbaKo?2guQR1&EVYn6y@Jq3S_h*u@G*bw0ogf|yF^ zpv03W>YK(v={u#`x|rQ{6gU@SW&!6sQ1+0*ffQ@$v>#RK&Uan!_vW>ucR(M~{*> zg0W-COd!JtFQ$7`zUKZTiaT}}={npJPseR6iz7iUn*F1ZhZ8hwC{2oH=Tw6wz740r zZuT|U=i$#FXB}wvT2Jg`^w+x!^w%p=`YS89cB{RBWvemr;c^J1qN4J%2JNXYS?yKH zYOk?e?Zs6>&joZBCMpeXXQ0nO-S|T5uD|HfUDYt7yQ-rN z-1kOF(@9TjSt^BSJOESS+!B^+f~ThU0jwfFY1z${;-HrIwR((mN&g?%MDYGZHRWdbRgx8eyt`fj~p_K(`VMbofs>sAs5>1 z4jvu)AKo)TBC^z=6V^8vzDR%$Vgz`(gB%(O!)mO7A)_k;6YhdPUKA%#Kv0`#mGc@P)6Pwz#gu?u^=ys+!#S&D;T&ee_iA}>F*dUpA;fh5Spv&z+QiT zy3X_5^wD+iy%Jq_C4L)}s>#)LLRl#n@Do7Qz^T5vZrS*4p78xP;u`-4bl$`T-uf43 zy!ASyU`INxDo9AZ^CiqeTE|UKO_nCBvtM8x7xdV{;SV1QZghbJ$ZQuhzER^E2pT_# zNURMRzb8l?h9JLBvJccVZlugFf@+3N7nImZ!@=rd`XIv#PiMe0yAY( z(lJ}L&1qu_WhS1{GE(8@Mb1GsJa^9-XA^eYO(wH}kqN_;M&v`sb@sg1lCdij3MD-I zqC2~AvqMKh2A)219Hv%H>59?a4LZ}|V@HH!MNUM=*Xoz?`OKL|Vk{=T)uT)kjCl-C z$YN$1h(Tatbq$mtILWD}K?N0z!7pHvKrED+bjL)Q>gvjX31K@j&_PF=`sXxaY!C@$ zI4_B{PV+O;l6K6@g$=f{j$FhAyeR4!apt4q3oPBlpP;F~^wbN>RlI<_Tn=DM?G)Tj zqxy=H03DLbb0(SD;ySB60p!f%>Wmp&_jDb*RBHVtI?VV<)Q|ZHa`QA9&=ihwJA==j zI7$-7s2#isb{uBJL`bS$#N=GPgU~xdO?sj{F^PD)w_emTZy2EJDQwY@5CN;kpJ2^j z?lcB+B1g7WOFuDt`4VMWz+;P5*AcQsoppae{0Fo`s0UOH2}${(F1$*qKqnqlgIMGV zF*fsrmU$ws9ikvCC2RwE2EcB7u`9_1OufcRaEOC%S|pj3C|GSQKBzW3ib~Rpz*2%a zLHlRF@wF1bVKRyuwk8=xKO9z*SA+H+V$4P|vj+|#^(h=2qKz9EL5;QKN9;dYpz|{< zbo1j1B)pUIg+^`xhqWkmL9~8J0y({`L}-1S>Z|p8BgVm(QV74-g_{lGd+)?Rgx&yt zWol+>db%jaGoKT{e}j$f?mT!ep?-Q9O_Y_p_X)Gw!)Nvfhs-iUIZ~65bjZj#$6^}& z^P%h^TFD9&5CDQ$OIIGHkqhC6$cspHY`(&}63*oDHl=kyVjHg1xfi*Axl_G8oJc(* z9E_7VhPE8Dvow||G2zTi_yN_Tr3grJo01_4 zg5xM-OMyWjGIXYL^W5xls_?6%sF(2@ipCpTtzA~DQ^U({7RC}I63;H?HR=nYVlLqT z4NvP%RZ-Q?wbuux;O+*oKuvZMsB&atY=qHjLsA+t^-~j)k`i$ligH1!RD#Waro6i@ zAt^~Aib9IgcNYk}Omy0=RF`FR)yPTYS?30$ARpyan8ce#T5XLueJKgF!&$H1OVR?3 zrBpQsE^X#iCrdNsso5eK_B@Hje|MlaJYDS#Ps58hV!U`RN}c1!6K;`{H|CoPIUx*K zs(IYyy(lvfyU}{i6mN(qFp$G}^0oo=zR4p_J^|Yf=0kj%@IfA}MeuGOQ?$DY#|(o( z8fd_nz}E)@1yy$-gG5+bnJZYPwrIj9MQ%IVbk5o2xrWr22kAyvQbLmvFB^p+3PDRv zX${KQsEdq1Au=&4&61d*c!B86o98??PM7{i21{3>#|U-pyyO7A%H$EjDi-zMD#``wV9H1 ziB_=BB#&RC7g)TfAcXqvN^AI%$V$@_+6@W`Y63$`^31aCIyGATVWnJE-g0~didXDX z=v{_Ba~;wPoAdB3yA`tpQ=lcGqXhRmK(C8mmlcE`-o_#-mC%)^epbHXBsA!Bn9PE_ z4yd53#(6-#z!h*F{!@X9*@p9QC7v|Y7Af$gP0q#T+;m{589CJ#Q@;>BX}v%>Ct6hX zxDF^hhbhV^nYq$*X}Yk8$yMSy>YQ;kdISC*{k9?b5c31K zE+LszwX{rD%WAcSk_>FqOQ`hDsv8&l5N86ULZ`OYAmB@!DY^$?;C5;;!U~SnT1S;{ zIM__Q1NDxXu8G_*Mg?%^WJ{_Gh7ltOC&^i088yR*T|f;b`eh)7R3_0u!)<<{&b(Lk z0YDXa^_@i1)RyXqdT2JVsig4ShGBA+rIshT1LLrZa+qR8zWk6Dk%uP>Mz*d-?I}!x9CBD(IJ!xqp^^7zfS$%)Bg`Ht+GF0` zr(ABI*=sQc6W=VRID@}OziE(L4c(IPfik6rtq460hO(i0F-=>(!ZJdCO>}xv}|t4lqk~Xx{E%HUF7x`z3$NFS~~9gdbBw&>~FSRX><_N9-b0{yY=iJR&rkbcr#XdHnk8^ zv;J)7rP!fEJ3TQVJ+V|OWk!1qX=yP2eWeeiE_#vGlAYG3@gN~Ps&Ax>0>vW+3cO)y z%RG)>QXrUEGvk5Ca-MI4v&`x_OHzLfUqePS!h@LFl@0h`$Y!v`ZxTw73LFiV;@y%a z4Uf+0FPACQH+E=y_3)Kan4NyCRLR zxwa-Jb3BQ}Aql(7(wA4QL>|N5}QMu*{Uo>7^OYg#?BCE&W*KNJCE&>avJ+C z01-%aHlkq}0^?8sM+W{-?lcDC#iI@RmrrmNIDx&3d;m+9CrL1#-;9MU=!rfLT^QCp z2B^1m!U$uN2t2?yDf(~~s`F9?;p!RCsM4fZ64ynpnC5H(H=NwXn?{01Hi;sUM!0+@ zHsMG>7MfI`l{6a{>LzTcSraJsAj$nG7&aE2TE$gur3TAI5YQv=6&JyenoV*IB^n2R z^X5Vuq!)l8ec1Urt&No6#E)#8^Aeo)ZeGApY_7AV5z!PJG*Vo@pMRrRZfOD0XMIGJ zwLJc|!16$sLaz>@uE|3ARE&e>uH38P+a6XC1}`{QM{iusGefG|;UZXP`2~Bf7B2b~ z)5!}X2}eZYOq54|S6!QEO9Bx<6>VG^mIN3xCB+sT!7}Dop+^zBuFab8^4kqq?=UB$#myGl zSZOKFjO~$3^gL&&3X{L}&8u-Wue3JPFH)F{ zgnJmz-`nHI`1=BB^5T>m<;hJ`2D8e=)_O_SOosrt0k;o%RXyfI2m)=>T$;G!O1EQQy;0iRI< z`!qg}er(YDMDc=x9q>PL8$i7~pQE?Q1>A_U6*fp`bFsFWYzW05%*}v)rr$AJy9irJ z>cY@=vAK@g)P=Mie<+Jc`DT0rvPAeysFVc#@2A;N%bVK}eOUSpb|u7^kQ#B+XAerk zp5A`k-kiT=s2;>fp^x&j0wKsSZD9eOZ+5c5Nv10VzNryl!B{pLoPulg@T58HZOHfs{Q1*$hM~*H4er% z^f(xIS>=bqkIqHD=xw}2ridMyu~#G{$1Nn0M%Q`2xO&y>So z7zhrUZ(=oYuh=E|V5eR1I?wNuU*P-x8~b1K<5Q@3Y99*4tkd_}-TTV*62p>2G1E%; zcLTw-t@>r;`*CS^!kbP52`@eb-MSgQfybkhJU%uAtz$X>e8)Slzw7sYQ~N)t%1KdY znL3Jg$Gl7@kpECqZ*p;d$Ij~eB|nP2p#=pp1 z^4wf((ZlzaQ0{W_g#XOX{?N!>&%RC?KomW)?3(Noe_O#5LxOp4H^a~m-iZw|~LbmTTYk|}#F%H*#UXQp{qe%&75K_yk8Br*@wha3dK zP;_LMkR+`Isq=D5RQQ^dh}p@Bl2jf#bLR9T<56{TLJJYGnzMd!Zemuxa2$M6*m>>d z=5n*`iVZ!t^{=(6J(VQROGN?zMNUE+!)P`5EMnT@_u#G5m8f$lmMK!9K|=?1T>&mm zlAxu)l#rJ_c#=*dBKgr?BME-9n|(I;ow&>MgWkVUfaOM0%P#fuA%*;6aeFG0<-RRP za+_{r!(kN`l=Z>%x|L(b{Wv_ZPMded6w~Dj6Hd{v__bkny!{A z(Yp>GKiW75SpVw+#kg252*1L&gcZE{MdYPnCuBW0aYjEYF79eF1%KP`t)j z4{XL@dX|6iFAsn9xBvVv=xWn094zMg*l;*Fu&1+seiI%IntBxE-(Dcr;?(-uZY=pr zE+=JQ6B>#v1PIJh_yZ&8MSV+m;xN*tE-Dz-mBp%()e6Hu%?<_p10^>ClFevi6b zStAC0{OYwf%(?;1&jg36T`N%1NE_{iYfM*;ONIfj7KrdoAlDWHks^ffG4U6w(dj(( z;wXq&VW|cYE}h%hzr*!6mC9#Z`$Rx}@^60V&wu?4TXsM|h2QY(e|_gYTr!TG^P^lp zd%}SMq{|H9?R54$jc#TeTtS~HkWg7`ldp7QCV!N@ddoQzjezp{V*=bhAv?}fEb$E1?zJQ}kTlaLl-WJ=6VLU88` zv8&~=`kB4K%`lWMV3!gv#H5+HO%WEK8vWU!`;woj>6tM)leP+KbXq!|^u4%@c%Qhu z6Ti;JI=!}WeGwH1e8eFLLvrz&^erp8u*Bkp&57t79A?I@bv|rZ$lJgXo2d9klolgI z`bxkcNw@Q5hp`z=??-Gv&zP}w2Dq<w=Kag5+0o%G0poWp2J*f$#W zr-x9xF6e`TkwXGNR8xN#84FV&78wXg{}h27^7i5I!#C#un^fo|9U1urDLwB^>Vznr zgx~k(r|){-A1mV~Nw9NqI~LD%wG&w{_xv!|OlRnFtwG80R|UG~)hXRmyqK<%EFtGN z*Rm3$1E;Qfo2~C04qmDa(rV@MJl?h9BxrtgVt<96DOalVaqsETS3D%0{&4z4e7syf z5ufsZ?bYiSHk8&f{`Q|IA~4eZUwnD>Yk&GwO1i&W-VN`Yw1dR^`E)_K{yS6er~i0& z`;Lmh=yshVvm*V4SHAxz27?#-A~g}G`G>Ra<6Z7W6!1^)#-AwIjhW^QxkKFTTQ1P* z+wR006UDthnV1EyDfbh_4o!a#>ee3g3098UtfBF_o? zAbwer#-t`C;L`PTTWwrs4Hb595->)3mM`!{6HTFC8&mO3>dR^!-aeFFpPs3nz{Sz4 z$@C*KB^puOhUMl(OdkjN48jeE;bb&30Z#@(QbT=^XbF~}>XYltK{C%c8`h(Xd zA1pi)@}bt^r*mHV^MT(dOCA4KQmLwU=sOeo|av# zPM}T>=?! zp5K?jlxBc6NFS*S2S`?(6m4QD`v|=cln6lA4i3bqj2SOAF5`x-6@?HX(7B_*ghf`h zSgSQUU>u;DB!`AY$?z(|o>Dsm@yop04`<|_?u^dBxl}<2F)M}hZD1I)m|dMQgi-M< z_I0>d4dp{$;1_$V$u4011i zi}P(n%n{2ft`?^HO>j{FCrjNH23fgwv7nA{r2*@J_0pM~XY75qei- zoxi4Gmf1;&^ABO~7gCFpHH4A{$90k^ zxrwm+6_gJ`YQE=5VF?nG=k#T7`ZRhe|2I{@`r$9}AjBz(aSE%*kQTe@G)GAL-_4TKGQTk7A%MucS2W6f;!IE{Jl+a{v za_$RE_esU;*-tk7*}>o?23MEVpW@bWSajlQ!~dTufd99rjJ=}K>}napt}ZYDSjet` zF{`KY4S@e}AULRu&iaM;B3QTKO?RWz%Go+TK3V}sa$QH$t3(n&G71@#PqlY|4!B!Fg3%vMZHj z90ZRb3=2>QXCL2@luyO;B{w&{vQU>=rj`%?q&xd$^rS+T#V0u$yg3w^CdvU6-&iz_r6hC_PVix0LR`hIRFYhFL zF_~4rUuI|csQp8yTKRK$+^n%&otv7TDoQYZo+WMGkCIA+0}$4zEo0q9_H$`!UXG1^ za^M`ci|B_e34#zzehnYe5{Ry3x%$USQzvYiu4U}@h8KPDMi~HGn~_FH3$Gl2@nJ+~ z#q6MzD&yK-kpPO~HMV6$lP8fMRwaR#n)VKe1V9Z#Dic3mk>gLo1|(-K2hI}PUYP?a zJHNX^4gY^|AoWa8c27*tmS!r|@aNHWUSs4{5XL1kULAeU9`hGb4Q-!nczg+UCE%{$ z%NKnK`r}3iI=UWb8he-;LB6plAce{9q*s$LdM0st-ouO)*u{s>wC_2Rc70Ntx#STh z(khBTL~a>5_=rlm*6HYjF0Ip%t*XxhP2Pmf3f;yOTcO>w)?r7wJUV{?Q%5?bW>RaC zGM@`Xf| zgQxCBBRewfq}~guQ@}v@ z(E_76mw`~-t*MuJ(ah;W2)yr6_afG(xva5He$49@#&kok6%cs~O7rS!e z!bEK%8k0gmC*Vy)-Q+O}!WPQ&7(5N(QM-t;)YP$RbWxCDn*ppt6cHK2A!!PNB+|ne z7ot0j0PATGWG`ofM!1~T6-h1ww=4Y0K>d`7>gJ}ai!zZ?PW`q}<;;r1w&!Ps^(dds z+pjNE?F+j-Gnn&eQaWdB1^gvC<63bW& zPzM8v3gpPx-h)E}!`CJJv#mkIpp@;H!*hR~X0Pa(-BqQ0U%~Fq;CC;HS1KgxirLj> zhhL(gVi??GlSNf{<6DIHh6oYfEIsKLW|dMnBFUx$Zp(M z^ycFFlaxjo($IDa#nW!h?&L$d|Tz^xhMRH3)q|F42#Q!U()A5{)z$l zxIGSc@{t;t^;9mo2748m20bUf8(5S=$@VlR7JtBZly}KV_U-4mA-6 z%dosR=B3<@Gr|yXv2%rAknTQ1S<06_ECgw0?%b`O+kC=h>;MtX6$t$LuSX=cd(5p*ooYF=x$O+a*bK8U1-vh$|6{&kN?&E`W&cVz*Ezq>I@AwcC1lwQaHoL<*SXfvKuV z9*}l?zR=(%#>TExP4a+5Wg(`ZZqYJP_4O>CRWb`s-FpwdKB3Poaao3ZnXD!O-;~JN zj1Jc)mM3uO(!_L9CAnJ>&;kTg+7KRg;M)QLf3 z(r+UdH7PgwfNh!hsOsdgVc<)*GPgi(+fpTwKu9di1ZiSn{?=dr%k%GFC%8O+gBh8E z9L=m$2t<$YcNeIMEYrQ^u4Nl)Ek)WuDYk2RaC&<}p~ zvTbxkKJ`1wF~Sb`R}J4X6pSLSvawjVm0+h(rHTGoR)Y<1D?aC38AUz1lI9$F9hIl# zJxXOCY0k0l(3Wk>b-qsv<*y&;X^vcd%fSCp5Xda00+~B`7`*G}>HLR9AA( za>)#fPv{1-Mw`}%=QYfsF5Q4sl6 zsktv4{jN4w@!S%N6?M3mQ+e-|IaxO<^_sLiu6n5kT?uoKdhCy_-$Uxv*08wCHAMAk zRhv&q?$xz0eQghhtv%XUHAS<2N5O*4^qI*U+ioYUNO6JzU!*vBD*VJiu+I_{Li5)| z3H3MGKHa9ENj?$!@iA zWGtDrZIFpc^9y?qjUET1z|fw6v%m&hYXeX*a6=2pSh5D$DjY^oSqO2oe{llzp(IsQ z{h)s3($p?Bedl#fS<|$q;zv;?MYgPZ_R)4VuYD?*qD*P7dqgXk&H|qduOmk<98qo7 zKRGI2edYTQ|Ia^Y<`7>nZ%z<8UVhqL?|bp!1;B3*U_-?mv?!1&;P(3Xp(Da!K@h-888Q0r zeDv-r*vMG%D=#A%Ym!!#x6-odh4R$e=)su>^mE9L^_+G+6?3n&KodNHDWkYhZ zA550X*-|y^e|q#M2JhCo5g@lN#_%hepQ{fRmtts=?p|k-n?Qugn)d4xh|3a^%4h=> zNkFT_FHAn8Gge|P5}&XtB1Ie`@4`_+xuXykK&x5EQWH0jh>;9I1BWb0EKxa9T{=w_}CNP+BA;?7y#tKIC+faxq|9`wY3$=v62WgW1RMoJoaLv zhR>^5)a?kXSqsGrn3X&-L*F3V8pT1{V9!CU3ER4I&yh-b+pL{OCd&Dth}VHYWL z9yV04sj?VQQ>40q^3q)ukAOKvk^v?a>kEwXiz2^~`F z3164~0IQDmb?F>p*?KMv9W(4j@~Cf@!_%G`s)vq&ov0kD&BHT+dmavL5cb+sVCKl<(3T1;InsEKRqisdGV zrTu@d^1bgiQTtycgQwE+8-Y?(QM8k1XVif4$z)-0|B8ts^Ch61>*xS^Vs{U{%G+OL5;I6#@rYH!OC<@|I-uv()asnKY3R*qG+DAQZ`u|=a zZFBqpSdRJ2efPli1;>i$e1WVU&6?h{TXmjZ< z=Lc!8Tu0X)?sQ4&#D<+Wy)cT5GUe7A@vUqMif?C>N!>rwK;8x~bBwY-Dscb5Bn2aq z>DlGd&UgBgj8{;(%bvR3poUizknZ>bwo(0K1>D;}Y>LBDeI{4t(b2cr|f)-P=@+;c_gMP4OcbOIWm)rw)k>f4S>yB}{x)QNrGc z$;7ugyAbzM!9DGk!jH-QkKid9j~+Pru&#Wk5tuwJa$fCE<>yHcOR5ivp2V<(A~Zsz z`jM|XGfl6B%<6R(E^*-zQDDY+E65l+mXjbvR8OB=av|BI`DsA>vXl?;GJhU+&Nq|I zi##KkpvVkZZ!a%1Er7NrMaq+ItY18-z3D==pB(~QLSx-IgkMr8Abr$7^nQFO$oYAu zK$T_*1X3-tdhx0V9QE|%vV85S+^(FiRMiz3C`u6SABWs|0ju7IoPJKv#f&$Y$M>I^<6?w|BA)HgzD7}k|R2oItN_ZG)bsXR;9 zFzNtIS{JbwiQ-ODg^RD4zMc{g6by{)J`tCu9suHYXKi;p1F1c=tb@{vIY~z>i{Tq; zGSGnF-vlU&cllDpjB zXzC}3Q`amo%O|k!2EGb)fPfYO1~Zxp>wLD*H3Xt`4by_zIBsjCI$3ngBy~a3hKd_-Ea;qlH3*hBJOA;q$P zeT5f(hURoun9reTY;|V3AmVwoN<)8YUjSLI#C6bzm4a=87Q(0mvodtwK+nzGZ;XCy z_#Cx@q0-SxlJJue%aFtH?28uhWJqtFcc8lnJuEu3h#XU5387P|7S?V-mDnxD%}9w3 z_W)@s$D%jZUk!h^3soS^LlBmVav`#aYEB6rBc?Ml!&e=U4(3kWt?DG!UP5aq0i$74 zI7;N<^;Vi-(rROLb&*-ACx-H%ezX@&bcip{Yf<%fX?w;9j;l8qfoqSvdfJTCYv8>I zTSrR=px%XHEk}o^W+&!ukDmLc?~JNbw;w_BixFs2iE2A1o#dttZoupo(*)pZMMyy>YIUd`lQlDzUgCTAzB;jL-AmVuuy2wWdcEr#s}jn(jl%Su4Hha@=W z5Dj=L{Neq|3#dqG^6+{(P*88p79$`1so~5K^+xauiP?`-!JdN2T02Aqo~>sod}&?} zGLw~)VT@ykqvm;XDJeNgUk12i#GhUDF*~>jGRbO_E$O<@73iRi-$O)8ok_2b$Czjp zafw)f@1UqQ3|CAiC|w!?>F10A?_CBWUQ8Eg9VQuzZx5D?4fLhk52S#;NhEalH=VQL z1Q%Ul!@M@;c^ECG?u69SBETB)L=2m4yBPz{3rnC+CHi%;Os#nefuYo8V8@{q6XwXl zWn@i@&x6#*i*=wzHHL`bD`jIiWvnoP#g-Ic%Dd2!siGRHl{fgR+QV-!pqT~MaL7lU zJ|8~iBl!5!q>Aj-eWv!)qZrH12 zM%onadrv>`U`nhB7M!Y1yHes(DBPFWoFZgn&n5NnEs!&wAk&+N?@^i-&{1so5bJazWL2Ou%el;8tcsDrBl1349tO+_^kZD}v9 zL+ik@Z!N>GB8(kOcgBoR5z8jSN+n8rlTTpwS3pj_00l++!U%ugd@u~0dqG}4yN(ja z@{{o-T8j}MKlgg3i`XW3=Z-%Tn{Q!=@O&lFV2a+P3WeHIu(8;7-PRw79xjOl7wI6H z-IvUQl}%~5(=%^A{^Tdv*Bl7xhtWQ2{t)@m`Gt*tyZ{PkXnV=7%$K|Q!T#-%XVngf;^(!^+1kfUbLKGq9Gam0_+OYx-ui)+ymOGnzb%-b z9Ax2*Vwba%pGr=`s_}s3V0K0v!*}cnZdOo3{Mw0Y>?~x79pNqhsj1mXpdSDH=uZrs zgEL%^uYy`q*o+s(1+zt1h^yVzp*$%ewdlBjOCL`{A_9n`)gfe2PsGocd?}wqPksLh z4ofw10S$S;?T4*HaAK-~Uyz^+178~@{&D^r{_O(sHJ-wb+sx&zUN~`Ke!rB$s5gEh z0r3~09Umxn0%m1pK0K(pr!sjWK3+L7`i{NwS)EF{f&E|+)HJ;txT53$v0BvIcjjUWr|A5x2@5MMlCQ5+sn8!o*fN~v;Y?%8}@97jT>^Db4X+jVrw`U zXEyC(KufI<4)ltWSrn3B2f@8j!^{A`^@>8j7y(mywNgqFl>t6`y_nAI3zha|?#Ocf z)dI^oQ&;OLv79TSLgufQu-ndV?x*{EX<&~VBzSh0ND#mr>~HLu#V3q28V2;$Hl&Ya zVWKn%G={X9)LMn2_gORIT zI}d4degiF#81qbWOPr+G%xNw~S-(cC1B;DHN1FC%!-EEsPjt||EpkqJkrc*}5&2rI z)8uX=srU>gH<5&lfx}G*9U>8FbO$02)doALnxq#y8kyJ8S^@qyMt3zA7CBc4fQdgr z!$UTMB;_Pjp_&e+YeI+4Np};GCs2fWD~~_m6et8-g4L$xNcjc!JCp8OCBL45q?eOP za7n&q+?6_6Oe=xB1tFs*r58o>m?-#jThGkXZhvm;?V_~6)OA_`3KF>^dJV;(N#?rk zc(hQb@n7+Y|jyAKcy(pJ$0Q*MOeUKnCgE z*IbZ3?`xxP8P*&cG^jK|`uMc8T;o0$_hf-5)gX88*S3*DT5&+$bZT+7+h11nGPl6y z)hVqA(d-oza9CbJYB1nG`czv-qh~*;Sjmao8^;^0P{1z0ap$S??NU$+nk@F>156IKNECaTJAV8`r zF*rQNkjoIkQJ>Jdb|z)rNWfA3!19uSqniKwjrgDe)!{>1F1FsklHU&&6jngbW-q|L zvvs^HK}DxjfpxxJl)Y;p=mmwKTC(|0HlDl_rS}@`JB8|eH8`U?tS&uQz;M5n-1ff1 z3tpQQS>x$EH%J6S@5z%K8Btc9oh{8x&ld5Mo@X%VoAw7U1HGFa>rIcfbkF*JCr978 z=RWDUZZpGZmSsY_>WY%!jK_7c^dUl6i70sRa|7GmKuLpE=@RpCCvYFO*ebI-2wkla z45I2P*g%A)D!mpE&;_@J6&A=?Vonq7RoKyj5FuLxVG4`{rp0&Qa%gR63JI7@x{F(o zR`NXrG-?QGpu!Y#pf!N4jWG0X2Cag?O8O$kh3j@X7(vAvE@G_}mrBe_5eg$;T1RK) zg+NCy(F>B(rW|V*bNSLAk&Rumf;+wHHgq7k;Ll{bfw;0!!TQb=K5ShvgC`> zk4c=)R$6kw9VfS~;{%YkQx^(?sR)i`6=0(a+;lw9q%Wp*n~uQV_Ch3D(s8Vqhq7Y3Pym%da+Qfk5ROzD!39F%l#hA2{{ zOFsEGKZGFMuTzcg4TlGZhWBK$aKEV_Op)oNkUzNm7n&Q7BOos*l_96XKin7W*E(ki zM~BIuqi>c3+^Vfu@c>+1$4@0K*_}sKge}7KOW;6rW2iZs;zg>d54nW%;$E!Vt)kmo z#GbhB8VX$iABzIc>bnJ+l(K=CuiaY_B1>{^p>5%c+ z-CSs|fgNOf8|NTusxaf`O}TMF&~2h4AQS=oEP%hY?uFh`g{;N(D~AfGdDoaC2bz0( z0Q?~K3D|R9is)f}{QBfYT=Yz?qFbN&3%@XknV+kOF8ABD5Lq<1@T+@p;Z?$d51!kG z>=PFN!53$MppGXP&w{^~(YQc62{(0`_sA#j<)Y8bOixdLfjjMnzwF9`mi$Vfp(#X} zIyn0EraWjVlA4=&^+8Kt;QF9a<8fE@LCs(xxsq&m3Jyv|hQ&<0EGrtXxIk!L1-t>- zb|ivO@#I#(+f{wg)N8p~+i$yScrS$dpzq&jbAP8@?(g)Yqi-5E*-Iw(SAFr~XMm!F z|5|O6RH+-$KiNV`DHS3>HAR3*V6X9wNd7I*v(33RKpOO3h;g*tZJ;BUuhC5KOH=PI zAjT(kb?6+27NlDRrF!7BkPxm^F_JMD^8O;U?gH!a0&JVYPmLBlJLaau#3tWL$*O)i zJ6n7eY8JK!w}x0YEnHBY>*DEGYTaV{5>+@NvG^8pLF$)Lp^iFEgNZtqQsYwnFI0wr z^Kih^K`&DhQ($^NCV7lUPC8o_SJ@5%Ax&vY*1JLsV=&S1yGP@`=J7lZ*HFL^zQO(;Y2A& z-2zO^FDMN?DyRBJ{hlzI^o=P^dez)2{rO-!C>7*;c!F1DwdjkzgxqP-prtY7wqq`Ngu4j&iffiCC#ME^H({1t!r`p!n4GO$Gj^E-i&$tX&S{QVUMe-{;aK zTvLYw{xCZG(dQD5+O@{Q&LZF*BK|0<;^@Jy5?_GT+O1 zGjf=xDk+t7<>ZR>{-h+ouwR@90b<09vVwUa<NN3pqq9us!~( zCAlQrEN9KQ#%+M(Qb11gup#bhNX&5T9Dg)WEcN+~WxAq}WJhX`>BbId2LBd3ygWf$ zh?5QJda+TzG=bFSB$q8UjpI=j72(~?iysSS99%JQyW>KObZJ2mvGp3A-EBJzP~h7j>~_;}0|B^njW#h(nn1HG{hE#5qN z`jnPg5@x(~*8?}XvHtpvdGo58?G0<=5gbS{aJtV-wJVazIX#P;t=cN`A>l^^2_2IS z`OTZ8OJhg~qAQKD#cVWz{4U7xwzi&>rz9Scu=NVF8#@~?VR08-ROG~)l7`8;#m+>q zLn>F}(TyV(M!%(j_)8880(qO7U9`|a)CQww@i4Rno8R=FjP5%uDcwv9Y)Htpi*{lS z7XQJsx#Au*sZ#=L97tY$xY}xLV+{o-;Yszdonj!D@vo3f+Vwdgq0b8Z;qP>DLnd!n zpbi}ooE9y?%k5{zN|yy@6kcXE_W5IP5sjOKd?FY z#2)JaCjZ{SfdgJYvMsOv_Mbe&`1H@Io>8jiXYc`fW=a0}Me3Q6a1X7Iz30WH#Qk^K zxBUOG0Lx@(Od`SAI)c9s;1;MYFL&z8H3*z=|5FY4IF{pDBX~t{y(<}Z5`NyLr7+OK zRI@RfXBK+K7E+Z<^RbCqLp+Mupv&Er##)L{*Hb3R zDmMra?hLE*EOSORtL$Sjl~$mXo`wcj%BQD@HL$@;C|%I*MPU&(7}_<>&Q+~&o0$rv zM)pV$2v_#?Aim3DwV(;WqM_FeiO_5#%FmV%B-$+7NL0yG&YfyEC3_sGzm!ydR2f@< zktQW2tRGdRh6FEYfIv+yVViVln28fmXL%KZjulHb+g3Gu%yCFlDa5Zchza!!B3?ZS znIvcf87N6|!?Q_63dNfSe5xt4U%_V*^LT7H92^)yDZb`vXV24k-y3WJ_rU^DfXWkI z;8+(CcDgSDu7A4LplrY-vvx+SKl7x(PY63ES42$5lOhKoJ+dOgo_awPrg&x6s(F#K zYN{$0%9wz(SeTidEzgx^%R8sCd}aW6TdEA1Ff;Hr_Bl#iYOQjA*)XqTYp=C(_w#3( zuWuH`z*`31RbXah^@CI?F?ZZw%Ufpf>z2Lvc4^JBiM0Th0j0c{!?5HJ$uFa-uBy&6JGL(ro2rB5T- zkk+_Z+UNj|vac~>6;jffQqGDLqvu_SgVa&PjhFEgNm?}L9TgL(2w0zWP^~DXU_+B* zE15V50V`h6oAC!8XRRkCEJ9pc0qD!mA_g_h8Di=#O7S523f2uK!j_*houHz%4YVm) zNSHu!hk8essQ4)|fhYEcX%7|Cc2~BSZ0mRdz5rCyPowwfG_yan7Jxz5{-*)}c5@1V>1u~M$B(bFP}5|& zewwyFu9 zm}Ap3rTNeun9u{MCk*b|`uxE8z51Ju`R`>sQ4vuolTU4LX0E;aM`9neez1nG?4Zuu z#pW>-F+y%?>SLxY!p`5>Hx|6J45~}rZ1>McB7bgEO#8}y151)g1^^$>V3lJ?zRi3d z8}j=;@B#m5kFPKi=>j9 zUBTm^!EfwH|q`KG~L53w^ufF@ibC2ZKZnT5{>w(~a?vd)T${b#Sewxrvd3tKk4yvZ%$%?wu zXHx#;-sWz7AFm%o$^#yFK%j}D#fMWf;d28)=xWbgtXS;fA1i<#TLF2Z1C)YVPRFZz zgJ*wp!K#c>;v(lhePe=~T`F>SZ_3Zp^p9-oP%eL=R z{Wj#r1-K343*}>__0ap<-**(}ht3c4i=BO|ZG;R!kK(p_d%+9Jh>he9eIKE~S6iHv z3MsE{{2X6Bp=J2%fW%Ho;t9(DSlI);51Ox&cL<6zx9j`Bxhrv7b^NOkHCAv;2E-na(jc_KEMvP5&O}$ zm=xAQ*sv0}YOVG$6pRS-4yInvUD#Rs&hN9o?d)xlW>MqbdP~7$-I@}9d3V_D0}83H zP{hkd`Up0XZ#R_ip0WpaBmZ`|nKHNB?St*$&3y_G-k4A*PDYAT1(q`m&D^8Cj|jPV zF!lQ@!SSgJm;vneS(TaD7xTV7Na6UHZjcm?mm{Me(0Wfo)at}KDObp7zmc(bx3$Vc+sW}jlwycQPRg+{S_<9))L*Jhcs4!>R_u#us z##0CF?BofAou1e!~{QR zXQtke5-e-Nkc@Ca^{A5%4NlaiQUM*&8XT`ill7@g(HDd#HM12Bz%Pg|oohmSzDS^f zbR)z4mYOOUkTBN7FAow%b1?!hO;!Lq0D8^TwIZhZG028{gKj(SNYF`=JL8| z9t)LhjxO>xnvEst&tV^uFtbk`{A< zxPUMsOG+e;DNn?BBf53S9oU?Lqy*e(smXH7lK*KLQIxOvu?h`-=Z)Y^iwy{IDLvkz@jc-V7Q6Zuu#aWL9p zXdM|a5Nv0`@8Y;-Cnw683Qv!^_94~ML`6=XN!NzbTh2_IvnM+3OWpcOyb&|aeE-cZ z)gLlyMPIsF(o98#&X>k9$e;JJX)psTH7Oq|eZ8#G!ke%Z6sa~~Dj?|eq93j#w@Nt} z$0qSf8(SW)?0FPP0&M<+g3xBhrXjY}@@o^-z8kSvT=t=m6^qMKxqo0Y%P(6pvh`~- zvUR&M^H}xD8hk<--6)vXM;t$Y{zfk+)y%M8KB-zvKAW4IMbYJ*2Sc9;0wC|zc6_mH zd01x}XWH?jWy8bz1Z17}*$1)eDTEq#eP1&`)<9GR9ADX}wO1RKZh*i1sx;1)OJiJGk=ANd7P(e)?wG%m5j_Q7;rRd93mq?C%f=k$KI-hf zEbE-Uhi3~!XeK)8aXsH{%e=r!+fCSB7DCt%g*&}sR4vJ`u zr7c43LV|*E7wf=r`W;hE?P2qsvAsik_67rkcMK1ps7ZNnU}$#h9cUsONtJdrWnKx| zx2tZ$58DzaKmvbTfro1{<>A`FQ$T(9tZpW~3-P3xwA`#1D_2j?mj+^arHz ze59Y$)-oyM=|q1IJj_%l>3w2uzB4nLLf01fVRSYuEl)QJ>ZVi7t5zly@Myl%MqVrO z$xwU-miVe~kBMt&B0%Ztp*sc#24xK=y%*G5@hQ9)$1Y@MN7g}sm-L$j4vI|p+nc1_ zz94O}P{b6RjY0b@T&}^L20h>A=c2oWE;_`zrWMmRu85r{XY~@9`S9Y9Fw4aFBG?@w zBkj?TzA{yroC^Q^K=2a&48a7Lnk>s7I}**yxX3E%nT1c3Mf#&tCXHwGyZ0q$V{o<}Mw-J=j;%VLKMV3nMTbpk{$Us>S07SxwHl%z{Kux!nGfOR+t#ShE* z&?-{&G`ljVa&b^PMr~L5o`%qwy zgB@~HjVq{%7GBCv~`_D1Mx`#Mw@crTS zH(FXwob_7QSlxAwiB$0>uB*0{IP0w+NiuA@?IzQcF)-_0!?T-41ojyhX))5OhH10F z$^dH6a~r!-XG#5XOp11?M%}QKQ(7oZUL`M9BXr>H8b5(gOA}HKbw&&Zpn33{1?EAf zJRyH)NV+`&U&eD=j<3SwTSra6hS%QnbWRnbdN5T%Y}I^&$C49pAKttQG@CN3PA;QqZ|A? z7I24B$k+g~HF*$jWUNH4`_dT)SrpNTJi=;b+`@jaczOYa|*Unk+zQ zREt4OfLDcYPn#Y#HxT77wOO3w6sbdNs>yAw2pe8Vj*89^rQKQt(m*U#vOT4ehd9^h z>(aDFsL%Su*iaU5Zw-oBq-7NTP#3j+V4u}(twxCB&lN1>3{8#;xk3a=BE8Ia(Sq(H zg{KOn@Km3roZDLUE6n;n@%UJ?xyo)XrhmED*u|C&dxG^VFv8SFVmsd-!t>sK4`NEDQ+s}JZ2%*K$j zX2>Gp4gwVzwGJ4)X?BI)=RpL!6Cbtf1BY=5Lc`Lv2w#eVK%GCW>()>C$HiSm11V}x z$Z2L@#I_f!!=7`=c7!;q^9lyd9dRkd;8@%h?OoVAhED<>0pOy2h$vl{{ zO;>mmJJ+Te@5Qdk5%Aw%08=t;6MNoY-))f-mc@2qW;i`fSt6o6RcxTX%It$%`pEgp zHn!!GGci<@?MDgE40dBwkBmMT5bzV&ts%Ip zM=SUn58f~MGGr&{!XsS}$7~TeFi$p+LPrUZ3Y(&KnFSWI;e70ebHEjC1Vxu^xw9Nj zhg53CMnrQkZW%Oil=@;$B&b?6WjVYakNP;=W@M*9*QW@BzH`TO2GI|fGVCEW%n)@S z+l{F5$N7FzQ9yO$z8dxE6-R!=2bF)7wUn}o6M*u+6e!LNqgs|q3@UJ6g_&>77vsn< zWz7$xXUJ3iLyJ9`;9t~D274>>a2kyso|-pyVcOD~nx3qbO1tSV_@@KG$Ws@el9mR}e61NXr{r`JGyza%Rc%8j((V{4_mLaAQY{^YD zvZo4BRQMx-1ro@#FCqO(Avo9oNXCMPyfkxS^wYx*N#M9KCEaPuaivQX{W8mk zh%D)8S$raMDVS(wQ(n6HNr5Gn4$6qf97n#$u2{WD_QI<;n8h-=0EegvCdwK(*o10$ zjZ_2h*CN0W9lr08Q|RTD*d?%t-bmYn?Qcvh0Omsa|%_Wlprp!uXo>x$? zXw8?#(^8GmPMPKY$+b`n8g_ng$I!swu!hzoC03lSy2XB9h4QcbGXG)61h}vC%m;C1 zXSsntUQnAT&%i+u59TGx?kL)tMM#MZ;5@ruju&pqAj@Ryn|iox?4T$7K%HNy8OHy1 zl($|3-E$@girL1H+HLXZ$*fWJWr;BNC)~2$vTYbp|D#-)M6;HBFU1>kTxci70k{)5 zHx3%exbJXtMI6KLKo1;_G+u+#fSnoGuL`Ae&)33RI8PCs3DHS=JUaXp@Pr}aQ9pus zF(?lMvZj%|H0*IeOc+5i2Vurz#s|B#$8;qa<~>s?r6?ghC!@c2tVPRMG)xZc!0}Sv zGUoB{zYS_~OgXM5k=7eI8SrpN0KzWB>ht>%N>L?RfV6VJ)t?k-(M)bXr;1|%ymI5Y zf(Ooxn&5%abS{On_JZEAu+a?yvkeCy@9$RIal6a^fvoPQEeg<>y!%l<$bM^~hRZ6_VOe>qoF zMRTjHkHf93(PxLf)39rSTq-BxRPFqJz+#h`C7LH1@+p zuXJae34z2`<)X^;2;gMYueFih)b61F65ZNa%}pgOdJyqx8?~NDn#(0dq0evyyzsg& zxuhfOMo}6Jy%IucH6)qC%Ko>I1~Djk;IFE6T1P~+8~9_Dx8?!~>Hto-h@dOcj5UtM z)d^<^h%+F{camUxqFLYy!DOfzCdM5@<+Sc_xJe5+O_Ruv(Q=F#mV{gYR0%cn2w_Y{ zm1IT9Lp^}}yW^*@ssQ5FC2r5Tje&vq8dqy9RqYF>1ac_K_-%z8%GeuJ$dN?Mvb(Gg z4fKu_;D0$qm0j$%O}c&VqMcU+HSBTUk!k|HQ(2MKs?Rqdvi?!yy5%eaxRtY!a;x@C zN!qxp0+y94GZe@NZSC80n``xMIE26mT6`X|NAESB!SKkAF+liv)H&gZ824!}pkyHh z3>~QtoP2m}2*rgg`Rjde0YgtIoxw-nwf5TJle$`x57`9*i}0BOT~68uK0DVo`Hz%ToAQp%hl?_*dFxN#34K%(Q$V z)jNitir5`|1gnU#tA6z396k{hE)VmI8YpI)Y>{LQdgm!<&$GFMA9g@k9$FjjTD8>S zTZPYp4iaiP8{U8xCW=tUE8HCy)s&d_(bf*^$9SRy2X}d`v zyQ=o3ZXnP}I%z_ux&YfpeLc?NI)2iUfi0$tZsTshZ6{UVUa<6{Gj_ z;f1@6>+<|Cg5aROJdZc<{?`xjE$6kq*g&H7x%-%t3fCg5@axry+T=uS1H7OAYexW- zS%GVw?B#R>D03Pw2fxcFNo`=$oXbpj&KRj0Rm6z^0u*6HHV;68aSpZeID>*SSXdSy zDi2_oFp=*I$iTIL>yDgpx8t}VHL*Br2+$202ET65o5Fkya3c<-Ia&{&W##SStjU#F z)Pw7|N~5vdo+8(YTRr*&rlL!cY>eb)<(Vx8aUF%ewACBZrb5&bZSAqlLPKqf@ zvBL}OF;SLPX&V=s=%(`u2ornQW2G;?X3=lx*SrEH9PJ}5b>SHolW6>C&r%nCux!Bw z$*V6&K3^bg-x;xa3JQPhP|D-cyqg$`g#j=W9~Yo#VMbSb1T=nm2SYe;8(85YA^FnUUT9Qazuy>%(j?DA;6p&E1zbcLA|n&@vJSg7-BhqOF@<}6iI!r8;ugg z?YifM&i=0!Y(e;Pe-x1(T_H8y2rqu+vtOb#=!r_Jb1YjeelT-8i;Ut2}e+B4aM>)Pn|Qs z^grl7YSHM7Di)dvv}w4aHNgV|2xdD^;ZRG(D-XUBplB4&=yc?KLBpZ+aRhp$q5GUcm?$!GwX+m{H*-KLXk( zNV>%#oA9~bXVV=THvCzYF<&iFk&hPdw$DX&TY*vH9&*nuR-o6gi3$@t@_ux4p+aQa zDteG5I~9W5JK<3rohxqi`ho3gHE=>=uJ4fpjZG`o`j__X7i7=A?*Y;vFrfqqsT=q% zyeN>6e;V1dIR=?0#3s^5{KdXye{pwdH|J9mBhSG9VA>7-z;+RxH1i}>#df)9mkE={ z)M$BNj8E$*xF%@(vedYUu#_na6gDKpetcYn$Px`nQ4|M|H}TqtKxMSZh-DRU6q9Ad zSr&u3P+_x!M=ELn?wVi>IjYw-me+`vXC;}11!rM124AD%Pbs1pfD!bH_>}@|(OY)} z)ozNED1V92W4-p0U}S@-2wL|TVp>R@i$p!Xd|`2+1M)!1TFc(Ye44hhorkTvHiL3a zY8)2D*~uovsuZ4eo^|YC6k}wF`XqcrF3{<{)ZY)moNJp(!?? zs%9xFbApbe&ldRr?1`r9==q9Y$`u+@fin>1Knl_mLMiw%KsU{T?N`HC%Lsl68f+by zh4(}PV}I+_!x1FW8ib;fV6|zCF(TTCK>O_>* zhDc>{fmT=^7>OAs~y4qN7*wY(?|HZ+7_GrPagHVWJ z|9JS<3I2oM%L5~7&Wmi3Vc0=H@(bV_h`6I_G9T^21Lwn8=qdWI~+u6*yRyu$|oKLh+O1fUU(XRG`+V{P9wJls$1$TA~~ z%+N%!G}Uga-1yE+5ueBh>_lI~hh6VV^XyiYczQD>b|XD24JtBKd+4QdG(A!qJ^GKR zbnp6g(weKzRW)l`1nY}C^c4BvXx;)x^67n%b5fW&XCAD5yD_#UKUrw`v>K@g*IyZSRG?Eau-VtHLjxY4NKBAXt9NNRN@~&#``5!2)%@MEqH0t zgMhj!0m?bCI#&;+`1Wjbt^v&oGL-vm%@R#*pkl!gE9$B&L1-L%X3L=lVXQ(RAP@s@ zg&$R%*&-Wr(wkuIZfg#olgw@B+A=g1a^?m#kL1{sPYS^-12`5wXFKl(JT*{pEqv2* znwY+#dV3Rpk~|eY!RV1v(af$1jjTkMoW-uhFH8i2k_Dl9#TTGMxVSXM9t~AJv1=gl zk%a{ou(>K!=OkXi{o$2$E(wDSeo_N!_=Cm;B58<<1++GrJ)9#KCQF$x1&bC;4_Y;2 z%a4%{;4p)_8!fG*QA=|hkQxd{M3rgBD ziC@GcSw(Ti^1>5qxXdRfY!!i*0x=Hu6}X#CJbOIG8F*|6jal?WSN)Yq?Wx4e5UgZe zjmV}RlK3OEN4V)>hKdmov-ov84}ifHNy(&%EDEk zR}oTZEv`xI0k@?oy`*U|pjyGa#LuX#fXHscyay(R%yOiZ0zs?{5R}T8m@r*s11v21 zG?U6O)wzNrA*=HhM<%T=610$}j;SusYYH3vki?-sffEHA{rSWH{mXW|{(JBuf0mE| z`I$(ntH25;mV-|;W`*}icS%mG7*qz*x*iDf(ldx;Igqy{gw8@!UUuqPJEArLLelE! zX!1+_Xrb3`T|&bfN=tOZ#*4I;p1=!4U~Y9;1Ht$6ae4a>k4s-nsxhc9J}-}pzu6wR zipFkuROruA*x49jVO( zRV>;ThKAA&c)_AHc0=hytyN=i`N3eA(oEwjlIGNu{tBcSuS_>Vc-LV{3s-puI_v&Z zN>vV(Z$B&ufVl;#@Y85{PO*h3Ain@b-GVHYA5gk&le(Tg)T;!wTOiG z4)qY1umYsngt%?8&=2ug6|VeQ`O))OaKs*G07#f$U0vzCe0Z3zhOQ1Z7UZvCS^Cbf zJ~NPjj7%Pqx<9w|$;bTpfq-gl7q+|G`$OU0|8)1>|D}k^$9A-k@RPHN*)p+h55+7T z$_3kgoVvVNK*^=zZU4B*n*FmvnBCJV(c@m$6?S{<1fA43+u zIM;Vy0TO>*b$HeQOTMnZv{yDuwXDI$WUn0WDlGxT9uI6ov)@}SBA;G#8tff1^SQl4 zj%BDhm7BbBk(KgZZpHvbxwLC$G_frN9VhAg{1)Q-2&NTnA$zHC3nl*s;TE|w{UA{u z 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�#8Ez~Al=F6N2uvD51@0|R>kE?MP}uvu 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≶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 literal 0 HcmV?d00001 diff --git a/packages/provider-catalog/data/providers.pb b/packages/provider-catalog/data/providers.pb new file mode 100644 index 0000000000000000000000000000000000000000..b312ae0d0290148a56cc40ef99d51daccbc41fa0 GIT binary patch literal 8539 zcmbVRTW=gm6}Fwpcw7#vQ0yj7`8Jt2iIdIr#11TpwF|Nhi5I?aoG};99|eS%4+#?D5+I65h}pJ`aQdys&BQbgO&TKrBQQ33d)(-;ITZvyGbDD$M~VIRb!1wnWXLp>0g?k6F&`wL1L0 z1Dx0=yZZ;lJ$os!PdC{0F@w~$_`fFE#Wzi>-Q^d*)3gq|;+k$q$*YhYpQtw4s@2ez279Q$L@I65 z>{NPRF5QLFZ$&UjPSf$5p4+x+fLvzppUE4pnlb99Q#g}wHdfepUcwVK=+Bjn+vch1$UbabQTwtF8xJWG z^mTw&`$jNV zT?*HrI(i7_e4QbDtDZ`8xF-%K`95lVPQpVU*dY3t4_rl^q0X*>0LPVqjrAq!6e>io{a^p7?f( zW*gclD&3Cs$~Ecu(u?J-PO3{GaliirzGa)vY1iTTXPf)4UhE4Iw4}Uc$eWM$)53LH zR=N%rP^kene4E`ogCRzejQ*dp3+?7Ze&KNEp}2}4QZ$F?ccPWS4ugd>!QzWD_>UCu zCzW2f?IsW+y#TQ6y-n^oHiBHSv3RPbX^M%Qdeqv5&@TSAu|y}yeakJwiuYdx18+pS99B+Rxn!9QdLR589B zK<$N3GzG;kL==TKu}xem`G)1AZsT8gqT8T2>WJ&9F0~AjkVLW))UHObnDR~BzS5IU zP2$c?NI;n!J6cC2|2Gj2Qh_rum@Jz{Yt$qgQ8Dq~0&mr{YR#_c@Y$`+-R+$hoBJAX zqC?`%C@@;5Y+8W=bqzP7#1SS+i8_$}7q@2J>@+*D1j(njOuVa@j+hS#646&+>T8k1 z#*+=xwvZy$v{P4bb++O4Ra=&okwboI4Z1C#GtF$bJ1ISHu!rYN$x6zY>C>sL3I1c1 z-}$nAj5K!o2*l$q_8Ai7J~NgQP7lp?4L%t zW{0L(QQ1Fm-D7v7LmbC7R%JiOv8l+@G6#Hm55%515c45HqRKc-I9+>-GQ>>1m|}>- zf2I!lKFc*c_chNw!LOK8zi$UX%~D1)%)3Xtb%cRZ21gZE@ZE;=@jS~5&qGQqL=*`n zUCQJ3jaD!Qr*CjF$x?n~lmvkgIdg1zvhR8)9X|6oBE@1%k~mV)QC;%!BBRmKVD}p5{c=5aKu5X zsG~I7Q&09siWW;lnv`-EyNsW)A*GB9I>%Z96s|h3P}w(l{-th(2wHLzDLd_39c1X`!J1L?k#7Kda8W0})ns`nC>-Hm*k?*QbfLAOh zIwZ~;z!^WXzeX~8Y&wQsu3D2iiuTrFfvYSw>oH836i1bZjdr)=n>BoAd2;yTNKs-T zq9`~CFZ?cwOZOB7tS&A!rHjjuqGp#e@i$r_Fpd1-^;}^B?_MK!;E*G$R+XaRLArtB zl}|qdv2EhzG$cp>sWVo-5l+V^WYZ6`q->g&VoIcGiS7RZrq}E?8px23&6?zwc7jgv zF?yny?2*7$@D3AawBgfQVm&AcR==7{*6`!FvU>Up^wdC((Kk=|7x5-h!{>B-A$=13 zC2nT^A|a+zDXSc=X!P`gMyIL#QfCm!uN`CgP^lr&ZzOd13nZE~#E7bAcKGaeNaG4zrI+6m7a1zD-{{X8X9RC0S literal 0 HcmV?d00001 diff --git a/packages/provider-catalog/package.json b/packages/provider-catalog/package.json new file mode 100644 index 00000000000..d222c9dfbe5 --- /dev/null +++ b/packages/provider-catalog/package.json @@ -0,0 +1,68 @@ +{ + "name": "@cherrystudio/provider-catalog", + "version": "0.0.1-alpha.1", + "description": "Provider and Model Catalog", + "main": "dist/index.js", + "module": "dist/index.mjs", + "types": "dist/index.d.ts", + "packageManager": "pnpm@10.27.0", + "scripts": { + "proto:generate": "cd proto && buf generate .", + "proto:lint": "cd proto && buf lint .", + "proto:breaking": "cd proto && buf breaking . --against '../../../.git#subdir=packages/provider-catalog/proto,branch=main'", + "build": "pnpm proto:generate && tsdown", + "dev": "pnpm proto:generate && tsc -w", + "clean": "rm -rf dist", + "test": "vitest run", + "test:watch": "vitest", + "import:modelsdev": "tsx scripts/import-modelsdev.ts", + "import:aihubmix": "tsx scripts/import-aihubmix.ts", + "import:openrouter": "tsx scripts/import-openrouter.ts", + "import:all": "pnpm import:modelsdev && pnpm import:aihubmix && pnpm import:openrouter", + "sync:all": "tsx scripts/generate-providers.ts", + "generate:provider-models": "tsx scripts/generate-provider-models.ts", + "populate:reasoning": "tsx scripts/populate-reasoning-data.ts", + "migrate:json-to-pb": "tsx scripts/migrate-json-to-pb.ts", + "pipeline": "pnpm import:all && pnpm sync:all && pnpm generate:provider-models && pnpm populate:reasoning" + }, + "author": "Cherry Studio", + "license": "MIT", + "files": [ + "dist/**/*" + ], + "repository": { + "type": "git", + "url": "git+https://github.com/CherryHQ/cherry-studio.git" + }, + "bugs": { + "url": "https://github.com/CherryHQ/cherry-studio/issues" + }, + "homepage": "https://github.com/CherryHQ/cherry-studio#readme", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "react-native": "./dist/index.js", + "import": "./dist/index.mjs", + "require": "./dist/index.js", + "default": "./dist/index.js" + } + }, + "devDependencies": { + "@bufbuild/buf": "^1.66.0", + "@bufbuild/protoc-gen-es": "^2.11.0", + "@types/json-schema": "^7.0.15", + "@types/node": "^24.10.2", + "dotenv": "^17.2.3", + "tsdown": "^0.16.6", + "typescript": "^5.9.3", + "vitest": "^4.0.13", + "zod": "^4.1.12" + }, + "peerDependencies": {}, + "dependencies": { + "@bufbuild/protobuf": "^2.11.0", + "class-variance-authority": "^0.7.1", + "json-schema": "^0.4.0", + "lucide-react": "^0.563.0" + } +} diff --git a/packages/provider-catalog/src/catalog-reader.ts b/packages/provider-catalog/src/catalog-reader.ts new file mode 100644 index 00000000000..15046784103 --- /dev/null +++ b/packages/provider-catalog/src/catalog-reader.ts @@ -0,0 +1,35 @@ +/** + * Read-only catalog reader for .pb files. + * + * Reads protobuf catalog data and returns proto Message types directly. + * No JSON conversion — proto types are the single source of truth. + */ + +import { readFileSync } from 'node:fs' + +import { fromBinary } from '@bufbuild/protobuf' + +import type { ModelConfig } from './gen/v1/model_pb' +import { ModelCatalogSchema } from './gen/v1/model_pb' +import type { ProviderModelOverride } from './gen/v1/provider_models_pb' +import { ProviderModelCatalogSchema } from './gen/v1/provider_models_pb' +import type { ProviderConfig } from './gen/v1/provider_pb' +import { ProviderCatalogSchema } from './gen/v1/provider_pb' + +export function readModelCatalog(pbPath: string): { version: string; models: ModelConfig[] } { + const bytes = readFileSync(pbPath) + const catalog = fromBinary(ModelCatalogSchema, new Uint8Array(bytes)) + return { version: catalog.version, models: [...catalog.models] } +} + +export function readProviderCatalog(pbPath: string): { version: string; providers: ProviderConfig[] } { + const bytes = readFileSync(pbPath) + const catalog = fromBinary(ProviderCatalogSchema, new Uint8Array(bytes)) + return { version: catalog.version, providers: [...catalog.providers] } +} + +export function readProviderModelCatalog(pbPath: string): { version: string; overrides: ProviderModelOverride[] } { + const bytes = readFileSync(pbPath) + const catalog = fromBinary(ProviderModelCatalogSchema, new Uint8Array(bytes)) + return { version: catalog.version, overrides: [...catalog.overrides] } +} diff --git a/packages/provider-catalog/src/gen/v1/common_pb.ts b/packages/provider-catalog/src/gen/v1/common_pb.ts new file mode 100644 index 00000000000..1c74223cf96 --- /dev/null +++ b/packages/provider-catalog/src/gen/v1/common_pb.ts @@ -0,0 +1,456 @@ +// @generated by protoc-gen-es v2.11.0 with parameter "target=ts" +// @generated from file v1/common.proto (package catalog.v1, syntax proto3) +/* eslint-disable */ + +import type { GenEnum, GenFile, GenMessage } from '@bufbuild/protobuf/codegenv2' +import { enumDesc, fileDesc, messageDesc } from '@bufbuild/protobuf/codegenv2' +import type { Message } from '@bufbuild/protobuf' + +/** + * Describes the file v1/common.proto. + */ +export const file_v1_common: GenFile = + /*@__PURE__*/ + fileDesc( + 'Cg92MS9jb21tb24ucHJvdG8SCmNhdGFsb2cudjEiKAoMTnVtZXJpY1JhbmdlEgsKA21pbhgBIAEoARILCgNtYXgYAiABKAEibwoNUHJpY2VQZXJUb2tlbhIfChJwZXJfbWlsbGlvbl90b2tlbnMYASABKAFIAIgBARImCghjdXJyZW5jeRgCIAEoDjIULmNhdGFsb2cudjEuQ3VycmVuY3lCFQoTX3Blcl9taWxsaW9uX3Rva2VucyJuCghNZXRhZGF0YRIyCgdlbnRyaWVzGAEgAygLMiEuY2F0YWxvZy52MS5NZXRhZGF0YS5FbnRyaWVzRW50cnkaLgoMRW50cmllc0VudHJ5EgsKA2tleRgBIAEoCRINCgV2YWx1ZRgCIAEoCToCOAEq/AQKDEVuZHBvaW50VHlwZRIdChlFTkRQT0lOVF9UWVBFX1VOU1BFQ0lGSUVEEAASKQolRU5EUE9JTlRfVFlQRV9PUEVOQUlfQ0hBVF9DT01QTEVUSU9OUxABEikKJUVORFBPSU5UX1RZUEVfT1BFTkFJX1RFWFRfQ09NUExFVElPTlMQAhIkCiBFTkRQT0lOVF9UWVBFX0FOVEhST1BJQ19NRVNTQUdFUxADEiIKHkVORFBPSU5UX1RZUEVfT1BFTkFJX1JFU1BPTlNFUxAEEikKJUVORFBPSU5UX1RZUEVfR09PR0xFX0dFTkVSQVRFX0NPTlRFTlQQBRIdChlFTkRQT0lOVF9UWVBFX09MTEFNQV9DSEFUEAYSIQodRU5EUE9JTlRfVFlQRV9PTExBTUFfR0VORVJBVEUQBxIjCh9FTkRQT0lOVF9UWVBFX09QRU5BSV9FTUJFRERJTkdTEAgSHQoZRU5EUE9JTlRfVFlQRV9KSU5BX1JFUkFOSxAJEikKJUVORFBPSU5UX1RZUEVfT1BFTkFJX0lNQUdFX0dFTkVSQVRJT04QChIjCh9FTkRQT0lOVF9UWVBFX09QRU5BSV9JTUFHRV9FRElUEAsSLAooRU5EUE9JTlRfVFlQRV9PUEVOQUlfQVVESU9fVFJBTlNDUklQVElPThAMEioKJkVORFBPSU5UX1RZUEVfT1BFTkFJX0FVRElPX1RSQU5TTEFUSU9OEA0SJwojRU5EUE9JTlRfVFlQRV9PUEVOQUlfVEVYVF9UT19TUEVFQ0gQDhIpCiVFTkRQT0lOVF9UWVBFX09QRU5BSV9WSURFT19HRU5FUkFUSU9OEA8qnAUKD01vZGVsQ2FwYWJpbGl0eRIgChxNT0RFTF9DQVBBQklMSVRZX1VOU1BFQ0lGSUVEEAASIgoeTU9ERUxfQ0FQQUJJTElUWV9GVU5DVElPTl9DQUxMEAESHgoaTU9ERUxfQ0FQQUJJTElUWV9SRUFTT05JTkcQAhImCiJNT0RFTF9DQVBBQklMSVRZX0lNQUdFX1JFQ09HTklUSU9OEAMSJQohTU9ERUxfQ0FQQUJJTElUWV9JTUFHRV9HRU5FUkFUSU9OEAQSJgoiTU9ERUxfQ0FQQUJJTElUWV9BVURJT19SRUNPR05JVElPThAFEiUKIU1PREVMX0NBUEFCSUxJVFlfQVVESU9fR0VORVJBVElPThAGEh4KGk1PREVMX0NBUEFCSUxJVFlfRU1CRURESU5HEAcSGwoXTU9ERUxfQ0FQQUJJTElUWV9SRVJBTksQCBIlCiFNT0RFTF9DQVBBQklMSVRZX0FVRElPX1RSQU5TQ1JJUFQQCRImCiJNT0RFTF9DQVBBQklMSVRZX1ZJREVPX1JFQ09HTklUSU9OEAoSJQohTU9ERUxfQ0FQQUJJTElUWV9WSURFT19HRU5FUkFUSU9OEAsSJgoiTU9ERUxfQ0FQQUJJTElUWV9TVFJVQ1RVUkVEX09VVFBVVBAMEh8KG01PREVMX0NBUEFCSUxJVFlfRklMRV9JTlBVVBANEh8KG01PREVMX0NBUEFCSUxJVFlfV0VCX1NFQVJDSBAOEiMKH01PREVMX0NBUEFCSUxJVFlfQ09ERV9FWEVDVVRJT04QDxIgChxNT0RFTF9DQVBBQklMSVRZX0ZJTEVfU0VBUkNIEBASIQodTU9ERUxfQ0FQQUJJTElUWV9DT01QVVRFUl9VU0UQESqIAQoITW9kYWxpdHkSGAoUTU9EQUxJVFlfVU5TUEVDSUZJRUQQABIRCg1NT0RBTElUWV9URVhUEAESEgoOTU9EQUxJVFlfSU1BR0UQAhISCg5NT0RBTElUWV9BVURJTxADEhIKDk1PREFMSVRZX1ZJREVPEAQSEwoPTU9EQUxJVFlfVkVDVE9SEAUqSAoIQ3VycmVuY3kSGAoUQ1VSUkVOQ1lfVU5TUEVDSUZJRUQQABIQCgxDVVJSRU5DWV9VU0QQARIQCgxDVVJSRU5DWV9DTlkQAirzAQoPUmVhc29uaW5nRWZmb3J0EiAKHFJFQVNPTklOR19FRkZPUlRfVU5TUEVDSUZJRUQQABIZChVSRUFTT05JTkdfRUZGT1JUX05PTkUQARIcChhSRUFTT05JTkdfRUZGT1JUX01JTklNQUwQAhIYChRSRUFTT05JTkdfRUZGT1JUX0xPVxADEhsKF1JFQVNPTklOR19FRkZPUlRfTUVESVVNEAQSGQoVUkVBU09OSU5HX0VGRk9SVF9ISUdIEAUSGAoUUkVBU09OSU5HX0VGRk9SVF9NQVgQBhIZChVSRUFTT05JTkdfRUZGT1JUX0FVVE8QByqnAQoVT3BlbkFJUmVhc29uaW5nRWZmb3J0EicKI09QRU5BSV9SRUFTT05JTkdfRUZGT1JUX1VOU1BFQ0lGSUVEEAASHwobT1BFTkFJX1JFQVNPTklOR19FRkZPUlRfTE9XEAESIgoeT1BFTkFJX1JFQVNPTklOR19FRkZPUlRfTUVESVVNEAISIAocT1BFTkFJX1JFQVNPTklOR19FRkZPUlRfSElHSBADKtoBChhBbnRocm9waWNSZWFzb25pbmdFZmZvcnQSKgomQU5USFJPUElDX1JFQVNPTklOR19FRkZPUlRfVU5TUEVDSUZJRUQQABIiCh5BTlRIUk9QSUNfUkVBU09OSU5HX0VGRk9SVF9MT1cQARIlCiFBTlRIUk9QSUNfUkVBU09OSU5HX0VGRk9SVF9NRURJVU0QAhIjCh9BTlRIUk9QSUNfUkVBU09OSU5HX0VGRk9SVF9ISUdIEAMSIgoeQU5USFJPUElDX1JFQVNPTklOR19FRkZPUlRfTUFYEARiBnByb3RvMw' + ) + +/** + * @generated from message catalog.v1.NumericRange + */ +export type NumericRange = Message<'catalog.v1.NumericRange'> & { + /** + * @generated from field: double min = 1; + */ + min: number + + /** + * @generated from field: double max = 2; + */ + max: number +} + +/** + * Describes the message catalog.v1.NumericRange. + * Use `create(NumericRangeSchema)` to create a new message. + */ +export const NumericRangeSchema: GenMessage = /*@__PURE__*/ messageDesc(file_v1_common, 0) + +/** + * @generated from message catalog.v1.PricePerToken + */ +export type PricePerToken = Message<'catalog.v1.PricePerToken'> & { + /** + * nullable — absent means unknown + * + * @generated from field: optional double per_million_tokens = 1; + */ + perMillionTokens?: number + + /** + * @generated from field: catalog.v1.Currency currency = 2; + */ + currency: Currency +} + +/** + * Describes the message catalog.v1.PricePerToken. + * Use `create(PricePerTokenSchema)` to create a new message. + */ +export const PricePerTokenSchema: GenMessage = /*@__PURE__*/ messageDesc(file_v1_common, 1) + +/** + * Generic key-value metadata (replaces Record) + * + * @generated from message catalog.v1.Metadata + */ +export type Metadata = Message<'catalog.v1.Metadata'> & { + /** + * @generated from field: map entries = 1; + */ + entries: { [key: string]: string } +} + +/** + * Describes the message catalog.v1.Metadata. + * Use `create(MetadataSchema)` to create a new message. + */ +export const MetadataSchema: GenMessage = /*@__PURE__*/ messageDesc(file_v1_common, 2) + +/** + * @generated from enum catalog.v1.EndpointType + */ +export enum EndpointType { + /** + * @generated from enum value: ENDPOINT_TYPE_UNSPECIFIED = 0; + */ + UNSPECIFIED = 0, + + /** + * @generated from enum value: ENDPOINT_TYPE_OPENAI_CHAT_COMPLETIONS = 1; + */ + OPENAI_CHAT_COMPLETIONS = 1, + + /** + * @generated from enum value: ENDPOINT_TYPE_OPENAI_TEXT_COMPLETIONS = 2; + */ + OPENAI_TEXT_COMPLETIONS = 2, + + /** + * @generated from enum value: ENDPOINT_TYPE_ANTHROPIC_MESSAGES = 3; + */ + ANTHROPIC_MESSAGES = 3, + + /** + * @generated from enum value: ENDPOINT_TYPE_OPENAI_RESPONSES = 4; + */ + OPENAI_RESPONSES = 4, + + /** + * @generated from enum value: ENDPOINT_TYPE_GOOGLE_GENERATE_CONTENT = 5; + */ + GOOGLE_GENERATE_CONTENT = 5, + + /** + * @generated from enum value: ENDPOINT_TYPE_OLLAMA_CHAT = 6; + */ + OLLAMA_CHAT = 6, + + /** + * @generated from enum value: ENDPOINT_TYPE_OLLAMA_GENERATE = 7; + */ + OLLAMA_GENERATE = 7, + + /** + * @generated from enum value: ENDPOINT_TYPE_OPENAI_EMBEDDINGS = 8; + */ + OPENAI_EMBEDDINGS = 8, + + /** + * @generated from enum value: ENDPOINT_TYPE_JINA_RERANK = 9; + */ + JINA_RERANK = 9, + + /** + * @generated from enum value: ENDPOINT_TYPE_OPENAI_IMAGE_GENERATION = 10; + */ + OPENAI_IMAGE_GENERATION = 10, + + /** + * @generated from enum value: ENDPOINT_TYPE_OPENAI_IMAGE_EDIT = 11; + */ + OPENAI_IMAGE_EDIT = 11, + + /** + * @generated from enum value: ENDPOINT_TYPE_OPENAI_AUDIO_TRANSCRIPTION = 12; + */ + OPENAI_AUDIO_TRANSCRIPTION = 12, + + /** + * @generated from enum value: ENDPOINT_TYPE_OPENAI_AUDIO_TRANSLATION = 13; + */ + OPENAI_AUDIO_TRANSLATION = 13, + + /** + * @generated from enum value: ENDPOINT_TYPE_OPENAI_TEXT_TO_SPEECH = 14; + */ + OPENAI_TEXT_TO_SPEECH = 14, + + /** + * @generated from enum value: ENDPOINT_TYPE_OPENAI_VIDEO_GENERATION = 15; + */ + OPENAI_VIDEO_GENERATION = 15 +} + +/** + * Describes the enum catalog.v1.EndpointType. + */ +export const EndpointTypeSchema: GenEnum = /*@__PURE__*/ enumDesc(file_v1_common, 0) + +/** + * @generated from enum catalog.v1.ModelCapability + */ +export enum ModelCapability { + /** + * @generated from enum value: MODEL_CAPABILITY_UNSPECIFIED = 0; + */ + UNSPECIFIED = 0, + + /** + * @generated from enum value: MODEL_CAPABILITY_FUNCTION_CALL = 1; + */ + FUNCTION_CALL = 1, + + /** + * @generated from enum value: MODEL_CAPABILITY_REASONING = 2; + */ + REASONING = 2, + + /** + * @generated from enum value: MODEL_CAPABILITY_IMAGE_RECOGNITION = 3; + */ + IMAGE_RECOGNITION = 3, + + /** + * @generated from enum value: MODEL_CAPABILITY_IMAGE_GENERATION = 4; + */ + IMAGE_GENERATION = 4, + + /** + * @generated from enum value: MODEL_CAPABILITY_AUDIO_RECOGNITION = 5; + */ + AUDIO_RECOGNITION = 5, + + /** + * @generated from enum value: MODEL_CAPABILITY_AUDIO_GENERATION = 6; + */ + AUDIO_GENERATION = 6, + + /** + * @generated from enum value: MODEL_CAPABILITY_EMBEDDING = 7; + */ + EMBEDDING = 7, + + /** + * @generated from enum value: MODEL_CAPABILITY_RERANK = 8; + */ + RERANK = 8, + + /** + * @generated from enum value: MODEL_CAPABILITY_AUDIO_TRANSCRIPT = 9; + */ + AUDIO_TRANSCRIPT = 9, + + /** + * @generated from enum value: MODEL_CAPABILITY_VIDEO_RECOGNITION = 10; + */ + VIDEO_RECOGNITION = 10, + + /** + * @generated from enum value: MODEL_CAPABILITY_VIDEO_GENERATION = 11; + */ + VIDEO_GENERATION = 11, + + /** + * @generated from enum value: MODEL_CAPABILITY_STRUCTURED_OUTPUT = 12; + */ + STRUCTURED_OUTPUT = 12, + + /** + * @generated from enum value: MODEL_CAPABILITY_FILE_INPUT = 13; + */ + FILE_INPUT = 13, + + /** + * @generated from enum value: MODEL_CAPABILITY_WEB_SEARCH = 14; + */ + WEB_SEARCH = 14, + + /** + * @generated from enum value: MODEL_CAPABILITY_CODE_EXECUTION = 15; + */ + CODE_EXECUTION = 15, + + /** + * @generated from enum value: MODEL_CAPABILITY_FILE_SEARCH = 16; + */ + FILE_SEARCH = 16, + + /** + * @generated from enum value: MODEL_CAPABILITY_COMPUTER_USE = 17; + */ + COMPUTER_USE = 17 +} + +/** + * Describes the enum catalog.v1.ModelCapability. + */ +export const ModelCapabilitySchema: GenEnum = /*@__PURE__*/ enumDesc(file_v1_common, 1) + +/** + * @generated from enum catalog.v1.Modality + */ +export enum Modality { + /** + * @generated from enum value: MODALITY_UNSPECIFIED = 0; + */ + UNSPECIFIED = 0, + + /** + * @generated from enum value: MODALITY_TEXT = 1; + */ + TEXT = 1, + + /** + * @generated from enum value: MODALITY_IMAGE = 2; + */ + IMAGE = 2, + + /** + * @generated from enum value: MODALITY_AUDIO = 3; + */ + AUDIO = 3, + + /** + * @generated from enum value: MODALITY_VIDEO = 4; + */ + VIDEO = 4, + + /** + * @generated from enum value: MODALITY_VECTOR = 5; + */ + VECTOR = 5 +} + +/** + * Describes the enum catalog.v1.Modality. + */ +export const ModalitySchema: GenEnum = /*@__PURE__*/ enumDesc(file_v1_common, 2) + +/** + * @generated from enum catalog.v1.Currency + */ +export enum Currency { + /** + * defaults to USD + * + * @generated from enum value: CURRENCY_UNSPECIFIED = 0; + */ + UNSPECIFIED = 0, + + /** + * @generated from enum value: CURRENCY_USD = 1; + */ + USD = 1, + + /** + * @generated from enum value: CURRENCY_CNY = 2; + */ + CNY = 2 +} + +/** + * Describes the enum catalog.v1.Currency. + */ +export const CurrencySchema: GenEnum = /*@__PURE__*/ enumDesc(file_v1_common, 3) + +/** + * Shared reasoning effort levels — superset of all providers' actual API values. + * Used in ReasoningCommon.supported_efforts to describe catalog-level capabilities. + * Per-provider params use their own specific effort enums below. + * + * @generated from enum catalog.v1.ReasoningEffort + */ +export enum ReasoningEffort { + /** + * @generated from enum value: REASONING_EFFORT_UNSPECIFIED = 0; + */ + UNSPECIFIED = 0, + + /** + * @generated from enum value: REASONING_EFFORT_NONE = 1; + */ + NONE = 1, + + /** + * @generated from enum value: REASONING_EFFORT_MINIMAL = 2; + */ + MINIMAL = 2, + + /** + * @generated from enum value: REASONING_EFFORT_LOW = 3; + */ + LOW = 3, + + /** + * @generated from enum value: REASONING_EFFORT_MEDIUM = 4; + */ + MEDIUM = 4, + + /** + * @generated from enum value: REASONING_EFFORT_HIGH = 5; + */ + HIGH = 5, + + /** + * @generated from enum value: REASONING_EFFORT_MAX = 6; + */ + MAX = 6, + + /** + * @generated from enum value: REASONING_EFFORT_AUTO = 7; + */ + AUTO = 7 +} + +/** + * Describes the enum catalog.v1.ReasoningEffort. + */ +export const ReasoningEffortSchema: GenEnum = /*@__PURE__*/ enumDesc(file_v1_common, 4) + +/** + * @generated from enum catalog.v1.OpenAIReasoningEffort + */ +export enum OpenAIReasoningEffort { + /** + * @generated from enum value: OPENAI_REASONING_EFFORT_UNSPECIFIED = 0; + */ + OPENAI_REASONING_EFFORT_UNSPECIFIED = 0, + + /** + * @generated from enum value: OPENAI_REASONING_EFFORT_LOW = 1; + */ + OPENAI_REASONING_EFFORT_LOW = 1, + + /** + * @generated from enum value: OPENAI_REASONING_EFFORT_MEDIUM = 2; + */ + OPENAI_REASONING_EFFORT_MEDIUM = 2, + + /** + * @generated from enum value: OPENAI_REASONING_EFFORT_HIGH = 3; + */ + OPENAI_REASONING_EFFORT_HIGH = 3 +} + +/** + * Describes the enum catalog.v1.OpenAIReasoningEffort. + */ +export const OpenAIReasoningEffortSchema: GenEnum = /*@__PURE__*/ enumDesc(file_v1_common, 5) + +/** + * @generated from enum catalog.v1.AnthropicReasoningEffort + */ +export enum AnthropicReasoningEffort { + /** + * @generated from enum value: ANTHROPIC_REASONING_EFFORT_UNSPECIFIED = 0; + */ + UNSPECIFIED = 0, + + /** + * @generated from enum value: ANTHROPIC_REASONING_EFFORT_LOW = 1; + */ + LOW = 1, + + /** + * @generated from enum value: ANTHROPIC_REASONING_EFFORT_MEDIUM = 2; + */ + MEDIUM = 2, + + /** + * @generated from enum value: ANTHROPIC_REASONING_EFFORT_HIGH = 3; + */ + HIGH = 3, + + /** + * @generated from enum value: ANTHROPIC_REASONING_EFFORT_MAX = 4; + */ + MAX = 4 +} + +/** + * Describes the enum catalog.v1.AnthropicReasoningEffort. + */ +export const AnthropicReasoningEffortSchema: GenEnum = + /*@__PURE__*/ + enumDesc(file_v1_common, 6) diff --git a/packages/provider-catalog/src/gen/v1/model_pb.ts b/packages/provider-catalog/src/gen/v1/model_pb.ts new file mode 100644 index 00000000000..271e7d11f5d --- /dev/null +++ b/packages/provider-catalog/src/gen/v1/model_pb.ts @@ -0,0 +1,387 @@ +// @generated by protoc-gen-es v2.11.0 with parameter "target=ts" +// @generated from file v1/model.proto (package catalog.v1, syntax proto3) +/* eslint-disable */ + +import type { GenEnum, GenFile, GenMessage } from '@bufbuild/protobuf/codegenv2' +import { enumDesc, fileDesc, messageDesc } from '@bufbuild/protobuf/codegenv2' +import type { + Currency, + Metadata, + Modality, + ModelCapability, + NumericRange, + PricePerToken, + ReasoningEffort +} from './common_pb' +import { file_v1_common } from './common_pb' +import type { Message } from '@bufbuild/protobuf' + +/** + * Describes the file v1/model.proto. + */ +export const file_v1_model: GenFile = + /*@__PURE__*/ + fileDesc( + 'Cg52MS9tb2RlbC5wcm90bxIKY2F0YWxvZy52MSJrChNUaGlua2luZ1Rva2VuTGltaXRzEhAKA21pbhgBIAEoDUgAiAEBEhAKA21heBgCIAEoDUgBiAEBEhQKB2RlZmF1bHQYAyABKA1IAogBAUIGCgRfbWluQgYKBF9tYXhCCgoIX2RlZmF1bHQi0wEKEFJlYXNvbmluZ1N1cHBvcnQSQwoVdGhpbmtpbmdfdG9rZW5fbGltaXRzGAEgASgLMh8uY2F0YWxvZy52MS5UaGlua2luZ1Rva2VuTGltaXRzSACIAQESNgoRc3VwcG9ydGVkX2VmZm9ydHMYAiADKA4yGy5jYXRhbG9nLnYxLlJlYXNvbmluZ0VmZm9ydBIYCgtpbnRlcmxlYXZlZBgDIAEoCEgBiAEBQhgKFl90aGlua2luZ190b2tlbl9saW1pdHNCDgoMX2ludGVybGVhdmVkImMKFlJhbmdlZFBhcmFtZXRlclN1cHBvcnQSEQoJc3VwcG9ydGVkGAEgASgIEiwKBXJhbmdlGAIgASgLMhguY2F0YWxvZy52MS5OdW1lcmljUmFuZ2VIAIgBAUIICgZfcmFuZ2Ui1gMKEFBhcmFtZXRlclN1cHBvcnQSPAoLdGVtcGVyYXR1cmUYASABKAsyIi5jYXRhbG9nLnYxLlJhbmdlZFBhcmFtZXRlclN1cHBvcnRIAIgBARI2CgV0b3BfcBgCIAEoCzIiLmNhdGFsb2cudjEuUmFuZ2VkUGFyYW1ldGVyU3VwcG9ydEgBiAEBEjYKBXRvcF9rGAMgASgLMiIuY2F0YWxvZy52MS5SYW5nZWRQYXJhbWV0ZXJTdXBwb3J0SAKIAQESHgoRZnJlcXVlbmN5X3BlbmFsdHkYBCABKAhIA4gBARIdChBwcmVzZW5jZV9wZW5hbHR5GAUgASgISASIAQESFwoKbWF4X3Rva2VucxgGIAEoCEgFiAEBEhsKDnN0b3Bfc2VxdWVuY2VzGAcgASgISAaIAQESGwoOc3lzdGVtX21lc3NhZ2UYCCABKAhIB4gBAUIOCgxfdGVtcGVyYXR1cmVCCAoGX3RvcF9wQggKBl90b3Bfa0IUChJfZnJlcXVlbmN5X3BlbmFsdHlCEwoRX3ByZXNlbmNlX3BlbmFsdHlCDQoLX21heF90b2tlbnNCEQoPX3N0b3Bfc2VxdWVuY2VzQhEKD19zeXN0ZW1fbWVzc2FnZSJtCgpJbWFnZVByaWNlEg0KBXByaWNlGAEgASgBEiYKCGN1cnJlbmN5GAIgASgOMhQuY2F0YWxvZy52MS5DdXJyZW5jeRIoCgR1bml0GAMgASgOMhouY2F0YWxvZy52MS5JbWFnZVByaWNlVW5pdCJECgtNaW51dGVQcmljZRINCgVwcmljZRgBIAEoARImCghjdXJyZW5jeRgCIAEoDjIULmNhdGFsb2cudjEuQ3VycmVuY3ki6gIKDE1vZGVsUHJpY2luZxIoCgVpbnB1dBgBIAEoCzIZLmNhdGFsb2cudjEuUHJpY2VQZXJUb2tlbhIpCgZvdXRwdXQYAiABKAsyGS5jYXRhbG9nLnYxLlByaWNlUGVyVG9rZW4SMgoKY2FjaGVfcmVhZBgDIAEoCzIZLmNhdGFsb2cudjEuUHJpY2VQZXJUb2tlbkgAiAEBEjMKC2NhY2hlX3dyaXRlGAQgASgLMhkuY2F0YWxvZy52MS5QcmljZVBlclRva2VuSAGIAQESLgoJcGVyX2ltYWdlGAUgASgLMhYuY2F0YWxvZy52MS5JbWFnZVByaWNlSAKIAQESMAoKcGVyX21pbnV0ZRgGIAEoCzIXLmNhdGFsb2cudjEuTWludXRlUHJpY2VIA4gBAUINCgtfY2FjaGVfcmVhZEIOCgxfY2FjaGVfd3JpdGVCDAoKX3Blcl9pbWFnZUINCgtfcGVyX21pbnV0ZSKaBgoLTW9kZWxDb25maWcSCgoCaWQYASABKAkSEQoEbmFtZRgCIAEoCUgAiAEBEhgKC2Rlc2NyaXB0aW9uGAMgASgJSAGIAQESMQoMY2FwYWJpbGl0aWVzGAQgAygOMhsuY2F0YWxvZy52MS5Nb2RlbENhcGFiaWxpdHkSLgoQaW5wdXRfbW9kYWxpdGllcxgFIAMoDjIULmNhdGFsb2cudjEuTW9kYWxpdHkSLwoRb3V0cHV0X21vZGFsaXRpZXMYBiADKA4yFC5jYXRhbG9nLnYxLk1vZGFsaXR5EhsKDmNvbnRleHRfd2luZG93GAcgASgNSAKIAQESHgoRbWF4X291dHB1dF90b2tlbnMYCCABKA1IA4gBARIdChBtYXhfaW5wdXRfdG9rZW5zGAkgASgNSASIAQESLgoHcHJpY2luZxgKIAEoCzIYLmNhdGFsb2cudjEuTW9kZWxQcmljaW5nSAWIAQESNAoJcmVhc29uaW5nGAsgASgLMhwuY2F0YWxvZy52MS5SZWFzb25pbmdTdXBwb3J0SAaIAQESPAoRcGFyYW1ldGVyX3N1cHBvcnQYDCABKAsyHC5jYXRhbG9nLnYxLlBhcmFtZXRlclN1cHBvcnRIB4gBARITCgZmYW1pbHkYDSABKAlICIgBARIVCghvd25lZF9ieRgOIAEoCUgJiAEBEhkKDG9wZW5fd2VpZ2h0cxgPIAEoCEgKiAEBEg0KBWFsaWFzGBAgAygJEisKCG1ldGFkYXRhGBEgASgLMhQuY2F0YWxvZy52MS5NZXRhZGF0YUgLiAEBQgcKBV9uYW1lQg4KDF9kZXNjcmlwdGlvbkIRCg9fY29udGV4dF93aW5kb3dCFAoSX21heF9vdXRwdXRfdG9rZW5zQhMKEV9tYXhfaW5wdXRfdG9rZW5zQgoKCF9wcmljaW5nQgwKCl9yZWFzb25pbmdCFAoSX3BhcmFtZXRlcl9zdXBwb3J0QgkKB19mYW1pbHlCCwoJX293bmVkX2J5Qg8KDV9vcGVuX3dlaWdodHNCCwoJX21ldGFkYXRhIkgKDE1vZGVsQ2F0YWxvZxIPCgd2ZXJzaW9uGAEgASgJEicKBm1vZGVscxgCIAMoCzIXLmNhdGFsb2cudjEuTW9kZWxDb25maWcqagoOSW1hZ2VQcmljZVVuaXQSIAocSU1BR0VfUFJJQ0VfVU5JVF9VTlNQRUNJRklFRBAAEhoKFklNQUdFX1BSSUNFX1VOSVRfSU1BR0UQARIaChZJTUFHRV9QUklDRV9VTklUX1BJWEVMEAJiBnByb3RvMw', + [file_v1_common] + ) + +/** + * @generated from message catalog.v1.ThinkingTokenLimits + */ +export type ThinkingTokenLimits = Message<'catalog.v1.ThinkingTokenLimits'> & { + /** + * @generated from field: optional uint32 min = 1; + */ + min?: number + + /** + * @generated from field: optional uint32 max = 2; + */ + max?: number + + /** + * @generated from field: optional uint32 default = 3; + */ + default?: number +} + +/** + * Describes the message catalog.v1.ThinkingTokenLimits. + * Use `create(ThinkingTokenLimitsSchema)` to create a new message. + */ +export const ThinkingTokenLimitsSchema: GenMessage = /*@__PURE__*/ messageDesc(file_v1_model, 0) + +/** + * Model-level reasoning capabilities + * + * @generated from message catalog.v1.ReasoningSupport + */ +export type ReasoningSupport = Message<'catalog.v1.ReasoningSupport'> & { + /** + * @generated from field: optional catalog.v1.ThinkingTokenLimits thinking_token_limits = 1; + */ + thinkingTokenLimits?: ThinkingTokenLimits + + /** + * @generated from field: repeated catalog.v1.ReasoningEffort supported_efforts = 2; + */ + supportedEfforts: ReasoningEffort[] + + /** + * @generated from field: optional bool interleaved = 3; + */ + interleaved?: boolean +} + +/** + * Describes the message catalog.v1.ReasoningSupport. + * Use `create(ReasoningSupportSchema)` to create a new message. + */ +export const ReasoningSupportSchema: GenMessage = /*@__PURE__*/ messageDesc(file_v1_model, 1) + +/** + * @generated from message catalog.v1.RangedParameterSupport + */ +export type RangedParameterSupport = Message<'catalog.v1.RangedParameterSupport'> & { + /** + * @generated from field: bool supported = 1; + */ + supported: boolean + + /** + * @generated from field: optional catalog.v1.NumericRange range = 2; + */ + range?: NumericRange +} + +/** + * Describes the message catalog.v1.RangedParameterSupport. + * Use `create(RangedParameterSupportSchema)` to create a new message. + */ +export const RangedParameterSupportSchema: GenMessage = + /*@__PURE__*/ + messageDesc(file_v1_model, 2) + +/** + * @generated from message catalog.v1.ParameterSupport + */ +export type ParameterSupport = Message<'catalog.v1.ParameterSupport'> & { + /** + * @generated from field: optional catalog.v1.RangedParameterSupport temperature = 1; + */ + temperature?: RangedParameterSupport + + /** + * @generated from field: optional catalog.v1.RangedParameterSupport top_p = 2; + */ + topP?: RangedParameterSupport + + /** + * @generated from field: optional catalog.v1.RangedParameterSupport top_k = 3; + */ + topK?: RangedParameterSupport + + /** + * @generated from field: optional bool frequency_penalty = 4; + */ + frequencyPenalty?: boolean + + /** + * @generated from field: optional bool presence_penalty = 5; + */ + presencePenalty?: boolean + + /** + * @generated from field: optional bool max_tokens = 6; + */ + maxTokens?: boolean + + /** + * @generated from field: optional bool stop_sequences = 7; + */ + stopSequences?: boolean + + /** + * @generated from field: optional bool system_message = 8; + */ + systemMessage?: boolean +} + +/** + * Describes the message catalog.v1.ParameterSupport. + * Use `create(ParameterSupportSchema)` to create a new message. + */ +export const ParameterSupportSchema: GenMessage = /*@__PURE__*/ messageDesc(file_v1_model, 3) + +/** + * @generated from message catalog.v1.ImagePrice + */ +export type ImagePrice = Message<'catalog.v1.ImagePrice'> & { + /** + * @generated from field: double price = 1; + */ + price: number + + /** + * @generated from field: catalog.v1.Currency currency = 2; + */ + currency: Currency + + /** + * @generated from field: catalog.v1.ImagePriceUnit unit = 3; + */ + unit: ImagePriceUnit +} + +/** + * Describes the message catalog.v1.ImagePrice. + * Use `create(ImagePriceSchema)` to create a new message. + */ +export const ImagePriceSchema: GenMessage = /*@__PURE__*/ messageDesc(file_v1_model, 4) + +/** + * @generated from message catalog.v1.MinutePrice + */ +export type MinutePrice = Message<'catalog.v1.MinutePrice'> & { + /** + * @generated from field: double price = 1; + */ + price: number + + /** + * @generated from field: catalog.v1.Currency currency = 2; + */ + currency: Currency +} + +/** + * Describes the message catalog.v1.MinutePrice. + * Use `create(MinutePriceSchema)` to create a new message. + */ +export const MinutePriceSchema: GenMessage = /*@__PURE__*/ messageDesc(file_v1_model, 5) + +/** + * @generated from message catalog.v1.ModelPricing + */ +export type ModelPricing = Message<'catalog.v1.ModelPricing'> & { + /** + * @generated from field: catalog.v1.PricePerToken input = 1; + */ + input?: PricePerToken + + /** + * @generated from field: catalog.v1.PricePerToken output = 2; + */ + output?: PricePerToken + + /** + * @generated from field: optional catalog.v1.PricePerToken cache_read = 3; + */ + cacheRead?: PricePerToken + + /** + * @generated from field: optional catalog.v1.PricePerToken cache_write = 4; + */ + cacheWrite?: PricePerToken + + /** + * @generated from field: optional catalog.v1.ImagePrice per_image = 5; + */ + perImage?: ImagePrice + + /** + * @generated from field: optional catalog.v1.MinutePrice per_minute = 6; + */ + perMinute?: MinutePrice +} + +/** + * Describes the message catalog.v1.ModelPricing. + * Use `create(ModelPricingSchema)` to create a new message. + */ +export const ModelPricingSchema: GenMessage = /*@__PURE__*/ messageDesc(file_v1_model, 6) + +/** + * @generated from message catalog.v1.ModelConfig + */ +export type ModelConfig = Message<'catalog.v1.ModelConfig'> & { + /** + * @generated from field: string id = 1; + */ + id: string + + /** + * @generated from field: optional string name = 2; + */ + name?: string + + /** + * @generated from field: optional string description = 3; + */ + description?: string + + /** + * @generated from field: repeated catalog.v1.ModelCapability capabilities = 4; + */ + capabilities: ModelCapability[] + + /** + * @generated from field: repeated catalog.v1.Modality input_modalities = 5; + */ + inputModalities: Modality[] + + /** + * @generated from field: repeated catalog.v1.Modality output_modalities = 6; + */ + outputModalities: Modality[] + + /** + * @generated from field: optional uint32 context_window = 7; + */ + contextWindow?: number + + /** + * @generated from field: optional uint32 max_output_tokens = 8; + */ + maxOutputTokens?: number + + /** + * @generated from field: optional uint32 max_input_tokens = 9; + */ + maxInputTokens?: number + + /** + * @generated from field: optional catalog.v1.ModelPricing pricing = 10; + */ + pricing?: ModelPricing + + /** + * @generated from field: optional catalog.v1.ReasoningSupport reasoning = 11; + */ + reasoning?: ReasoningSupport + + /** + * @generated from field: optional catalog.v1.ParameterSupport parameter_support = 12; + */ + parameterSupport?: ParameterSupport + + /** + * @generated from field: optional string family = 13; + */ + family?: string + + /** + * @generated from field: optional string owned_by = 14; + */ + ownedBy?: string + + /** + * @generated from field: optional bool open_weights = 15; + */ + openWeights?: boolean + + /** + * @generated from field: repeated string alias = 16; + */ + alias: string[] + + /** + * @generated from field: optional catalog.v1.Metadata metadata = 17; + */ + metadata?: Metadata +} + +/** + * Describes the message catalog.v1.ModelConfig. + * Use `create(ModelConfigSchema)` to create a new message. + */ +export const ModelConfigSchema: GenMessage = /*@__PURE__*/ messageDesc(file_v1_model, 7) + +/** + * Top-level container + * + * @generated from message catalog.v1.ModelCatalog + */ +export type ModelCatalog = Message<'catalog.v1.ModelCatalog'> & { + /** + * @generated from field: string version = 1; + */ + version: string + + /** + * @generated from field: repeated catalog.v1.ModelConfig models = 2; + */ + models: ModelConfig[] +} + +/** + * Describes the message catalog.v1.ModelCatalog. + * Use `create(ModelCatalogSchema)` to create a new message. + */ +export const ModelCatalogSchema: GenMessage = /*@__PURE__*/ messageDesc(file_v1_model, 8) + +/** + * @generated from enum catalog.v1.ImagePriceUnit + */ +export enum ImagePriceUnit { + /** + * @generated from enum value: IMAGE_PRICE_UNIT_UNSPECIFIED = 0; + */ + UNSPECIFIED = 0, + + /** + * @generated from enum value: IMAGE_PRICE_UNIT_IMAGE = 1; + */ + IMAGE = 1, + + /** + * @generated from enum value: IMAGE_PRICE_UNIT_PIXEL = 2; + */ + PIXEL = 2 +} + +/** + * Describes the enum catalog.v1.ImagePriceUnit. + */ +export const ImagePriceUnitSchema: GenEnum = /*@__PURE__*/ enumDesc(file_v1_model, 0) diff --git a/packages/provider-catalog/src/gen/v1/provider_models_pb.ts b/packages/provider-catalog/src/gen/v1/provider_models_pb.ts new file mode 100644 index 00000000000..5534231b49a --- /dev/null +++ b/packages/provider-catalog/src/gen/v1/provider_models_pb.ts @@ -0,0 +1,208 @@ +// @generated by protoc-gen-es v2.11.0 with parameter "target=ts" +// @generated from file v1/provider_models.proto (package catalog.v1, syntax proto3) +/* eslint-disable */ + +import type { GenFile, GenMessage } from '@bufbuild/protobuf/codegenv2' +import { fileDesc, messageDesc } from '@bufbuild/protobuf/codegenv2' +import type { EndpointType, Modality, ModelCapability } from './common_pb' +import { file_v1_common } from './common_pb' +import type { ModelPricing, ParameterSupport, ReasoningSupport } from './model_pb' +import { file_v1_model } from './model_pb' +import type { Message } from '@bufbuild/protobuf' + +/** + * Describes the file v1/provider_models.proto. + */ +export const file_v1_provider_models: GenFile = + /*@__PURE__*/ + fileDesc( + 'Chh2MS9wcm92aWRlcl9tb2RlbHMucHJvdG8SCmNhdGFsb2cudjEilwEKEkNhcGFiaWxpdHlPdmVycmlkZRIoCgNhZGQYASADKA4yGy5jYXRhbG9nLnYxLk1vZGVsQ2FwYWJpbGl0eRIrCgZyZW1vdmUYAiADKA4yGy5jYXRhbG9nLnYxLk1vZGVsQ2FwYWJpbGl0eRIqCgVmb3JjZRgDIAMoDjIbLmNhdGFsb2cudjEuTW9kZWxDYXBhYmlsaXR5Is8BCgtNb2RlbExpbWl0cxIbCg5jb250ZXh0X3dpbmRvdxgBIAEoDUgAiAEBEh4KEW1heF9vdXRwdXRfdG9rZW5zGAIgASgNSAGIAQESHQoQbWF4X2lucHV0X3Rva2VucxgDIAEoDUgCiAEBEhcKCnJhdGVfbGltaXQYBCABKA1IA4gBAUIRCg9fY29udGV4dF93aW5kb3dCFAoSX21heF9vdXRwdXRfdG9rZW5zQhMKEV9tYXhfaW5wdXRfdG9rZW5zQg0KC19yYXRlX2xpbWl0IoYGChVQcm92aWRlck1vZGVsT3ZlcnJpZGUSEwoLcHJvdmlkZXJfaWQYASABKAkSEAoIbW9kZWxfaWQYAiABKAkSGQoMYXBpX21vZGVsX2lkGAMgASgJSACIAQESGgoNbW9kZWxfdmFyaWFudBgEIAEoCUgBiAEBEjkKDGNhcGFiaWxpdGllcxgFIAEoCzIeLmNhdGFsb2cudjEuQ2FwYWJpbGl0eU92ZXJyaWRlSAKIAQESLAoGbGltaXRzGAYgASgLMhcuY2F0YWxvZy52MS5Nb2RlbExpbWl0c0gDiAEBEi4KB3ByaWNpbmcYByABKAsyGC5jYXRhbG9nLnYxLk1vZGVsUHJpY2luZ0gEiAEBEjQKCXJlYXNvbmluZxgIIAEoCzIcLmNhdGFsb2cudjEuUmVhc29uaW5nU3VwcG9ydEgFiAEBEjwKEXBhcmFtZXRlcl9zdXBwb3J0GAkgASgLMhwuY2F0YWxvZy52MS5QYXJhbWV0ZXJTdXBwb3J0SAaIAQESMAoOZW5kcG9pbnRfdHlwZXMYCiADKA4yGC5jYXRhbG9nLnYxLkVuZHBvaW50VHlwZRIuChBpbnB1dF9tb2RhbGl0aWVzGAsgAygOMhQuY2F0YWxvZy52MS5Nb2RhbGl0eRIvChFvdXRwdXRfbW9kYWxpdGllcxgMIAMoDjIULmNhdGFsb2cudjEuTW9kYWxpdHkSFQoIZGlzYWJsZWQYDSABKAhIB4gBARIZCgxyZXBsYWNlX3dpdGgYDiABKAlICIgBARITCgZyZWFzb24YDyABKAlICYgBARIQCghwcmlvcml0eRgQIAEoDUIPCg1fYXBpX21vZGVsX2lkQhAKDl9tb2RlbF92YXJpYW50Qg8KDV9jYXBhYmlsaXRpZXNCCQoHX2xpbWl0c0IKCghfcHJpY2luZ0IMCgpfcmVhc29uaW5nQhQKEl9wYXJhbWV0ZXJfc3VwcG9ydEILCglfZGlzYWJsZWRCDwoNX3JlcGxhY2Vfd2l0aEIJCgdfcmVhc29uIl0KFFByb3ZpZGVyTW9kZWxDYXRhbG9nEg8KB3ZlcnNpb24YASABKAkSNAoJb3ZlcnJpZGVzGAIgAygLMiEuY2F0YWxvZy52MS5Qcm92aWRlck1vZGVsT3ZlcnJpZGViBnByb3RvMw', + [file_v1_common, file_v1_model] + ) + +/** + * @generated from message catalog.v1.CapabilityOverride + */ +export type CapabilityOverride = Message<'catalog.v1.CapabilityOverride'> & { + /** + * @generated from field: repeated catalog.v1.ModelCapability add = 1; + */ + add: ModelCapability[] + + /** + * @generated from field: repeated catalog.v1.ModelCapability remove = 2; + */ + remove: ModelCapability[] + + /** + * @generated from field: repeated catalog.v1.ModelCapability force = 3; + */ + force: ModelCapability[] +} + +/** + * Describes the message catalog.v1.CapabilityOverride. + * Use `create(CapabilityOverrideSchema)` to create a new message. + */ +export const CapabilityOverrideSchema: GenMessage = + /*@__PURE__*/ + messageDesc(file_v1_provider_models, 0) + +/** + * @generated from message catalog.v1.ModelLimits + */ +export type ModelLimits = Message<'catalog.v1.ModelLimits'> & { + /** + * @generated from field: optional uint32 context_window = 1; + */ + contextWindow?: number + + /** + * @generated from field: optional uint32 max_output_tokens = 2; + */ + maxOutputTokens?: number + + /** + * @generated from field: optional uint32 max_input_tokens = 3; + */ + maxInputTokens?: number + + /** + * @generated from field: optional uint32 rate_limit = 4; + */ + rateLimit?: number +} + +/** + * Describes the message catalog.v1.ModelLimits. + * Use `create(ModelLimitsSchema)` to create a new message. + */ +export const ModelLimitsSchema: GenMessage = /*@__PURE__*/ messageDesc(file_v1_provider_models, 1) + +/** + * @generated from message catalog.v1.ProviderModelOverride + */ +export type ProviderModelOverride = Message<'catalog.v1.ProviderModelOverride'> & { + /** + * Identification + * + * @generated from field: string provider_id = 1; + */ + providerId: string + + /** + * @generated from field: string model_id = 2; + */ + modelId: string + + /** + * @generated from field: optional string api_model_id = 3; + */ + apiModelId?: string + + /** + * @generated from field: optional string model_variant = 4; + */ + modelVariant?: string + + /** + * Overrides + * + * @generated from field: optional catalog.v1.CapabilityOverride capabilities = 5; + */ + capabilities?: CapabilityOverride + + /** + * @generated from field: optional catalog.v1.ModelLimits limits = 6; + */ + limits?: ModelLimits + + /** + * @generated from field: optional catalog.v1.ModelPricing pricing = 7; + */ + pricing?: ModelPricing + + /** + * @generated from field: optional catalog.v1.ReasoningSupport reasoning = 8; + */ + reasoning?: ReasoningSupport + + /** + * @generated from field: optional catalog.v1.ParameterSupport parameter_support = 9; + */ + parameterSupport?: ParameterSupport + + /** + * @generated from field: repeated catalog.v1.EndpointType endpoint_types = 10; + */ + endpointTypes: EndpointType[] + + /** + * @generated from field: repeated catalog.v1.Modality input_modalities = 11; + */ + inputModalities: Modality[] + + /** + * @generated from field: repeated catalog.v1.Modality output_modalities = 12; + */ + outputModalities: Modality[] + + /** + * Status + * + * @generated from field: optional bool disabled = 13; + */ + disabled?: boolean + + /** + * @generated from field: optional string replace_with = 14; + */ + replaceWith?: string + + /** + * Metadata + * + * @generated from field: optional string reason = 15; + */ + reason?: string + + /** + * 0 = auto, 100+ = manual + * + * @generated from field: uint32 priority = 16; + */ + priority: number +} + +/** + * Describes the message catalog.v1.ProviderModelOverride. + * Use `create(ProviderModelOverrideSchema)` to create a new message. + */ +export const ProviderModelOverrideSchema: GenMessage = + /*@__PURE__*/ + messageDesc(file_v1_provider_models, 2) + +/** + * Top-level container + * + * @generated from message catalog.v1.ProviderModelCatalog + */ +export type ProviderModelCatalog = Message<'catalog.v1.ProviderModelCatalog'> & { + /** + * @generated from field: string version = 1; + */ + version: string + + /** + * @generated from field: repeated catalog.v1.ProviderModelOverride overrides = 2; + */ + overrides: ProviderModelOverride[] +} + +/** + * Describes the message catalog.v1.ProviderModelCatalog. + * Use `create(ProviderModelCatalogSchema)` to create a new message. + */ +export const ProviderModelCatalogSchema: GenMessage = + /*@__PURE__*/ + messageDesc(file_v1_provider_models, 3) diff --git a/packages/provider-catalog/src/gen/v1/provider_pb.ts b/packages/provider-catalog/src/gen/v1/provider_pb.ts new file mode 100644 index 00000000000..a4bf80d264d --- /dev/null +++ b/packages/provider-catalog/src/gen/v1/provider_pb.ts @@ -0,0 +1,689 @@ +// @generated by protoc-gen-es v2.11.0 with parameter "target=ts" +// @generated from file v1/provider.proto (package catalog.v1, syntax proto3) +/* eslint-disable */ + +import type { GenEnum, GenFile, GenMessage } from '@bufbuild/protobuf/codegenv2' +import { enumDesc, fileDesc, messageDesc } from '@bufbuild/protobuf/codegenv2' +import type { AnthropicReasoningEffort, EndpointType, OpenAIReasoningEffort } from './common_pb' +import { file_v1_common } from './common_pb' +import type { Message } from '@bufbuild/protobuf' + +/** + * Describes the file v1/provider.proto. + */ +export const file_v1_provider: GenFile = + /*@__PURE__*/ + fileDesc( + 'ChF2MS9wcm92aWRlci5wcm90bxIKY2F0YWxvZy52MSKfAgoLQXBpRmVhdHVyZXMSGgoNYXJyYXlfY29udGVudBgBIAEoCEgAiAEBEhsKDnN0cmVhbV9vcHRpb25zGAIgASgISAGIAQESGwoOZGV2ZWxvcGVyX3JvbGUYAyABKAhIAogBARIZCgxzZXJ2aWNlX3RpZXIYBCABKAhIA4gBARIWCgl2ZXJib3NpdHkYBSABKAhIBIgBARIcCg9lbmFibGVfdGhpbmtpbmcYBiABKAhIBYgBAUIQCg5fYXJyYXlfY29udGVudEIRCg9fc3RyZWFtX29wdGlvbnNCEQoPX2RldmVsb3Blcl9yb2xlQg8KDV9zZXJ2aWNlX3RpZXJCDAoKX3ZlcmJvc2l0eUISChBfZW5hYmxlX3RoaW5raW5nInIKGU9wZW5BSUNoYXRSZWFzb25pbmdGb3JtYXQSQAoQcmVhc29uaW5nX2VmZm9ydBgBIAEoDjIhLmNhdGFsb2cudjEuT3BlbkFJUmVhc29uaW5nRWZmb3J0SACIAQFCEwoRX3JlYXNvbmluZ19lZmZvcnQipwEKHk9wZW5BSVJlc3BvbnNlc1JlYXNvbmluZ0Zvcm1hdBI2CgZlZmZvcnQYASABKA4yIS5jYXRhbG9nLnYxLk9wZW5BSVJlYXNvbmluZ0VmZm9ydEgAiAEBEjYKB3N1bW1hcnkYAiABKA4yIC5jYXRhbG9nLnYxLlJlc3BvbnNlc1N1bW1hcnlNb2RlSAGIAQFCCQoHX2VmZm9ydEIKCghfc3VtbWFyeSLNAQoYQW50aHJvcGljUmVhc29uaW5nRm9ybWF0EjQKBHR5cGUYASABKA4yIS5jYXRhbG9nLnYxLkFudGhyb3BpY1RoaW5raW5nVHlwZUgAiAEBEhoKDWJ1ZGdldF90b2tlbnMYAiABKA1IAYgBARI5CgZlZmZvcnQYAyABKA4yJC5jYXRhbG9nLnYxLkFudGhyb3BpY1JlYXNvbmluZ0VmZm9ydEgCiAEBQgcKBV90eXBlQhAKDl9idWRnZXRfdG9rZW5zQgkKB19lZmZvcnQifAoUR2VtaW5pVGhpbmtpbmdDb25maWcSHQoQaW5jbHVkZV90aG91Z2h0cxgBIAEoCEgAiAEBEhwKD3RoaW5raW5nX2J1ZGdldBgCIAEoDUgBiAEBQhMKEV9pbmNsdWRlX3Rob3VnaHRzQhIKEF90aGlua2luZ19idWRnZXQimQEKFUdlbWluaVJlYXNvbmluZ0Zvcm1hdBI7Cg90aGlua2luZ19jb25maWcYASABKAsyIC5jYXRhbG9nLnYxLkdlbWluaVRoaW5raW5nQ29uZmlnSAASOQoOdGhpbmtpbmdfbGV2ZWwYAiABKA4yHy5jYXRhbG9nLnYxLkdlbWluaVRoaW5raW5nTGV2ZWxIAEIICgZjb25maWciqAEKGU9wZW5Sb3V0ZXJSZWFzb25pbmdGb3JtYXQSNgoGZWZmb3J0GAEgASgOMiEuY2F0YWxvZy52MS5PcGVuQUlSZWFzb25pbmdFZmZvcnRIAIgBARIXCgptYXhfdG9rZW5zGAIgASgNSAGIAQESFAoHZXhjbHVkZRgDIAEoCEgCiAEBQgkKB19lZmZvcnRCDQoLX21heF90b2tlbnNCCgoIX2V4Y2x1ZGUigwEKHUVuYWJsZVRoaW5raW5nUmVhc29uaW5nRm9ybWF0EhwKD2VuYWJsZV90aGlua2luZxgBIAEoCEgAiAEBEhwKD3RoaW5raW5nX2J1ZGdldBgCIAEoDUgBiAEBQhIKEF9lbmFibGVfdGhpbmtpbmdCEgoQX3RoaW5raW5nX2J1ZGdldCJlChtUaGlua2luZ1R5cGVSZWFzb25pbmdGb3JtYXQSNAoNdGhpbmtpbmdfdHlwZRgBIAEoDjIYLmNhdGFsb2cudjEuVGhpbmtpbmdUeXBlSACIAQFCEAoOX3RoaW5raW5nX3R5cGUihAEKGERhc2hzY29wZVJlYXNvbmluZ0Zvcm1hdBIcCg9lbmFibGVfdGhpbmtpbmcYASABKAhIAIgBARIfChJpbmNyZW1lbnRhbF9vdXRwdXQYAiABKAhIAYgBAUISChBfZW5hYmxlX3RoaW5raW5nQhUKE19pbmNyZW1lbnRhbF9vdXRwdXQicQoZU2VsZkhvc3RlZFJlYXNvbmluZ0Zvcm1hdBIcCg9lbmFibGVfdGhpbmtpbmcYASABKAhIAIgBARIVCgh0aGlua2luZxgCIAEoCEgBiAEBQhIKEF9lbmFibGVfdGhpbmtpbmdCCwoJX3RoaW5raW5nItcEChdQcm92aWRlclJlYXNvbmluZ0Zvcm1hdBI8CgtvcGVuYWlfY2hhdBgBIAEoCzIlLmNhdGFsb2cudjEuT3BlbkFJQ2hhdFJlYXNvbmluZ0Zvcm1hdEgAEkYKEG9wZW5haV9yZXNwb25zZXMYAiABKAsyKi5jYXRhbG9nLnYxLk9wZW5BSVJlc3BvbnNlc1JlYXNvbmluZ0Zvcm1hdEgAEjkKCWFudGhyb3BpYxgDIAEoCzIkLmNhdGFsb2cudjEuQW50aHJvcGljUmVhc29uaW5nRm9ybWF0SAASMwoGZ2VtaW5pGAQgASgLMiEuY2F0YWxvZy52MS5HZW1pbmlSZWFzb25pbmdGb3JtYXRIABI7CgpvcGVucm91dGVyGAUgASgLMiUuY2F0YWxvZy52MS5PcGVuUm91dGVyUmVhc29uaW5nRm9ybWF0SAASRAoPZW5hYmxlX3RoaW5raW5nGAYgASgLMikuY2F0YWxvZy52MS5FbmFibGVUaGlua2luZ1JlYXNvbmluZ0Zvcm1hdEgAEkAKDXRoaW5raW5nX3R5cGUYByABKAsyJy5jYXRhbG9nLnYxLlRoaW5raW5nVHlwZVJlYXNvbmluZ0Zvcm1hdEgAEjkKCWRhc2hzY29wZRgIIAEoCzIkLmNhdGFsb2cudjEuRGFzaHNjb3BlUmVhc29uaW5nRm9ybWF0SAASPAoLc2VsZl9ob3N0ZWQYCSABKAsyJS5jYXRhbG9nLnYxLlNlbGZIb3N0ZWRSZWFzb25pbmdGb3JtYXRIAEIICgZmb3JtYXQikwEKD1Byb3ZpZGVyV2Vic2l0ZRIVCghvZmZpY2lhbBgBIAEoCUgAiAEBEhEKBGRvY3MYAiABKAlIAYgBARIUCgdhcGlfa2V5GAMgASgJSAKIAQESEwoGbW9kZWxzGAQgASgJSAOIAQFCCwoJX29mZmljaWFsQgcKBV9kb2NzQgoKCF9hcGlfa2V5QgkKB19tb2RlbHMiewoNTW9kZWxzQXBpVXJscxIUCgdkZWZhdWx0GAEgASgJSACIAQESFgoJZW1iZWRkaW5nGAIgASgJSAGIAQESFQoIcmVyYW5rZXIYAyABKAlIAogBAUIKCghfZGVmYXVsdEIMCgpfZW1iZWRkaW5nQgsKCV9yZXJhbmtlciJRChBQcm92aWRlck1ldGFkYXRhEjEKB3dlYnNpdGUYASABKAsyGy5jYXRhbG9nLnYxLlByb3ZpZGVyV2Vic2l0ZUgAiAEBQgoKCF93ZWJzaXRlIscECg5Qcm92aWRlckNvbmZpZxIKCgJpZBgBIAEoCRIMCgRuYW1lGAIgASgJEhgKC2Rlc2NyaXB0aW9uGAMgASgJSACIAQESOwoJYmFzZV91cmxzGAQgAygLMiguY2F0YWxvZy52MS5Qcm92aWRlckNvbmZpZy5CYXNlVXJsc0VudHJ5EjwKFWRlZmF1bHRfY2hhdF9lbmRwb2ludBgFIAEoDjIYLmNhdGFsb2cudjEuRW5kcG9pbnRUeXBlSAGIAQESMgoMYXBpX2ZlYXR1cmVzGAYgASgLMhcuY2F0YWxvZy52MS5BcGlGZWF0dXJlc0gCiAEBEjcKD21vZGVsc19hcGlfdXJscxgHIAEoCzIZLmNhdGFsb2cudjEuTW9kZWxzQXBpVXJsc0gDiAEBEjMKCG1ldGFkYXRhGAggASgLMhwuY2F0YWxvZy52MS5Qcm92aWRlck1ldGFkYXRhSASIAQESQgoQcmVhc29uaW5nX2Zvcm1hdBgJIAEoCzIjLmNhdGFsb2cudjEuUHJvdmlkZXJSZWFzb25pbmdGb3JtYXRIBYgBARovCg1CYXNlVXJsc0VudHJ5EgsKA2tleRgBIAEoBRINCgV2YWx1ZRgCIAEoCToCOAFCDgoMX2Rlc2NyaXB0aW9uQhgKFl9kZWZhdWx0X2NoYXRfZW5kcG9pbnRCDwoNX2FwaV9mZWF0dXJlc0ISChBfbW9kZWxzX2FwaV91cmxzQgsKCV9tZXRhZGF0YUITChFfcmVhc29uaW5nX2Zvcm1hdCJRCg9Qcm92aWRlckNhdGFsb2cSDwoHdmVyc2lvbhgBIAEoCRItCglwcm92aWRlcnMYAiADKAsyGi5jYXRhbG9nLnYxLlByb3ZpZGVyQ29uZmlnKqgBChRSZXNwb25zZXNTdW1tYXJ5TW9kZRImCiJSRVNQT05TRVNfU1VNTUFSWV9NT0RFX1VOU1BFQ0lGSUVEEAASHwobUkVTUE9OU0VTX1NVTU1BUllfTU9ERV9BVVRPEAESIgoeUkVTUE9OU0VTX1NVTU1BUllfTU9ERV9DT05DSVNFEAISIwofUkVTUE9OU0VTX1NVTU1BUllfTU9ERV9ERVRBSUxFRBADKrEBChVBbnRocm9waWNUaGlua2luZ1R5cGUSJwojQU5USFJPUElDX1RISU5LSU5HX1RZUEVfVU5TUEVDSUZJRUQQABIjCh9BTlRIUk9QSUNfVEhJTktJTkdfVFlQRV9FTkFCTEVEEAESJAogQU5USFJPUElDX1RISU5LSU5HX1RZUEVfRElTQUJMRUQQAhIkCiBBTlRIUk9QSUNfVEhJTktJTkdfVFlQRV9BREFQVElWRRADKsABChNHZW1pbmlUaGlua2luZ0xldmVsEiUKIUdFTUlOSV9USElOS0lOR19MRVZFTF9VTlNQRUNJRklFRBAAEiEKHUdFTUlOSV9USElOS0lOR19MRVZFTF9NSU5JTUFMEAESHQoZR0VNSU5JX1RISU5LSU5HX0xFVkVMX0xPVxACEiAKHEdFTUlOSV9USElOS0lOR19MRVZFTF9NRURJVU0QAxIeChpHRU1JTklfVEhJTktJTkdfTEVWRUxfSElHSBAEKnwKDFRoaW5raW5nVHlwZRIdChlUSElOS0lOR19UWVBFX1VOU1BFQ0lGSUVEEAASGQoVVEhJTktJTkdfVFlQRV9FTkFCTEVEEAESGgoWVEhJTktJTkdfVFlQRV9ESVNBQkxFRBACEhYKElRISU5LSU5HX1RZUEVfQVVUTxADYgZwcm90bzM', + [file_v1_common] + ) + +/** + * --- Request format flags --- + * + * @generated from message catalog.v1.ApiFeatures + */ +export type ApiFeatures = Message<'catalog.v1.ApiFeatures'> & { + /** + * Whether the provider supports array-formatted content in messages + * + * @generated from field: optional bool array_content = 1; + */ + arrayContent?: boolean + + /** + * Whether the provider supports stream_options for usage data + * + * @generated from field: optional bool stream_options = 2; + */ + streamOptions?: boolean + + /** + * Whether the provider supports the 'developer' role (OpenAI-specific) + * + * @generated from field: optional bool developer_role = 3; + */ + developerRole?: boolean + + /** + * Whether the provider supports service tier selection (OpenAI/Groq-specific) + * + * @generated from field: optional bool service_tier = 4; + */ + serviceTier?: boolean + + /** + * Whether the provider supports verbosity settings (Gemini-specific) + * + * @generated from field: optional bool verbosity = 5; + */ + verbosity?: boolean + + /** + * Whether the provider supports enable_thinking parameter format + * + * @generated from field: optional bool enable_thinking = 6; + */ + enableThinking?: boolean +} + +/** + * Describes the message catalog.v1.ApiFeatures. + * Use `create(ApiFeaturesSchema)` to create a new message. + */ +export const ApiFeaturesSchema: GenMessage = /*@__PURE__*/ messageDesc(file_v1_provider, 0) + +/** + * @generated from message catalog.v1.OpenAIChatReasoningFormat + */ +export type OpenAIChatReasoningFormat = Message<'catalog.v1.OpenAIChatReasoningFormat'> & { + /** + * @generated from field: optional catalog.v1.OpenAIReasoningEffort reasoning_effort = 1; + */ + reasoningEffort?: OpenAIReasoningEffort +} + +/** + * Describes the message catalog.v1.OpenAIChatReasoningFormat. + * Use `create(OpenAIChatReasoningFormatSchema)` to create a new message. + */ +export const OpenAIChatReasoningFormatSchema: GenMessage = + /*@__PURE__*/ + messageDesc(file_v1_provider, 1) + +/** + * @generated from message catalog.v1.OpenAIResponsesReasoningFormat + */ +export type OpenAIResponsesReasoningFormat = Message<'catalog.v1.OpenAIResponsesReasoningFormat'> & { + /** + * @generated from field: optional catalog.v1.OpenAIReasoningEffort effort = 1; + */ + effort?: OpenAIReasoningEffort + + /** + * @generated from field: optional catalog.v1.ResponsesSummaryMode summary = 2; + */ + summary?: ResponsesSummaryMode +} + +/** + * Describes the message catalog.v1.OpenAIResponsesReasoningFormat. + * Use `create(OpenAIResponsesReasoningFormatSchema)` to create a new message. + */ +export const OpenAIResponsesReasoningFormatSchema: GenMessage = + /*@__PURE__*/ + messageDesc(file_v1_provider, 2) + +/** + * @generated from message catalog.v1.AnthropicReasoningFormat + */ +export type AnthropicReasoningFormat = Message<'catalog.v1.AnthropicReasoningFormat'> & { + /** + * @generated from field: optional catalog.v1.AnthropicThinkingType type = 1; + */ + type?: AnthropicThinkingType + + /** + * @generated from field: optional uint32 budget_tokens = 2; + */ + budgetTokens?: number + + /** + * @generated from field: optional catalog.v1.AnthropicReasoningEffort effort = 3; + */ + effort?: AnthropicReasoningEffort +} + +/** + * Describes the message catalog.v1.AnthropicReasoningFormat. + * Use `create(AnthropicReasoningFormatSchema)` to create a new message. + */ +export const AnthropicReasoningFormatSchema: GenMessage = + /*@__PURE__*/ + messageDesc(file_v1_provider, 3) + +/** + * @generated from message catalog.v1.GeminiThinkingConfig + */ +export type GeminiThinkingConfig = Message<'catalog.v1.GeminiThinkingConfig'> & { + /** + * @generated from field: optional bool include_thoughts = 1; + */ + includeThoughts?: boolean + + /** + * @generated from field: optional uint32 thinking_budget = 2; + */ + thinkingBudget?: number +} + +/** + * Describes the message catalog.v1.GeminiThinkingConfig. + * Use `create(GeminiThinkingConfigSchema)` to create a new message. + */ +export const GeminiThinkingConfigSchema: GenMessage = + /*@__PURE__*/ + messageDesc(file_v1_provider, 4) + +/** + * @generated from message catalog.v1.GeminiReasoningFormat + */ +export type GeminiReasoningFormat = Message<'catalog.v1.GeminiReasoningFormat'> & { + /** + * @generated from oneof catalog.v1.GeminiReasoningFormat.config + */ + config: + | { + /** + * @generated from field: catalog.v1.GeminiThinkingConfig thinking_config = 1; + */ + value: GeminiThinkingConfig + case: 'thinkingConfig' + } + | { + /** + * @generated from field: catalog.v1.GeminiThinkingLevel thinking_level = 2; + */ + value: GeminiThinkingLevel + case: 'thinkingLevel' + } + | { case: undefined; value?: undefined } +} + +/** + * Describes the message catalog.v1.GeminiReasoningFormat. + * Use `create(GeminiReasoningFormatSchema)` to create a new message. + */ +export const GeminiReasoningFormatSchema: GenMessage = + /*@__PURE__*/ + messageDesc(file_v1_provider, 5) + +/** + * @generated from message catalog.v1.OpenRouterReasoningFormat + */ +export type OpenRouterReasoningFormat = Message<'catalog.v1.OpenRouterReasoningFormat'> & { + /** + * @generated from field: optional catalog.v1.OpenAIReasoningEffort effort = 1; + */ + effort?: OpenAIReasoningEffort + + /** + * @generated from field: optional uint32 max_tokens = 2; + */ + maxTokens?: number + + /** + * @generated from field: optional bool exclude = 3; + */ + exclude?: boolean +} + +/** + * Describes the message catalog.v1.OpenRouterReasoningFormat. + * Use `create(OpenRouterReasoningFormatSchema)` to create a new message. + */ +export const OpenRouterReasoningFormatSchema: GenMessage = + /*@__PURE__*/ + messageDesc(file_v1_provider, 6) + +/** + * enable_thinking + thinking_budget format (Qwen, Hunyuan, Silicon, etc.) + * + * @generated from message catalog.v1.EnableThinkingReasoningFormat + */ +export type EnableThinkingReasoningFormat = Message<'catalog.v1.EnableThinkingReasoningFormat'> & { + /** + * @generated from field: optional bool enable_thinking = 1; + */ + enableThinking?: boolean + + /** + * @generated from field: optional uint32 thinking_budget = 2; + */ + thinkingBudget?: number +} + +/** + * Describes the message catalog.v1.EnableThinkingReasoningFormat. + * Use `create(EnableThinkingReasoningFormatSchema)` to create a new message. + */ +export const EnableThinkingReasoningFormatSchema: GenMessage = + /*@__PURE__*/ + messageDesc(file_v1_provider, 7) + +/** + * thinking: { type } format (Doubao, Zhipu, DeepSeek, many aggregators) + * + * @generated from message catalog.v1.ThinkingTypeReasoningFormat + */ +export type ThinkingTypeReasoningFormat = Message<'catalog.v1.ThinkingTypeReasoningFormat'> & { + /** + * @generated from field: optional catalog.v1.ThinkingType thinking_type = 1; + */ + thinkingType?: ThinkingType +} + +/** + * Describes the message catalog.v1.ThinkingTypeReasoningFormat. + * Use `create(ThinkingTypeReasoningFormatSchema)` to create a new message. + */ +export const ThinkingTypeReasoningFormatSchema: GenMessage = + /*@__PURE__*/ + messageDesc(file_v1_provider, 8) + +/** + * DashScope-specific: enable_thinking + incremental_output + * + * @generated from message catalog.v1.DashscopeReasoningFormat + */ +export type DashscopeReasoningFormat = Message<'catalog.v1.DashscopeReasoningFormat'> & { + /** + * @generated from field: optional bool enable_thinking = 1; + */ + enableThinking?: boolean + + /** + * @generated from field: optional bool incremental_output = 2; + */ + incrementalOutput?: boolean +} + +/** + * Describes the message catalog.v1.DashscopeReasoningFormat. + * Use `create(DashscopeReasoningFormatSchema)` to create a new message. + */ +export const DashscopeReasoningFormatSchema: GenMessage = + /*@__PURE__*/ + messageDesc(file_v1_provider, 9) + +/** + * Self-hosted/Nvidia: chat_template_kwargs + * + * @generated from message catalog.v1.SelfHostedReasoningFormat + */ +export type SelfHostedReasoningFormat = Message<'catalog.v1.SelfHostedReasoningFormat'> & { + /** + * @generated from field: optional bool enable_thinking = 1; + */ + enableThinking?: boolean + + /** + * @generated from field: optional bool thinking = 2; + */ + thinking?: boolean +} + +/** + * Describes the message catalog.v1.SelfHostedReasoningFormat. + * Use `create(SelfHostedReasoningFormatSchema)` to create a new message. + */ +export const SelfHostedReasoningFormatSchema: GenMessage = + /*@__PURE__*/ + messageDesc(file_v1_provider, 10) + +/** + * Discriminated union: which API parameter shape this provider uses for reasoning + * + * @generated from message catalog.v1.ProviderReasoningFormat + */ +export type ProviderReasoningFormat = Message<'catalog.v1.ProviderReasoningFormat'> & { + /** + * @generated from oneof catalog.v1.ProviderReasoningFormat.format + */ + format: + | { + /** + * @generated from field: catalog.v1.OpenAIChatReasoningFormat openai_chat = 1; + */ + value: OpenAIChatReasoningFormat + case: 'openaiChat' + } + | { + /** + * @generated from field: catalog.v1.OpenAIResponsesReasoningFormat openai_responses = 2; + */ + value: OpenAIResponsesReasoningFormat + case: 'openaiResponses' + } + | { + /** + * @generated from field: catalog.v1.AnthropicReasoningFormat anthropic = 3; + */ + value: AnthropicReasoningFormat + case: 'anthropic' + } + | { + /** + * @generated from field: catalog.v1.GeminiReasoningFormat gemini = 4; + */ + value: GeminiReasoningFormat + case: 'gemini' + } + | { + /** + * @generated from field: catalog.v1.OpenRouterReasoningFormat openrouter = 5; + */ + value: OpenRouterReasoningFormat + case: 'openrouter' + } + | { + /** + * @generated from field: catalog.v1.EnableThinkingReasoningFormat enable_thinking = 6; + */ + value: EnableThinkingReasoningFormat + case: 'enableThinking' + } + | { + /** + * @generated from field: catalog.v1.ThinkingTypeReasoningFormat thinking_type = 7; + */ + value: ThinkingTypeReasoningFormat + case: 'thinkingType' + } + | { + /** + * @generated from field: catalog.v1.DashscopeReasoningFormat dashscope = 8; + */ + value: DashscopeReasoningFormat + case: 'dashscope' + } + | { + /** + * @generated from field: catalog.v1.SelfHostedReasoningFormat self_hosted = 9; + */ + value: SelfHostedReasoningFormat + case: 'selfHosted' + } + | { case: undefined; value?: undefined } +} + +/** + * Describes the message catalog.v1.ProviderReasoningFormat. + * Use `create(ProviderReasoningFormatSchema)` to create a new message. + */ +export const ProviderReasoningFormatSchema: GenMessage = + /*@__PURE__*/ + messageDesc(file_v1_provider, 11) + +/** + * @generated from message catalog.v1.ProviderWebsite + */ +export type ProviderWebsite = Message<'catalog.v1.ProviderWebsite'> & { + /** + * @generated from field: optional string official = 1; + */ + official?: string + + /** + * @generated from field: optional string docs = 2; + */ + docs?: string + + /** + * @generated from field: optional string api_key = 3; + */ + apiKey?: string + + /** + * @generated from field: optional string models = 4; + */ + models?: string +} + +/** + * Describes the message catalog.v1.ProviderWebsite. + * Use `create(ProviderWebsiteSchema)` to create a new message. + */ +export const ProviderWebsiteSchema: GenMessage = /*@__PURE__*/ messageDesc(file_v1_provider, 12) + +/** + * @generated from message catalog.v1.ModelsApiUrls + */ +export type ModelsApiUrls = Message<'catalog.v1.ModelsApiUrls'> & { + /** + * @generated from field: optional string default = 1; + */ + default?: string + + /** + * @generated from field: optional string embedding = 2; + */ + embedding?: string + + /** + * @generated from field: optional string reranker = 3; + */ + reranker?: string +} + +/** + * Describes the message catalog.v1.ModelsApiUrls. + * Use `create(ModelsApiUrlsSchema)` to create a new message. + */ +export const ModelsApiUrlsSchema: GenMessage = /*@__PURE__*/ messageDesc(file_v1_provider, 13) + +/** + * @generated from message catalog.v1.ProviderMetadata + */ +export type ProviderMetadata = Message<'catalog.v1.ProviderMetadata'> & { + /** + * @generated from field: optional catalog.v1.ProviderWebsite website = 1; + */ + website?: ProviderWebsite +} + +/** + * Describes the message catalog.v1.ProviderMetadata. + * Use `create(ProviderMetadataSchema)` to create a new message. + */ +export const ProviderMetadataSchema: GenMessage = /*@__PURE__*/ messageDesc(file_v1_provider, 14) + +/** + * @generated from message catalog.v1.ProviderConfig + */ +export type ProviderConfig = Message<'catalog.v1.ProviderConfig'> & { + /** + * @generated from field: string id = 1; + */ + id: string + + /** + * @generated from field: string name = 2; + */ + name: string + + /** + * @generated from field: optional string description = 3; + */ + description?: string + + /** + * Base URLs keyed by endpoint type + * + * key = EndpointType enum value + * + * @generated from field: map base_urls = 4; + */ + baseUrls: { [key: number]: string } + + /** + * @generated from field: optional catalog.v1.EndpointType default_chat_endpoint = 5; + */ + defaultChatEndpoint?: EndpointType + + /** + * @generated from field: optional catalog.v1.ApiFeatures api_features = 6; + */ + apiFeatures?: ApiFeatures + + /** + * @generated from field: optional catalog.v1.ModelsApiUrls models_api_urls = 7; + */ + modelsApiUrls?: ModelsApiUrls + + /** + * @generated from field: optional catalog.v1.ProviderMetadata metadata = 8; + */ + metadata?: ProviderMetadata + + /** + * How this provider's API expects reasoning parameters to be formatted + * + * @generated from field: optional catalog.v1.ProviderReasoningFormat reasoning_format = 9; + */ + reasoningFormat?: ProviderReasoningFormat +} + +/** + * Describes the message catalog.v1.ProviderConfig. + * Use `create(ProviderConfigSchema)` to create a new message. + */ +export const ProviderConfigSchema: GenMessage = /*@__PURE__*/ messageDesc(file_v1_provider, 15) + +/** + * Top-level container + * + * @generated from message catalog.v1.ProviderCatalog + */ +export type ProviderCatalog = Message<'catalog.v1.ProviderCatalog'> & { + /** + * @generated from field: string version = 1; + */ + version: string + + /** + * @generated from field: repeated catalog.v1.ProviderConfig providers = 2; + */ + providers: ProviderConfig[] +} + +/** + * Describes the message catalog.v1.ProviderCatalog. + * Use `create(ProviderCatalogSchema)` to create a new message. + */ +export const ProviderCatalogSchema: GenMessage = /*@__PURE__*/ messageDesc(file_v1_provider, 16) + +/** + * @generated from enum catalog.v1.ResponsesSummaryMode + */ +export enum ResponsesSummaryMode { + /** + * @generated from enum value: RESPONSES_SUMMARY_MODE_UNSPECIFIED = 0; + */ + UNSPECIFIED = 0, + + /** + * @generated from enum value: RESPONSES_SUMMARY_MODE_AUTO = 1; + */ + AUTO = 1, + + /** + * @generated from enum value: RESPONSES_SUMMARY_MODE_CONCISE = 2; + */ + CONCISE = 2, + + /** + * @generated from enum value: RESPONSES_SUMMARY_MODE_DETAILED = 3; + */ + DETAILED = 3 +} + +/** + * Describes the enum catalog.v1.ResponsesSummaryMode. + */ +export const ResponsesSummaryModeSchema: GenEnum = /*@__PURE__*/ enumDesc(file_v1_provider, 0) + +/** + * @generated from enum catalog.v1.AnthropicThinkingType + */ +export enum AnthropicThinkingType { + /** + * @generated from enum value: ANTHROPIC_THINKING_TYPE_UNSPECIFIED = 0; + */ + UNSPECIFIED = 0, + + /** + * @generated from enum value: ANTHROPIC_THINKING_TYPE_ENABLED = 1; + */ + ENABLED = 1, + + /** + * @generated from enum value: ANTHROPIC_THINKING_TYPE_DISABLED = 2; + */ + DISABLED = 2, + + /** + * @generated from enum value: ANTHROPIC_THINKING_TYPE_ADAPTIVE = 3; + */ + ADAPTIVE = 3 +} + +/** + * Describes the enum catalog.v1.AnthropicThinkingType. + */ +export const AnthropicThinkingTypeSchema: GenEnum = /*@__PURE__*/ enumDesc(file_v1_provider, 1) + +/** + * @generated from enum catalog.v1.GeminiThinkingLevel + */ +export enum GeminiThinkingLevel { + /** + * @generated from enum value: GEMINI_THINKING_LEVEL_UNSPECIFIED = 0; + */ + UNSPECIFIED = 0, + + /** + * @generated from enum value: GEMINI_THINKING_LEVEL_MINIMAL = 1; + */ + MINIMAL = 1, + + /** + * @generated from enum value: GEMINI_THINKING_LEVEL_LOW = 2; + */ + LOW = 2, + + /** + * @generated from enum value: GEMINI_THINKING_LEVEL_MEDIUM = 3; + */ + MEDIUM = 3, + + /** + * @generated from enum value: GEMINI_THINKING_LEVEL_HIGH = 4; + */ + HIGH = 4 +} + +/** + * Describes the enum catalog.v1.GeminiThinkingLevel. + */ +export const GeminiThinkingLevelSchema: GenEnum = /*@__PURE__*/ enumDesc(file_v1_provider, 2) + +/** + * @generated from enum catalog.v1.ThinkingType + */ +export enum ThinkingType { + /** + * @generated from enum value: THINKING_TYPE_UNSPECIFIED = 0; + */ + UNSPECIFIED = 0, + + /** + * @generated from enum value: THINKING_TYPE_ENABLED = 1; + */ + ENABLED = 1, + + /** + * @generated from enum value: THINKING_TYPE_DISABLED = 2; + */ + DISABLED = 2, + + /** + * @generated from enum value: THINKING_TYPE_AUTO = 3; + */ + AUTO = 3 +} + +/** + * Describes the enum catalog.v1.ThinkingType. + */ +export const ThinkingTypeSchema: GenEnum = /*@__PURE__*/ enumDesc(file_v1_provider, 3) diff --git a/packages/provider-catalog/src/index.ts b/packages/provider-catalog/src/index.ts new file mode 100644 index 00000000000..93d4fc47693 --- /dev/null +++ b/packages/provider-catalog/src/index.ts @@ -0,0 +1,45 @@ +/** + * Cherry Studio Catalog + * Main entry point for the model and provider catalog system + */ + +// Proto enums (re-exported from schemas/enums.ts which re-exports from gen/) +export { + AnthropicReasoningEffort, + Currency, + ENDPOINT_TYPE, + EndpointType, + MODALITY, + Modality, + MODEL_CAPABILITY, + ModelCapability, + OpenAIReasoningEffort, + ReasoningEffort +} from './schemas/enums' + +// Proto types (source of truth) +export type { ModelCatalog, ModelConfig, ModelConfig as ProtoModelConfig } from './gen/v1/model_pb' +export type { + ModelPricing, + ModelPricing as ProtoModelPricing, + ReasoningSupport as ProtoReasoningSupport, + ReasoningSupport +} from './gen/v1/model_pb' +export type { + ProviderModelOverride as ProtoProviderModelOverride, + ProviderModelCatalog, + ProviderModelOverride +} from './gen/v1/provider_models_pb' +export type { + ProviderConfig as ProtoProviderConfig, + ProviderReasoningFormat as ProtoProviderReasoningFormat, + ProviderCatalog, + ProviderConfig, + ProviderReasoningFormat +} from './gen/v1/provider_pb' + +// Catalog reader (read .pb files and return proto Message types) +export { readModelCatalog, readProviderCatalog, readProviderModelCatalog } from './catalog-reader' + +// Model ID normalization utilities +export { normalizeModelId } from './utils/importers/base/base-transformer' diff --git a/packages/provider-catalog/src/schemas/common.ts b/packages/provider-catalog/src/schemas/common.ts new file mode 100644 index 00000000000..612134a5c0f --- /dev/null +++ b/packages/provider-catalog/src/schemas/common.ts @@ -0,0 +1,58 @@ +/** + * Common type definitions for the catalog system + * Shared across model, provider, and override schemas + */ + +import * as z from 'zod' + +// Common string types for reuse +export const ModelIdSchema = z.string() +export const ProviderIdSchema = z.string() + +/** Version in YYYY-MM-DD format */ +export const VersionSchema = z.string().regex(/^\d{4}-\d{2}-\d{2}$/, { + message: 'Version must be in YYYY-MM-DD format' +}) + +/** ISO 8601 datetime timestamp */ +export const ISOTimestampSchema = z.iso.datetime() + +// Range helper schemas +export const NumericRangeSchema = z + .object({ + min: z.number(), + max: z.number() + }) + .refine((r) => r.min <= r.max, { + message: 'min must be less than or equal to max' + }) + +export const StringRangeSchema = z.object({ + min: z.string(), + max: z.string() +}) + +// Supported currencies for pricing +export const ZodCurrencySchema = z.enum(['USD', 'CNY']).default('USD').optional() + +// Price per token schema +// Default currency is USD if not specified +// Allow null for perMillionTokens to handle incomplete pricing data from APIs +export const PricePerTokenSchema = z.object({ + perMillionTokens: z.number().nonnegative().nullable(), + currency: ZodCurrencySchema +}) + +// Generic metadata schema +export const MetadataSchema = z.record(z.string(), z.unknown()).optional() + +// Type exports +export type ModelId = z.infer +export type ProviderId = z.infer +export type Version = z.infer +export type ISOTimestamp = z.infer +export type NumericRange = z.infer +export type StringRange = z.infer +export type ZodCurrency = z.infer +export type PricePerToken = z.infer +export type Metadata = z.infer diff --git a/packages/provider-catalog/src/schemas/enums.ts b/packages/provider-catalog/src/schemas/enums.ts new file mode 100644 index 00000000000..b2979090b1b --- /dev/null +++ b/packages/provider-catalog/src/schemas/enums.ts @@ -0,0 +1,70 @@ +/** + * Canonical enum definitions for the catalog system. + * + * Re-exports proto-generated enums as the SINGLE SOURCE OF TRUTH. + * Proto numeric enums are used everywhere — no string conversion. + * + * - catalog/schemas/ uses these via z.nativeEnum() + * - shared/data/types/ re-exports these directly + */ + +import { + AnthropicReasoningEffort, + AnthropicReasoningEffortSchema, + Currency, + CurrencySchema, + EndpointType, + EndpointTypeSchema, + Modality, + ModalitySchema, + ModelCapability, + ModelCapabilitySchema, + OpenAIReasoningEffort, + OpenAIReasoningEffortSchema, + ReasoningEffort, + ReasoningEffortSchema +} from '../gen/v1/common_pb' + +// ───────────────────────────────────────────────────────────────────────────── +// Re-export proto enums as canonical source of truth +// ───────────────────────────────────────────────────────────────────────────── + +export { + AnthropicReasoningEffort, + Currency, + EndpointType, + Modality, + ModelCapability, + OpenAIReasoningEffort, + ReasoningEffort +} + +// Schema descriptors for enum-to-string conversion if needed +export { + AnthropicReasoningEffortSchema as AnthropicReasoningEffortEnumSchema, + CurrencySchema as CurrencyEnumSchema, + EndpointTypeSchema as EndpointTypeEnumSchema, + ModalitySchema as ModalityEnumSchema, + ModelCapabilitySchema as ModelCapabilityEnumSchema, + OpenAIReasoningEffortSchema as OpenAIReasoningEffortEnumSchema, + ReasoningEffortSchema as ReasoningEffortEnumSchema +} + +// ───────────────────────────────────────────────────────────────────────────── +// Backward-compatible aliases +// ───────────────────────────────────────────────────────────────────────────── +// These allow existing `ENDPOINT_TYPE.OPENAI_CHAT_COMPLETIONS` syntax to keep working. +// Values change from strings to numbers — consumers must be updated. + +export const ENDPOINT_TYPE = EndpointType +export const MODEL_CAPABILITY = ModelCapability +export const MODALITY = Modality + +// ───────────────────────────────────────────────────────────────────────────── +// Utility (kept for backward compatibility, will be removed with Zod schemas) +// ───────────────────────────────────────────────────────────────────────────── + +/** Extract the value tuple from a const object for use with z.enum(). */ +export function objectValues>(obj: T): [T[keyof T], ...T[keyof T][]] { + return Object.values(obj) as [T[keyof T], ...T[keyof T][]] +} diff --git a/packages/provider-catalog/src/schemas/index.ts b/packages/provider-catalog/src/schemas/index.ts new file mode 100644 index 00000000000..4d3748046ec --- /dev/null +++ b/packages/provider-catalog/src/schemas/index.ts @@ -0,0 +1,19 @@ +/** + * Unified export of all catalog schemas and types + * This file provides a single entry point for all schema definitions + */ + +// Export canonical const-object definitions and utilities +export * from './enums' + +// Export all schemas from common types +export * from './common' + +// Export model schemas +export * from './model' + +// Export provider schemas +export * from './provider' + +// Export provider-model mapping schemas +export * from './provider-models' diff --git a/packages/provider-catalog/src/schemas/model.ts b/packages/provider-catalog/src/schemas/model.ts new file mode 100644 index 00000000000..8979cdcf12a --- /dev/null +++ b/packages/provider-catalog/src/schemas/model.ts @@ -0,0 +1,179 @@ +/** + * Model configuration schema definitions + * Defines the structure for model metadata, capabilities, and configurations + */ + +import * as z from 'zod' + +import { + MetadataSchema, + ModelIdSchema, + NumericRangeSchema, + PricePerTokenSchema, + VersionSchema, + ZodCurrencySchema +} from './common' +import { Modality, ModelCapability, ReasoningEffort } from './enums' + +export const ModalitySchema = z.enum(Modality) +export type ModalityType = z.infer + +export const ModelCapabilityTypeSchema = z.enum(ModelCapability) +export type ModelCapabilityType = z.infer + +// Thinking token limits schema (shared across reasoning types) +export const ThinkingTokenLimitsSchema = z.object({ + min: z.number().nonnegative().optional(), + max: z.number().positive().optional(), + default: z.number().nonnegative().optional() +}) + +/** Reasoning effort levels shared across providers */ +export const ReasoningEffortSchema = z.enum(ReasoningEffort) + +// Common reasoning fields shared across all reasoning type variants +// Exported for shared/runtime types to reuse +export const CommonReasoningFieldsSchema = { + thinkingTokenLimits: ThinkingTokenLimitsSchema.optional(), + supportedEfforts: z.array(ReasoningEffortSchema).optional(), + interleaved: z.boolean().optional() +} + +/** + * Reasoning support schema — describes model-level reasoning capabilities. + * + * This only captures WHAT the model supports (effort levels, token limits). + * HOW to invoke reasoning is defined by the provider's reasoning format + * (see provider.ts ProviderReasoningFormatSchema). + */ +export const ReasoningSupportSchema = z.object({ + ...CommonReasoningFieldsSchema +}) + +// Parameter support configuration +export const ParameterSupportSchema = z.object({ + temperature: z + .object({ + supported: z.boolean(), + range: NumericRangeSchema.optional() + }) + .optional(), + + topP: z + .object({ + supported: z.boolean(), + range: NumericRangeSchema.optional() + }) + .optional(), + + topK: z + .object({ + supported: z.boolean(), + range: NumericRangeSchema.optional() + }) + .optional(), + + frequencyPenalty: z.boolean().optional(), + presencePenalty: z.boolean().optional(), + maxTokens: z.boolean().optional(), + stopSequences: z.boolean().optional(), + systemMessage: z.boolean().optional() +}) + +// Model pricing configuration +export const ModelPricingSchema = z.object({ + input: PricePerTokenSchema, + output: PricePerTokenSchema, + + cacheRead: PricePerTokenSchema.optional(), + cacheWrite: PricePerTokenSchema.optional(), + + perImage: z + .object({ + price: z.number(), + currency: ZodCurrencySchema, + unit: z.enum(['image', 'pixel']).optional() + }) + .optional(), + + perMinute: z + .object({ + price: z.number(), + currency: ZodCurrencySchema + }) + .optional() +}) + +// Model configuration schema +export const ModelConfigSchema = z.object({ + // Basic information + id: ModelIdSchema, + name: z.string().optional(), + description: z.string().optional(), + + // Capabilities + capabilities: z + .array(ModelCapabilityTypeSchema) + .refine((arr) => new Set(arr).size === arr.length, { + message: 'Capabilities must be unique' + }) + .optional(), + + // Modalities + inputModalities: z + .array(ModalitySchema) + .refine((arr) => new Set(arr).size === arr.length, { + message: 'Input modalities must be unique' + }) + .optional(), + outputModalities: z + .array(ModalitySchema) + .refine((arr) => new Set(arr).size === arr.length, { + message: 'Output modalities must be unique' + }) + .optional(), + + // Limits + contextWindow: z.number().optional(), + maxOutputTokens: z.number().optional(), + maxInputTokens: z.number().optional(), + + // Pricing + pricing: ModelPricingSchema.optional(), + + // Reasoning support (model capabilities only, no provider-specific params) + reasoning: ReasoningSupportSchema.optional(), + + // Parameter support + parameterSupport: ParameterSupportSchema.optional(), + + // Model family (e.g., "GPT-4", "Claude 3") + family: z.string().optional(), + + // Original creator of the model (e.g., "anthropic", "google", "openai") + // This is the original publisher/creator, not the aggregator that hosts the model + ownedBy: z.string().optional(), + + // Whether the model has open weights (from models.dev) + openWeights: z.boolean().optional(), + + // Date version variants (same capabilities, different snapshots) + // Example: gpt-4-turbo's variants: ["gpt-4-turbo-2024-04-09", "gpt-4-turbo-2024-01-25"] + alias: z.array(ModelIdSchema).optional(), + + // Additional metadata + metadata: MetadataSchema +}) + +// Model list container schema for JSON files +export const ModelListSchema = z.object({ + version: VersionSchema, + models: z.array(ModelConfigSchema) +}) + +export type ThinkingTokenLimits = z.infer +export type ReasoningSupport = z.infer +export type ParameterSupport = z.infer +export type ModelPricing = z.infer +export type ModelConfig = z.infer +export type ModelList = z.infer diff --git a/packages/provider-catalog/src/schemas/provider-models.ts b/packages/provider-catalog/src/schemas/provider-models.ts new file mode 100644 index 00000000000..4f413ac6573 --- /dev/null +++ b/packages/provider-catalog/src/schemas/provider-models.ts @@ -0,0 +1,88 @@ +/** + * Provider-Model mapping schema definitions + * Defines how providers can override specific model configurations + * + * This file was renamed from override.ts for clearer semantics + */ + +import * as z from 'zod' + +import { ModelIdSchema, ProviderIdSchema, VersionSchema } from './common' +import { + ModalitySchema, + ModelCapabilityTypeSchema, + ModelPricingSchema, + ParameterSupportSchema, + ReasoningSupportSchema +} from './model' +import { EndpointTypeSchema } from './provider' + +export const CapabilityOverrideSchema = z.object({ + add: z.array(ModelCapabilityTypeSchema).optional(), // Add capabilities + remove: z.array(ModelCapabilityTypeSchema).optional(), // Remove capabilities + force: z.array(ModelCapabilityTypeSchema).optional() // Force set capabilities (ignore base config) +}) + +// ═══════════════════════════════════════════════════════════════════════════════ +// Provider-Model Override Schema +// ═══════════════════════════════════════════════════════════════════════════════ + +export const ProviderModelOverrideSchema = z.object({ + // Identification + providerId: ProviderIdSchema, + modelId: ModelIdSchema, // Canonical/normalized ID (references models.json) + + // API Model ID - The actual ID used when calling the provider's API + // This preserves the original provider-specific ID format + // Examples: + // - OpenRouter: "anthropic/claude-3-5-sonnet" + // - AIHubMix: "claude-3-5-sonnet" + // - Vertex AI: "global.anthropic.claude-3-5-sonnet-v1:0" + // If not set, modelId is used for API calls + apiModelId: z.string().optional(), + + // Variant identification (for same model with different variants) + // Used to distinguish variants like :free, :thinking, -search + // providerId + modelId + modelVariant forms the unique identifier + modelVariant: z.string().optional(), // 'free', 'thinking', 'nitro', 'search', etc. + + // Override configuration + capabilities: CapabilityOverrideSchema.optional(), + limits: z + .object({ + contextWindow: z.number().optional(), + maxOutputTokens: z.number().optional(), + maxInputTokens: z.number().optional(), + rateLimit: z.number().optional() // requests per minute + }) + .optional(), + pricing: ModelPricingSchema.partial().optional(), + reasoning: ReasoningSupportSchema.optional(), + parameterSupport: ParameterSupportSchema.partial().optional(), + + // Endpoint type overrides (when model uses different endpoints than provider default) + endpointTypes: z.array(EndpointTypeSchema).optional(), + + // Modality overrides (when provider supports different modalities than base model) + inputModalities: z.array(ModalitySchema).optional(), + outputModalities: z.array(ModalitySchema).optional(), + + // Status control + disabled: z.boolean().optional(), + replaceWith: ModelIdSchema.optional(), + + // Metadata + reason: z.string().optional(), + priority: z.number().default(0) +}) + +// Container schema for JSON files +export const ProviderModelListSchema = z.object({ + version: VersionSchema, + overrides: z.array(ProviderModelOverrideSchema) +}) + +// Type exports +export type CapabilityOverride = z.infer +export type ProviderModelOverride = z.infer +export type ProviderModelList = z.infer diff --git a/packages/provider-catalog/src/schemas/provider.ts b/packages/provider-catalog/src/schemas/provider.ts new file mode 100644 index 00000000000..cdd69717fc0 --- /dev/null +++ b/packages/provider-catalog/src/schemas/provider.ts @@ -0,0 +1,231 @@ +/** + * Provider configuration schema definitions + * Defines the structure for provider connections and API configurations + */ + +import * as z from 'zod' + +import { MetadataSchema, ProviderIdSchema, VersionSchema } from './common' +import { EndpointType, ReasoningEffort } from './enums' +import { CommonReasoningFieldsSchema } from './model' + +export const EndpointTypeSchema = z.enum(EndpointType) + +// ═══════════════════════════════════════════════════════════════════════════════ +// API Features +// ═══════════════════════════════════════════════════════════════════════════════ + +/** API feature flags controlling request construction at the SDK level */ +export const ApiFeaturesSchema = z.object({ + // --- Request format flags --- + + /** Whether the provider supports array-formatted content in messages */ + arrayContent: z.boolean().optional(), + /** Whether the provider supports stream_options for usage data */ + streamOptions: z.boolean().optional(), + + // --- Provider-specific parameter flags --- + + /** Whether the provider supports the 'developer' role (OpenAI-specific) */ + developerRole: z.boolean().optional(), + /** Whether the provider supports service tier selection (OpenAI/Groq-specific) */ + serviceTier: z.boolean().optional(), + /** Whether the provider supports verbosity settings (Gemini-specific) */ + verbosity: z.boolean().optional(), + /** Whether the provider supports enable_thinking parameter */ + enableThinking: z.boolean().optional() +}) + +// ═══════════════════════════════════════════════════════════════════════════════ +// Provider Reasoning Format +// +// Describes HOW a provider's API expects reasoning parameters to be formatted. +// This is a provider-level concern — model-level reasoning capabilities +// (effort levels, token limits) are in model.ts ReasoningSupportSchema. +// ═══════════════════════════════════════════════════════════════════════════════ + +const ReasoningEffortSchema = z.enum(ReasoningEffort) + +/** Provider reasoning format — discriminated union by format type */ +export const ProviderReasoningFormatSchema = z.discriminatedUnion('type', [ + z.object({ + type: z.literal('openai-chat'), + params: z + .object({ + reasoningEffort: ReasoningEffortSchema.optional() + }) + .optional() + }), + z.object({ + type: z.literal('openai-responses'), + params: z + .object({ + reasoning: z.object({ + effort: ReasoningEffortSchema.optional(), + summary: z.enum(['auto', 'concise', 'detailed']).optional() + }) + }) + .optional() + }), + z.object({ + type: z.literal('anthropic'), + params: z + .object({ + type: z.union([z.literal('enabled'), z.literal('disabled'), z.literal('adaptive')]), + budgetTokens: z.number().optional(), + effort: ReasoningEffortSchema.optional() + }) + .optional() + }), + z.object({ + type: z.literal('gemini'), + params: z + .union([ + z + .object({ + thinkingConfig: z.object({ + includeThoughts: z.boolean().optional(), + thinkingBudget: z.number().optional() + }) + }) + .optional(), + z + .object({ + thinkingLevel: z.enum(['minimal', 'low', 'medium', 'high']).optional() + }) + .optional() + ]) + .optional() + }), + z.object({ + type: z.literal('openrouter'), + params: z + .object({ + reasoning: z + .object({ + effort: z + .union([ + z.literal('none'), + z.literal('minimal'), + z.literal('low'), + z.literal('medium'), + z.literal('high') + ]) + .optional(), + maxTokens: z.number().optional(), + exclude: z.boolean().optional() + }) + .refine( + (v) => v.effort == null || v.maxTokens == null, + 'Only one of effort or maxTokens can be specified, not both' + ) + }) + .optional() + }), + z.object({ + type: z.literal('enable-thinking'), + params: z + .object({ + enableThinking: z.boolean(), + thinkingBudget: z.number().optional() + }) + .optional(), + ...CommonReasoningFieldsSchema + }), + z.object({ + type: z.literal('thinking-type'), + params: z + .object({ + thinking: z.object({ + type: z.union([z.literal('enabled'), z.literal('disabled'), z.literal('auto')]) + }) + }) + .optional() + }), + z.object({ + type: z.literal('dashscope'), + params: z + .object({ + enableThinking: z.boolean(), + incrementalOutput: z.boolean().optional() + }) + .optional() + }), + z.object({ + type: z.literal('self-hosted'), + params: z + .object({ + chatTemplateKwargs: z.object({ + enableThinking: z.boolean().optional(), + thinking: z.boolean().optional() + }) + }) + .optional() + }) +]) + +// ═══════════════════════════════════════════════════════════════════════════════ +// Provider Config +// ═══════════════════════════════════════════════════════════════════════════════ + +export const ProviderWebsiteSchema = z.object({ + website: z.object({ + official: z.url().optional(), + docs: z.url().optional(), + apiKey: z.url().optional(), + models: z.url().optional() + }) +}) + +export const ProviderConfigSchema = z + .object({ + /** Unique provider identifier */ + id: ProviderIdSchema, + /** Display name */ + name: z.string(), + /** Provider description */ + description: z.string().optional(), + /** Base URLs keyed by endpoint type */ + baseUrls: z.record(EndpointTypeSchema, z.url()).optional(), + /** Default endpoint type for chat requests (must exist in baseUrls) */ + defaultChatEndpoint: EndpointTypeSchema.optional(), + /** API feature flags controlling request construction */ + apiFeatures: ApiFeaturesSchema.optional(), + /** URLs for fetching available models, separated by model category */ + modelsApiUrls: z + .object({ + /** Default models listing endpoint */ + default: z.url().optional(), + /** Embedding models listing endpoint (if separate from default) */ + embedding: z.url().optional(), + /** Reranker models listing endpoint (if separate from default) */ + reranker: z.url().optional() + }) + .optional(), + /** Additional metadata including website URLs */ + metadata: MetadataSchema.and(ProviderWebsiteSchema), + /** How this provider's API expects reasoning parameters to be formatted */ + reasoningFormat: ProviderReasoningFormatSchema.optional() + }) + .refine( + (data) => { + if (data.defaultChatEndpoint && data.baseUrls) { + return data.defaultChatEndpoint in data.baseUrls + } + return true + }, + { + message: 'defaultChatEndpoint must exist as a key in baseUrls' + } + ) + +export const ProviderListSchema = z.object({ + version: VersionSchema, + providers: z.array(ProviderConfigSchema) +}) + +export { ENDPOINT_TYPE } from './enums' +export type ApiFeatures = z.infer +export type ProviderReasoningFormat = z.infer +export type ProviderConfig = z.infer +export type ProviderList = z.infer diff --git a/packages/provider-catalog/src/utils/importers/base/base-transformer.ts b/packages/provider-catalog/src/utils/importers/base/base-transformer.ts new file mode 100644 index 00000000000..e78ed47c7e7 --- /dev/null +++ b/packages/provider-catalog/src/utils/importers/base/base-transformer.ts @@ -0,0 +1,857 @@ +/** + * Base transformer interface and OpenAI-compatible base class + * Provides structure for transforming provider API responses to internal ModelConfig + */ + +import type { ModelCapabilityType, ModelConfig } from '../../../schemas' +import { MODALITY, type Modality, MODEL_CAPABILITY } from '../../../schemas/enums' + +/** + * Generic transformer interface + */ +export interface ITransformer { + /** + * Transform API model to internal ModelConfig + */ + transform(apiModel: TInput): ModelConfig + + /** + * Optional: Validate API response structure + */ + validate?(response: any): boolean + + /** + * Optional: Extract models array from response + */ + extractModels?(response: any): TInput[] +} + +/** + * Known model ID patterns to original publisher mapping + * Used by all transformers to determine the original model creator + */ +export const MODEL_TO_PUBLISHER: [RegExp, string][] = [ + // Anthropic Claude models + [/^claude/, 'anthropic'], + // OpenAI models (including text-embedding-ada, text-embedding-3-*) + [/^(gpt-|o1|o3|o4|chatgpt|dall-e|whisper|tts-|sora|text-embedding-ada|text-embedding-3|babbage|davinci)/, 'openai'], + // Google models (including text-embedding-004, text-embedding-005) + [/^(gemini|palm|gemma|veo|imagen|learnlm|text-embedding-00|text-multilingual-embedding-00|nano-banana)/, 'google'], + // Alibaba/Qwen models (including text-embedding-v*) + [/^(qwen|qvq|qwq|wan|text-embedding-v|gte)/, 'alibaba'], + // Meta models + [/^llama/, 'meta'], + // Mistral models + [/^(voxtral|devstral|mistral|mixtral|codestral|ministral|pixtral|magistral)/, 'mistral'], + // DeepSeek models + [/^deepseek/, 'deepseek'], + // Cohere models + [/^(command|embed-|rerank-)/, 'cohere'], + // xAI Grok models + [/^grok/, 'xai'], + // Microsoft Phi models + [/^phi-/, 'microsoft'], + // 01.ai Yi models + [/^yi-/, '01ai'], + // Zhipu GLM models + [/^(glm|cogview|cogvideo)/, 'zhipu'], + // Stability AI models + [/^(stable-|sd3|sdxl)/, 'stability'], + // Perplexity models + [/^(sonar|pplx-)/, 'perplexity'], + // Amazon models + [/^nova-/, 'amazon'], + // Baidu ERNIE models + [/^ernie/, 'baidu'], + // Moonshot/Kimi models + [/^(moonshot|kimi)/, 'moonshot'], + // 360 models + [/^360/, '360ai'], + // ByteDance Doubao models + [/^(doubao|seed|ui-tars)/, 'bytedance'], + // MiniMax models + [/^(abab|minimax)/, 'minimax'], + // Baichuan models + [/^baichuan/, 'baichuan'], + // Nvidia models + [/^(nvidia|nemotron)/, 'nvidia'], + // AI21 models + [/^jamba/, 'ai21'], + // Inflection models + [/^inflection/, 'inflection'], + // Voyage models + [/^voyage/, 'voyage'], + // Jina models + [/^jina/, 'jina'], + // BGE models (BAAI) + [/^bge/, 'baai'], + // StreamLake modelsp + [/^kat/, 'streamlake'], + // allenai models + [/^(olmo|molmo)/, 'ai2'], + [/^(flux)/, 'bfl'], + [/^(lfm)/, 'liquidai'], + [/^(longcat)/, 'meituan'], + [/^(trinity|spotlight|virtuoso|coder-large)/, 'arceeai'], + [/^(solar)/, 'upstageai'], + [/^(step)/, 'stepfun'], + [/^(ling|ring)/, 'bailing'], + [/^cogito/, 'cogito'], + [/^rnj/, 'essentialai'], + [/^dolphin/, 'dolphinai'], + [/^ideogram/, 'ideogram'], + [/^hunyuan/, 'tencent'], + [/^morph/, 'morph'], + [/^mercury/, 'inception'], + [/^(hermes|deephermes)/, 'nousresearch'], + [/^recraft/, 'recraft'], + [/^runway/, 'runway'], + [/^eleven/, 'elevenlabs'], + [/^relace/, 'relace'], + [/^riverflow/, 'sourceful'], + [/^sensenova/, 'sensenova'], + [/^intern/, 'intern'], + [/^kling/, 'kling'], + [/^vidu/, 'vidu'], + [/^suno/, 'suno'], + [/^kolors/, 'kolors'], + [/^megrez/, 'infini'], + [/^aion/, 'aion'] +] + +// ═══════════════════════════════════════════════════════════════════════════════ +// Capability Detection Patterns (match + exclude) +// Each entry: [matchRegex, excludeRegex | null, capability] +// Based on renderer-layer detection logic in src/renderer/src/config/models/ +// ═══════════════════════════════════════════════════════════════════════════════ + +/** Reasoning model detection — based on renderer reasoning.ts REASONING_REGEX + model checks */ +const REASONING_MATCH = + /^(?!.*\bnon-reasoning\b)(o\d+(?:-[\w-]+)?$|.*\b(?:reasoning|reasoner|thinking|think)\b.*|.*-r\d+.*|.*\bqwq\b.*|.*\bqvq\b.*|.*\bhunyuan-t1\b.*|.*\bglm-zero-preview\b.*|.*\bgrok-(?:3-mini|4|4-fast|4-1)(?:-[\w-]+)?\b.*|.*\bclaude-(?:3-7|sonnet-4|opus-4|haiku-4)\b.*|.*\bgemini-(?:2-5|3-)(?!.*image).*|.*\bdoubao-(?:seed-1-[68]|1-5-thinking|seed-code)\b.*|.*\bdeepseek-(?:v3|chat)\b.*|.*\bbaichuan-m[23]\b.*|.*\bminimax-m[12]\b.*|.*\bstep-[r3]\b.*|.*\bmagistral\b.*|.*\bmimo-v2\b.*|.*\bsonar-deep-research\b.*)/i +const REASONING_EXCLUDE = /\b(embed|rerank|dall-e|stable-diffusion|whisper|tts-|sdxl|flux|cogview|imagen)\b/i + +/** Function calling detection — based on renderer tooluse.ts FUNCTION_CALLING_MODELS */ +const FUNCTION_CALL_MATCH = + /\b(?:gpt-4o|gpt-4-|gpt-4[.-][15]|gpt-5|o[134](?:-[\w-]+)?|claude|qwen[23]?(?:-[\w-]+)?|hunyuan|deepseek|glm-4|gemini-(?:2|3|flash|pro)|grok-[34]|doubao-seed|kimi-k2|minimax-m2|mimo-v2|mistral-large|llama-4)/i +const FUNCTION_CALL_EXCLUDE = + /\b(?:o1-mini|o1-preview|gemini-1[.-]|imagen|aqa|qwen-mt|gpt-5-chat|glm-4[.-]5v|deepseek-v3[.-]2-speciale|embed|rerank|dall-e|stable-diffusion|whisper|tts-|sdxl|flux|cogview)\b/i + +/** Vision/image recognition detection — based on renderer vision.ts visionAllowedModels */ +const VISION_MATCH = + /(-vision|-vl\b|-visual|vision-|vl-|4v|\bllava\b|\bminicpm\b|\bpixtral\b|\binternvl|\bgpt-4o\b|\bgpt-4-(?!32k|base)\b|\bgpt-4[.-][15]\b|\bgpt-5\b|\bo[134](?:-[\w-]+)?$|\bclaude-(?:3|haiku-4|sonnet-4|opus-4)\b|\bgemini-(?:1-5|2|3-(?:flash|pro))\b|\bgemma-?3\b|\bqwen(?:2|2[.-]5|3)-vl\b|\bqwen(?:2[.-]5|3)-omni\b|\bgrok-(?:4|vision)\b|\bdoubao-seed-1-[68]\b|\bkimi-(?:latest|vl|thinking)\b|\bllama-4\b)/i +const VISION_EXCLUDE = + /\b(?:gpt-4-\d+-preview|gpt-4-turbo-preview|gpt-4-32k|gpt-4o-image|gpt-image|o1-mini|o3-mini|o1-preview|embed|rerank|dall-e|stable-diffusion|sd3|sdxl|flux|cogview|imagen|midjourney|ideogram|sora|runway|pika|kling|veo|vidu|wan|whisper|tts-)\b/i + +/** Web search detection — models with built-in or API-supported web search */ +const WEB_SEARCH_MATCH = + /(-search\b|-online\b|searchgpt|\bsonar\b|\bgpt-4o\b|\bgpt-4[.-]1\b|\bgpt-4[.-]5\b|\bgpt-5\b|\bo[34](?:-[\w-]+)?$|\bclaude-(?:3-[57]-sonnet|3-5-haiku|sonnet-4|opus-4|haiku-4)\b|\bgemini-(?:2(?!.*image-preview).*|3-(?:flash|pro))\b|\bgrok-)/i +const WEB_SEARCH_EXCLUDE = + /\b(?:gpt-4o-image|gpt-4[.-]1-nano|embed|rerank|dall-e|stable-diffusion|whisper|tts-|sdxl|flux|cogview|imagen)\b/i + +/** File/document input detection — only models whose name definitively indicates file/doc processing + * Most FILE_INPUT capability comes from: + * 1. models.dev `attachment` field (modelsdev/transformer.ts) + * 2. Provider-level overrides (generate-provider-models.ts) for OpenAI/Anthropic/Google + * This regex is intentionally narrow — only models with document-specific naming */ +const FILE_INPUT_MATCH = /\b(?:qwen-(?:long|doc)\b|[-_]ocr\b)/i + +/** Computer use detection — models with API-supported computer/desktop interaction + * Anthropic: claude-sonnet-4, claude-opus-4, claude-3-7-sonnet, claude-3-5-sonnet (beta) + * OpenAI: computer-use-preview (CUA via Responses API) */ +const COMPUTER_USE_MATCH = /\b(?:claude-(?:sonnet-4|opus-4|3-[57]-sonnet|haiku-4)|computer-use)/i +const COMPUTER_USE_EXCLUDE = /\b(?:embed|rerank|tts-|dall-e|stable-diffusion|sdxl|flux|cogview|imagen|whisper)\b/i + +/** + * Model ID patterns that indicate specific capabilities + * Format: [matchRegex, excludeRegex | null, capability] + * Used to infer capabilities from model naming conventions + */ +export const CAPABILITY_PATTERNS: [RegExp, RegExp | null, ModelCapabilityType][] = [ + // Reasoning/thinking models + [REASONING_MATCH, REASONING_EXCLUDE, MODEL_CAPABILITY.REASONING], + // Function calling + [FUNCTION_CALL_MATCH, FUNCTION_CALL_EXCLUDE, MODEL_CAPABILITY.FUNCTION_CALL], + // Embedding models + [/(embed|embedding|bge-|e5-|gte-)/, null, MODEL_CAPABILITY.EMBEDDING], + // Reranker models + [/(rerank|reranker)/, null, MODEL_CAPABILITY.RERANK], + // Vision/multimodal models + [VISION_MATCH, VISION_EXCLUDE, MODEL_CAPABILITY.IMAGE_RECOGNITION], + // File/document input (PDF, etc.) — narrow regex, most detection via models.dev + provider overrides + [FILE_INPUT_MATCH, null, MODEL_CAPABILITY.FILE_INPUT], + // Image generation models + [/(dall-e|stable-diffusion|sd3|sdxl|flux|image|imagen|midjourney|ideogram)/, null, MODEL_CAPABILITY.IMAGE_GENERATION], + // Video generation models + [/(sora|runway|pika|kling|veo|luma|gen-3|video|vidu|wan)/, null, MODEL_CAPABILITY.VIDEO_GENERATION], + // Audio transcription models + [/(whisper)/, null, MODEL_CAPABILITY.AUDIO_TRANSCRIPT], + // TTS models + [/(tts-)/, null, MODEL_CAPABILITY.AUDIO_GENERATION], + // Web search models + [WEB_SEARCH_MATCH, WEB_SEARCH_EXCLUDE, MODEL_CAPABILITY.WEB_SEARCH], + // Computer use / desktop interaction + [COMPUTER_USE_MATCH, COMPUTER_USE_EXCLUDE, MODEL_CAPABILITY.COMPUTER_USE] +] + +/** + * Known official model aliases (from provider documentation) + * Format: normalized model ID -> alias array + * Only include officially documented aliases, not auto-generated ones + */ +export const OFFICIAL_ALIASES: Record = { + // Anthropic Claude 4.5 models + 'claude-sonnet-4-5-20250929': ['claude-sonnet-4-5'], + 'claude-haiku-4-5-20251001': ['claude-haiku-4-5'], + 'claude-opus-4-5-20251101': ['claude-opus-4-5'], + // Anthropic Claude 4 models + 'claude-sonnet-4-20250514': ['claude-sonnet-4', 'claude-sonnet-4-0'], + 'claude-opus-4-20250514': ['claude-opus-4', 'claude-opus-4-0'], + // Anthropic Claude 3.7 models + 'claude-3-7-sonnet-20250219': ['claude-3-7-sonnet', 'claude-3-7-sonnet-latest'], + // Anthropic Claude 3.5 models + 'claude-3-5-sonnet-20241022': ['claude-3-5-sonnet', 'claude-3-5-sonnet-latest'], + 'claude-3-5-sonnet-20240620': ['claude-3-5-sonnet-v1'], + 'claude-3-5-haiku-20241022': ['claude-3-5-haiku', 'claude-3-5-haiku-latest'] +} + +/** + * Common aggregator prefixes that should be stripped from model IDs + * These are routing/deployment prefixes, not actual model name parts + * Note: More specific prefixes must come before shorter ones (e.g., 'zai-org-' before 'zai-') + */ +export const COMMON_AGGREGATOR_PREFIXES = [ + // AIHubMix routing prefixes + 'aihubmix-', + 'aihub-', + 'ahm-', + // Cloud provider routing + 'alicloud-', + 'azure-', + 'baidu-', + 'cbs-', + 'cc-', + 'sf-', + 's-', + 'bai-', + 'mm-', + 'web-', + // Platform aggregators + 'deepinfra-', + 'groq-', + 'nvidia-', + 'sophnet-', + // Legacy prefixes + 'zai-org-', // Must be before zai- + 'zai-', + 'lucidquery-', + 'lucidnova-', + 'lucid-', + 'siliconflow-', + 'chutes-', + 'huoshan-', + 'meta-', + 'cohere-', + 'coding-', + 'dmxapi-', + 'perplexity-', + 'ai21-', + 'openai-', + // Underscore-based prefixes + 'dmxapi_', + 'aistudio_' +] + +/** + * Known abbreviated prefixes and their canonical expansions + * These are brand abbreviations that providers use in model IDs + * Applied after aggregator prefix stripping to restore canonical names + */ +export const PREFIX_EXPANSIONS: [string, string][] = [ + ['mm-', 'minimax-'] // MiniMax shorthand: mm-m2-1 → minimax-m2-1 +] + +/** + * Common colon-based variant suffixes (OpenRouter style) + */ +export const COLON_VARIANT_SUFFIXES = [':free', ':nitro', ':extended', ':beta', ':preview', ':thinking', ':exacto'] + +/** + * Common hyphen-based variant suffixes + * These are provider-specific deployment variants that should be stripped + */ +export const HYPHEN_VARIANT_SUFFIXES = [ + '-free', + '-search', + '-online', + '-think', + '-reasoning', + '-classic', + '-low', + '-high', + '-minimal', + '-medium', + '-nothink', + '-no-think', + '-ssvip', + '-thinking', + '-nothinking', + '-aliyun', + '-huoshan', + '-tee', // Trusted Execution Environment variant + '-cc', // Cloud compute variant + '-fw', // Firewall/provider-specific variant + '-di', // Provider-specific variant + '-t', // Test/provider-specific variant + '-reverse' // Reverse proxy variant +] + +/** + * Common parentheses-based variant suffixes + */ +export const PAREN_VARIANT_SUFFIXES = ['(free)', '(beta)', '(preview)', '(thinking)'] + +/** + * Compound prefixes that protect a hyphen-based suffix from being stripped. + * e.g., "non-" before "-reasoning" means "non-reasoning" is part of the model name, + * not a variant suffix. + */ +const PROTECTED_COMPOUND_PREFIXES = ['non', 'no', 'pre', 'anti', 'post'] + +/** + * Normalize version separators in model IDs + * Converts comma, dot, and 'p' separators to hyphen (-) + * Existing hyphens are left unchanged to avoid date pattern issues + * + * IMPORTANT: Call this AFTER stripping parameter sizes to avoid + * converting decimal parameter sizes like 1.5b to 1-5b + * + * Examples: + * - gpt-3,5-turbo → gpt-3-5-turbo + * - gpt-3p5-turbo → gpt-3-5-turbo + * - claude-3.5-sonnet → claude-3-5-sonnet (dot to hyphen) + * - claude-3-5-sonnet → claude-3-5-sonnet (unchanged) + * - deepseek-r1-0728 → deepseek-r1-0728 (unchanged, date pattern) + */ +export function normalizeVersionSeparators(modelId: string): string { + // Normalize comma, dot, and 'p' separators between digits to hyphen + // Uses lookahead so the trailing digit isn't consumed, allowing overlapping matches + // e.g., "4.0.1" → "4-0-1" (without lookahead, "4.0.1" → "4-0.1" because "0" is consumed) + return modelId.replace(/(\d)[,.p](?=\d)/g, '$1-') +} + +/** + * Parameter size pattern: matches Xb or X.Xb where X is a number + * Must be preceded by a hyphen and optionally followed by hyphen+suffix or end of string + * Examples: -72b, -7b, -1.5b, -72b-instruct + */ +const PARAMETER_SIZE_PATTERN = /-(\d+(?:\.\d+)?b)(?=-|$)/i + +/** + * Extract parameter size suffix from model ID + * Returns the parameter size (e.g., "72b", "7b", "1.5b") or undefined if not found + * + * Examples: + * - qwen-2.5-72b → "72b" + * - llama-3.1-70b-instruct → "70b" + * - qwen-2.5-1.5b → "1.5b" + * - gpt-4o → undefined + */ +export function extractParameterSize(modelId: string): string | undefined { + const match = modelId.match(PARAMETER_SIZE_PATTERN) + return match ? match[1].toLowerCase() : undefined +} + +/** + * Strip parameter size suffix from model ID + * Removes the parameter size but keeps other suffixes like -instruct, -chat + * + * Examples: + * - qwen-2.5-72b → qwen-2.5 + * - llama-3.1-70b-instruct → llama-3.1-instruct + * - gpt-4o → gpt-4o (unchanged) + */ +export function stripParameterSize(modelId: string): string { + return modelId.replace(PARAMETER_SIZE_PATTERN, '') +} + +/** + * Infer publisher from a normalized model ID using MODEL_TO_PUBLISHER patterns + */ +export function inferPublisherFromModelId(normalizedModelId: string): string | undefined { + const lowerId = normalizedModelId.toLowerCase() + for (const [pattern, publisher] of MODEL_TO_PUBLISHER) { + if (pattern.test(lowerId)) { + return publisher + } + } + return undefined +} + +/** + * Infer capabilities from model ID using CAPABILITY_PATTERNS + * Each pattern has an optional exclude regex to prevent false positives + */ +export function inferCapabilitiesFromModelId(modelId: string): ModelCapabilityType[] { + const caps: ModelCapabilityType[] = [] + const lowerId = modelId.toLowerCase() + + for (const [match, exclude, capability] of CAPABILITY_PATTERNS) { + if (match.test(lowerId) && (!exclude || !exclude.test(lowerId))) { + caps.push(capability) + } + } + + return caps +} + +/** + * Get official aliases for a normalized model ID + */ +export function getOfficialAliases(normalizedModelId: string): string[] | undefined { + return OFFICIAL_ALIASES[normalizedModelId] +} + +/** + * Map raw modality strings to internal Modality type + * Handles common variations in modality naming + */ +export function mapModalityString(modality: string): Modality | undefined { + const normalized = modality.toLowerCase().trim() + + switch (normalized) { + case 'text': + return MODALITY.TEXT + case 'image': + return MODALITY.IMAGE + case 'audio': + return MODALITY.AUDIO + case 'video': + return MODALITY.VIDEO + case 'embedding': + case 'embeddings': + return MODALITY.VECTOR + default: + return undefined + } +} + +/** + * Map an array of modality strings to internal Modality array + * Defaults to ['TEXT'] if no valid modalities found + */ +export function mapModalities(modalityList: string[]): Modality[] { + const modalities = new Set() + + for (const m of modalityList) { + const mapped = mapModalityString(m) + if (mapped) { + modalities.add(mapped) + } + } + + const result = Array.from(modalities) + return result.length > 0 ? result : [MODALITY.TEXT] +} + +/** + * Normalize a model ID to its canonical form: + * 1. Strip provider prefix (e.g., "anthropic/claude-3" -> "claude-3") + * 2. Convert to lowercase + * 3. Strip aggregator prefixes (zai-xxx -> xxx) + * 4. Strip variant suffixes (:free, -free, etc.) + * 5. Strip parameter size suffix (72b, 7b, 1.5b) - BEFORE version normalization + * 6. Normalize version separators (3.5, 3,5, 3p5 → 3-5) + * + * This is the single source of truth for model ID normalization. + * All parsers and scripts should use this function. + */ +export function normalizeModelId(modelId: string): string { + const parts = modelId.split('/') + let baseName = parts[parts.length - 1].toLowerCase() + baseName = stripAggregatorPrefixes(baseName) + baseName = expandKnownPrefixes(baseName) + baseName = stripVariantSuffixes(baseName) + baseName = stripParameterSize(baseName) + baseName = normalizeVersionSeparators(baseName) + return baseName +} + +/** + * Strip aggregator prefixes from a model ID + */ +export function stripAggregatorPrefixes(modelId: string, additionalPrefixes: string[] = []): string { + const allPrefixes = [...additionalPrefixes, ...COMMON_AGGREGATOR_PREFIXES] + let result = modelId + + for (const prefix of allPrefixes) { + if (result.startsWith(prefix)) { + result = result.slice(prefix.length) + break // Only remove one prefix + } + } + + return result +} + +/** + * Expand known abbreviated prefixes to their canonical form + * e.g., mm-m2-1 → minimax-m2-1 + */ +export function expandKnownPrefixes(modelId: string): string { + for (const [abbrev, canonical] of PREFIX_EXPANSIONS) { + if (modelId.startsWith(abbrev)) { + return canonical + modelId.slice(abbrev.length) + } + } + return modelId +} + +/** + * Strip variant suffixes from a model ID + * Handles colon-based (:free), hyphen-based (-free), and parentheses-based ((free)) suffixes + */ +export function stripVariantSuffixes( + modelId: string, + options: { + colonSuffixes?: string[] + hyphenSuffixes?: string[] + parenSuffixes?: string[] + officialModelsWithSuffix?: Set + } = {} +): string { + const colonSuffixes = options.colonSuffixes ?? COLON_VARIANT_SUFFIXES + const hyphenSuffixes = options.hyphenSuffixes ?? HYPHEN_VARIANT_SUFFIXES + const parenSuffixes = options.parenSuffixes ?? PAREN_VARIANT_SUFFIXES + const officialModels = options.officialModelsWithSuffix ?? new Set() + + // Don't strip if it's an official model + if (officialModels.has(modelId)) { + return modelId + } + + // Strip colon-based suffixes + const colonIdx = modelId.lastIndexOf(':') + if (colonIdx > 0) { + const suffix = modelId.slice(colonIdx) + if (colonSuffixes.includes(suffix)) { + return modelId.slice(0, colonIdx) + } + } + + // Strip hyphen-based suffixes + // Protect compound modifiers like "non-reasoning", "non-thinking" from being stripped + for (const suffix of hyphenSuffixes) { + if (modelId.endsWith(suffix)) { + const remaining = modelId.slice(0, -suffix.length) + if (PROTECTED_COMPOUND_PREFIXES.some((p) => remaining.endsWith(p))) { + continue + } + return remaining + } + } + + // Strip parentheses-based suffixes (with optional space before) + for (const suffix of parenSuffixes) { + if (modelId.endsWith(suffix)) { + let result = modelId.slice(0, -suffix.length) + // Also strip trailing space if present + if (result.endsWith(' ')) { + result = result.slice(0, -1) + } + return result + } + } + + return modelId +} + +/** + * Extract variant suffix from a model ID + * Returns the suffix without the leading character (: or - or parentheses) + */ +export function extractVariantSuffix( + modelId: string, + options: { + colonSuffixes?: string[] + hyphenSuffixes?: string[] + parenSuffixes?: string[] + officialModelsWithSuffix?: Set + } = {} +): string | undefined { + const colonSuffixes = options.colonSuffixes ?? COLON_VARIANT_SUFFIXES + const hyphenSuffixes = options.hyphenSuffixes ?? HYPHEN_VARIANT_SUFFIXES + const parenSuffixes = options.parenSuffixes ?? PAREN_VARIANT_SUFFIXES + const officialModels = options.officialModelsWithSuffix ?? new Set() + + const lowerModelId = modelId.toLowerCase() + + // Don't extract variant for official models + if (officialModels.has(lowerModelId)) { + return undefined + } + + // Check colon-based suffixes + const colonIdx = lowerModelId.lastIndexOf(':') + if (colonIdx > 0) { + const suffix = lowerModelId.slice(colonIdx) + if (colonSuffixes.includes(suffix)) { + return suffix.slice(1) // Remove leading ':' + } + } + + // Check hyphen-based suffixes + for (const suffix of hyphenSuffixes) { + if (lowerModelId.endsWith(suffix)) { + const remaining = lowerModelId.slice(0, -suffix.length) + if (PROTECTED_COMPOUND_PREFIXES.some((p) => remaining.endsWith(p))) { + continue + } + return suffix.slice(1) // Remove leading '-' + } + } + + // Check parentheses-based suffixes (with optional space before) + for (const suffix of parenSuffixes) { + if (lowerModelId.endsWith(suffix) || lowerModelId.endsWith(' ' + suffix)) { + // Return content without parentheses: "(free)" -> "free" + return suffix.slice(1, -1) + } + } + + return undefined +} + +/** + * Base class for OpenAI-compatible transformers + * Handles common patterns like extracting { data: [...] } responses + */ +export class OpenAICompatibleTransformer implements ITransformer { + /** + * Default implementation extracts from { data: [...] } or direct array + */ + extractModels(response: any): any[] { + if (Array.isArray(response.data)) { + return response.data + } + if (Array.isArray(response)) { + return response + } + throw new Error('Invalid API response structure: expected { data: [] } or []') + } + + /** + * Default transformation for OpenAI-compatible model responses + * Minimal transformation - most fields are optional + */ + transform(apiModel: any): ModelConfig { + // Normalize model ID to lowercase + const modelId = (apiModel.id || apiModel.model || '').toLowerCase() + + if (!modelId) { + throw new Error('Model ID is required') + } + + return { + id: modelId, + name: apiModel.name || modelId, + description: apiModel.description, + + capabilities: this.inferCapabilities(apiModel), + inputModalities: [MODALITY.TEXT], // Default to text + outputModalities: [MODALITY.TEXT], // Default to text + + contextWindow: apiModel.context_length || apiModel.context_window || undefined, + maxOutputTokens: apiModel.max_tokens || apiModel.max_output_tokens || undefined, + + pricing: this.extractPricing(apiModel), + + metadata: { + source: 'api', + owned_by: apiModel.owned_by, + tags: apiModel.tags || [], + created: apiModel.created, + updated: apiModel.updated + } + } + } + + /** + * Infer basic capabilities from model data + */ + protected inferCapabilities(apiModel: any): ModelCapabilityType[] | undefined { + const capabilities: ModelCapabilityType[] = [] + + // Check for common capability indicators + if (apiModel.supports_tools || apiModel.function_calling) { + capabilities.push(MODEL_CAPABILITY.FUNCTION_CALL) + } + if (apiModel.supports_vision || apiModel.vision) { + capabilities.push(MODEL_CAPABILITY.IMAGE_RECOGNITION) + } + if (apiModel.supports_json_output || apiModel.response_format) { + capabilities.push(MODEL_CAPABILITY.STRUCTURED_OUTPUT) + } + + return capabilities.length > 0 ? capabilities : undefined + } + + /** + * Extract pricing if available + */ + protected extractPricing(apiModel: any): ModelConfig['pricing'] { + if (!apiModel.pricing) return undefined + + const pricing = apiModel.pricing + + // Handle per-token pricing (convert to per-million) + if (pricing.prompt !== undefined && pricing.completion !== undefined) { + const inputCost = parseFloat(pricing.prompt) + const outputCost = parseFloat(pricing.completion) + + if (inputCost < 0 || outputCost < 0) return undefined + + return { + input: { perMillionTokens: inputCost * 1_000_000 }, + output: { perMillionTokens: outputCost * 1_000_000 } + } + } + + // Handle direct per-million pricing + if ( + pricing.input?.perMillionTokens != null && + pricing.output?.perMillionTokens != null && + !isNaN(pricing.input.perMillionTokens) && + !isNaN(pricing.output.perMillionTokens) + ) { + return { + input: { perMillionTokens: pricing.input.perMillionTokens }, + output: { perMillionTokens: pricing.output.perMillionTokens } + } + } + + return undefined + } +} + +/** + * Abstract base class for catalog transformers + * Provides common functionality for normalizing model IDs, inferring publishers, etc. + */ +export abstract class BaseCatalogTransformer implements ITransformer { + /** + * Additional aggregator prefixes specific to this transformer + * Override in subclasses to add provider-specific prefixes + */ + protected readonly aggregatorPrefixes: string[] = [] + + /** + * Colon-based variant suffixes to strip + * Override in subclasses to customize + */ + protected readonly colonVariantSuffixes: string[] = COLON_VARIANT_SUFFIXES + + /** + * Hyphen-based variant suffixes to strip + * Override in subclasses to customize + */ + protected readonly hyphenVariantSuffixes: string[] = HYPHEN_VARIANT_SUFFIXES + + /** + * Official models that have suffix-like endings but should NOT be stripped + * Override in subclasses to customize + */ + protected readonly officialModelsWithSuffix: Set = new Set() + + /** + * Transform API model to internal ModelConfig + * Must be implemented by subclasses + */ + abstract transform(apiModel: TInput): ModelConfig + + /** + * Normalize a model ID by: + * 1. Removing provider prefix (e.g., "anthropic/claude-3" -> "claude-3") + * 2. Removing aggregator prefixes + * 3. Stripping variant suffixes + * 4. Stripping parameter size suffix (72b, 7b, 1.5b) - BEFORE version normalization + * 5. Normalizing version separators (3.5, 3,5, 3p5 → 3-5) + * 6. Converting to lowercase + */ + protected normalizeModelId(modelId: string): string { + // Split by '/' and take the last part + const parts = modelId.split('/') + let baseName = parts[parts.length - 1].toLowerCase() + + // Remove aggregator prefixes + baseName = stripAggregatorPrefixes(baseName, this.aggregatorPrefixes) + + // Expand known abbreviated prefixes (e.g., mm- → minimax-) + baseName = expandKnownPrefixes(baseName) + + // Strip variant suffixes + baseName = stripVariantSuffixes(baseName, { + colonSuffixes: this.colonVariantSuffixes, + hyphenSuffixes: this.hyphenVariantSuffixes, + officialModelsWithSuffix: this.officialModelsWithSuffix + }) + + // Strip parameter size suffix BEFORE version normalization + // This preserves decimal parameter sizes like 1.5b + baseName = stripParameterSize(baseName) + + // Normalize version separators (e.g., claude-3.5 → claude-3-5) + baseName = normalizeVersionSeparators(baseName) + + return baseName + } + + /** + * Extract parameter size from model ID + * Returns the size (e.g., "72b") or undefined + */ + protected getParameterSize(modelId: string): string | undefined { + // Normalize version first, then extract parameter size + const normalized = normalizeVersionSeparators(modelId.toLowerCase()) + return extractParameterSize(normalized) + } + + /** + * Infer the original model publisher from model ID + */ + protected inferPublisher(modelId: string): string | undefined { + return inferPublisherFromModelId(modelId) + } + + /** + * Get variant suffix from model ID if present + */ + protected getModelVariant(modelId: string): string | undefined { + return extractVariantSuffix(modelId, { + colonSuffixes: this.colonVariantSuffixes, + hyphenSuffixes: this.hyphenVariantSuffixes, + officialModelsWithSuffix: this.officialModelsWithSuffix + }) + } + + /** + * Get official aliases for a model ID + */ + protected getAlias(modelId: string): string[] | undefined { + const normalizedId = this.normalizeModelId(modelId) + return getOfficialAliases(normalizedId) + } + + /** + * Infer capabilities from model ID patterns + */ + protected inferCapabilitiesFromId(modelId: string): ModelCapabilityType[] { + return inferCapabilitiesFromModelId(modelId) + } + + /** + * Map modality strings to internal format + */ + protected mapModalities(modalityList: string[]): Modality[] { + return mapModalities(modalityList) + } +} diff --git a/packages/shared/data/api/schemas/models.ts b/packages/shared/data/api/schemas/models.ts new file mode 100644 index 00000000000..a9b4c525f23 --- /dev/null +++ b/packages/shared/data/api/schemas/models.ts @@ -0,0 +1,155 @@ +/** + * Model API Schema definitions + * + * Contains all model-related endpoints for CRUD operations. + * DTO types are derived from Zod schemas in ../../types/model + */ + +import * as z from 'zod' + +import { + type Model, + ParameterSupportDbSchema, + RuntimeModelPricingSchema, + RuntimeReasoningSchema +} from '../../types/model' + +/** Query parameters for listing models */ +const ListModelsQuerySchema = z.object({ + /** Filter by provider ID */ + providerId: z.string().optional(), + /** Filter by capability (numeric ModelCapability enum value) */ + capability: z.number().optional(), + /** Filter by enabled status */ + enabled: z.boolean().optional() +}) +export type ListModelsQuery = z.infer + +/** DTO for creating a new model */ +const CreateModelDtoSchema = z.object({ + /** Provider ID */ + providerId: z.string(), + /** Model ID (used in API calls) */ + modelId: z.string(), + /** Associated preset model ID */ + presetModelId: z.string().optional(), + /** Display name */ + name: z.string().optional(), + /** Description */ + description: z.string().optional(), + /** UI grouping */ + group: z.string().optional(), + /** Capabilities (numeric ModelCapability enum values) */ + capabilities: z.array(z.number()).optional(), + /** Input modalities (numeric Modality enum values) */ + inputModalities: z.array(z.number()).optional(), + /** Output modalities (numeric Modality enum values) */ + outputModalities: z.array(z.number()).optional(), + /** Endpoint types */ + endpointTypes: z.array(z.number()).optional(), + /** Context window size */ + contextWindow: z.number().optional(), + /** Maximum output tokens */ + maxOutputTokens: z.number().optional(), + /** Streaming support */ + supportsStreaming: z.boolean().optional(), + /** Reasoning configuration */ + reasoning: RuntimeReasoningSchema.optional(), + /** Parameter support (DB form) */ + parameterSupport: ParameterSupportDbSchema.optional(), + /** Pricing configuration */ + pricing: RuntimeModelPricingSchema.optional() +}) +export type CreateModelDto = z.infer + +/** DTO for updating an existing model — CreateModelDto minus identity fields, all optional, plus status fields */ +const UpdateModelDtoSchema = CreateModelDtoSchema.omit({ + providerId: true, + modelId: true, + presetModelId: true +}) + .partial() + .extend({ + isEnabled: z.boolean().optional(), + isHidden: z.boolean().optional(), + sortOrder: z.number().optional(), + notes: z.string().optional() + }) +export type UpdateModelDto = z.infer + +/** DTO for resolving raw model entries against catalog presets */ +const ResolveModelsDtoSchema = z.object({ + /** Provider ID */ + providerId: z.string(), + /** Raw model entries from SDK */ + models: z.array( + z.object({ + modelId: z.string(), + name: z.string().optional(), + group: z.string().optional(), + description: z.string().optional(), + endpointTypes: z.array(z.number()).optional() + }) + ) +}) +export type ResolveModelsDto = z.infer + +/** + * Model API Schema definitions + */ +export interface ModelSchemas { + /** + * Models collection endpoint + * @example GET /models?providerId=openai&capability=REASONING + * @example POST /models { "providerId": "openai", "modelId": "gpt-5" } + */ + '/models': { + /** List models with optional filters */ + GET: { + query: ListModelsQuery + response: Model[] + } + /** Create a new model */ + POST: { + body: CreateModelDto + response: Model + } + } + + /** + * Resolve raw SDK model entries against catalog presets + * Returns enriched Model[] with capabilities, pricing, etc. from catalog + * @example POST /models/resolve { "providerId": "openai", "models": [{ "modelId": "gpt-4o" }] } + */ + '/models/resolve': { + POST: { + body: ResolveModelsDto + response: Model[] + } + } + + /** + * Individual model endpoint (keyed by providerId + modelId) + * @example GET /models/openai/gpt-5 + * @example PATCH /models/openai/gpt-5 { "isEnabled": false } + * @example DELETE /models/openai/gpt-5 + */ + '/models/:providerId/:modelId': { + /** Get a model by provider ID and model ID */ + GET: { + params: { providerId: string; modelId: string } + response: Model + } + /** Update a model */ + PATCH: { + params: { providerId: string; modelId: string } + body: UpdateModelDto + response: Model + } + /** Delete a model */ + DELETE: { + params: { providerId: string; modelId: string } + response: void + } + } +} diff --git a/packages/shared/data/api/schemas/providers.ts b/packages/shared/data/api/schemas/providers.ts new file mode 100644 index 00000000000..635d7d30dc5 --- /dev/null +++ b/packages/shared/data/api/schemas/providers.ts @@ -0,0 +1,166 @@ +/** + * Provider API Schema definitions + * + * Contains all provider-related endpoints for CRUD operations. + * DTO types are plain TypeScript interfaces — runtime validation + * is performed by the ORM-derived Zod schema in userProvider.ts (main process). + */ + +import type { EndpointType, Model } from '../../types/model' +import type { ApiFeatures, ApiKeyEntry, AuthConfig, Provider, ProviderSettings } from '../../types/provider' + +export interface ListProvidersQuery { + /** Filter by enabled status */ + enabled?: boolean +} + +/** Shared editable fields between Create and Update DTOs */ +interface ProviderMutableFields { + /** Display name */ + name?: string + /** Base URL mapping (EndpointType → baseURL) */ + baseUrls?: Partial> + /** Model list API URLs */ + modelsApiUrls?: Record + /** Default text generation endpoint (numeric EndpointType enum value) */ + defaultChatEndpoint?: EndpointType + /** API keys */ + apiKeys?: ApiKeyEntry[] + /** Authentication configuration */ + authConfig?: AuthConfig + /** API feature support */ + apiFeatures?: ApiFeatures + /** Provider-specific settings */ + providerSettings?: Partial +} + +/** DTO for creating a new provider */ +export interface CreateProviderDto extends ProviderMutableFields { + /** User-defined unique ID (required) */ + providerId: string + /** Associated preset provider ID */ + presetProviderId?: string + /** Display name (required on create) */ + name: string +} + +/** DTO for updating an existing provider — all mutable fields optional, plus status fields */ +export interface UpdateProviderDto extends ProviderMutableFields { + /** Whether this provider is enabled */ + isEnabled?: boolean + /** Sort order in UI */ + sortOrder?: number +} + +/** + * Provider API Schema definitions + */ +export interface ProviderSchemas { + /** + * Providers collection endpoint + * @example GET /providers?enabled=true + * @example POST /providers { "providerId": "openai-main", "name": "OpenAI" } + */ + '/providers': { + /** List providers with optional filters */ + GET: { + query: ListProvidersQuery + response: Provider[] + } + /** Create a new provider */ + POST: { + body: CreateProviderDto + response: Provider + } + } + + /** + * Individual provider endpoint + * @example GET /providers/openai-main + * @example PATCH /providers/openai-main { "isEnabled": false } + * @example DELETE /providers/openai-main + */ + '/providers/:providerId': { + /** Get a provider by ID */ + GET: { + params: { providerId: string } + response: Provider + } + /** Update a provider */ + PATCH: { + params: { providerId: string } + body: UpdateProviderDto + response: Provider + } + /** Delete a provider */ + DELETE: { + params: { providerId: string } + response: void + } + } + + /** + * Get a rotated API key for a provider (round-robin across enabled keys) + * @example GET /providers/openai-main/rotated-key + */ + '/providers/:providerId/rotated-key': { + GET: { + params: { providerId: string } + response: { apiKey: string } + } + } + + /** + * Get all enabled API key values for a provider (for health check etc.) + * @example GET /providers/openai-main/api-keys + * @example POST /providers/openai-main/api-keys { "key": "sk-xxx", "label": "From URL import" } + */ + '/providers/:providerId/api-keys': { + GET: { + params: { providerId: string } + response: { keys: ApiKeyEntry[] } + } + /** Add an API key to a provider */ + POST: { + params: { providerId: string } + body: { key: string; label?: string } + response: Provider + } + } + + /** + * Get all catalog preset models for a provider (read-only, no DB writes) + * @example GET /providers/openai/catalog-models + */ + '/providers/:providerId/catalog-models': { + GET: { + params: { providerId: string } + response: Model[] + } + } + + /** + * Get full auth config for a provider (includes sensitive credentials). + * SECURITY NOTE: Runtime Provider intentionally strips authConfig (only exposes authType). + * This endpoint is for settings pages only — never call in chat hot path. + * Acceptable in Electron (same-process IPC, no network exposure). + * @example GET /providers/vertexai/auth-config + */ + '/providers/:providerId/auth-config': { + GET: { + params: { providerId: string } + response: AuthConfig | null + } + } + + /** + * Delete a specific API key by ID + * @example DELETE /providers/openai/api-keys/abc-123 + */ + '/providers/:providerId/api-keys/:keyId': { + DELETE: { + params: { providerId: string; keyId: string } + response: Provider + } + } +} diff --git a/packages/shared/data/types/model.ts b/packages/shared/data/types/model.ts new file mode 100644 index 00000000000..987e0446896 --- /dev/null +++ b/packages/shared/data/types/model.ts @@ -0,0 +1,274 @@ +/** + * Model - Merged runtime model type + * + * This is the "final state" after merging from all data sources. + * Consumers don't need to know the source - they just use the merged config. + * + * Data source priority: + * 1. user_model (user customization) + * 2. provider-models.json (catalog provider-level override) + * 3. models.json (catalog base definition) + */ + +import { + Currency, + ENDPOINT_TYPE, + EndpointType, + MODALITY, + Modality, + MODEL_CAPABILITY, + ModelCapability, + ReasoningEffort +} from '@cherrystudio/provider-catalog' +import * as z from 'zod' + +// Re-export const objects and types for consumers +export { Currency, ENDPOINT_TYPE, EndpointType, MODALITY, Modality, MODEL_CAPABILITY, ModelCapability, ReasoningEffort } + +// ═══════════════════════════════════════════════════════════════════════════════ +// Zod schemas (formerly in provider-catalog/schemas, now owned by shared) +// ═══════════════════════════════════════════════════════════════════════════════ + +/** Price per token schema */ +export const PricePerTokenSchema = z.object({ + perMillionTokens: z.number().nonnegative().nullable(), + currency: z.nativeEnum(Currency).default(Currency.USD).optional() +}) + +/** Thinking token limits */ +export const ThinkingTokenLimitsSchema = z.object({ + min: z.number().nonnegative().optional(), + max: z.number().positive().optional(), + default: z.number().nonnegative().optional() +}) + +/** Reasoning effort levels */ +const ReasoningEffortSchema = z.enum(ReasoningEffort) + +/** Common reasoning fields shared across all reasoning type variants */ +const CommonReasoningFieldsSchema = { + thinkingTokenLimits: ThinkingTokenLimitsSchema.optional(), + supportedEfforts: z.array(ReasoningEffortSchema).optional(), + interleaved: z.boolean().optional() +} + +/** Parameter support (DB form) */ +const NumericRangeSchema = z.object({ + min: z.number(), + max: z.number() +}) + +export const ParameterSupportDbSchema = z.object({ + temperature: z.object({ supported: z.boolean(), range: NumericRangeSchema.optional() }).optional(), + topP: z.object({ supported: z.boolean(), range: NumericRangeSchema.optional() }).optional(), + topK: z.object({ supported: z.boolean(), range: NumericRangeSchema.optional() }).optional(), + frequencyPenalty: z.boolean().optional(), + presencePenalty: z.boolean().optional(), + maxTokens: z.boolean().optional(), + stopSequences: z.boolean().optional(), + systemMessage: z.boolean().optional() +}) + +/** Separator used in UniqueModelId */ +export const UNIQUE_MODEL_ID_SEPARATOR = '::' + +/** UniqueModelId type: "providerId::modelId" */ +export type UniqueModelId = `${string}${typeof UNIQUE_MODEL_ID_SEPARATOR}${string}` + +/** + * Create a UniqueModelId from provider and model IDs + * @throws Error if providerId contains the separator + */ +export function createUniqueModelId(providerId: string, modelId: string): UniqueModelId { + if (providerId.includes(UNIQUE_MODEL_ID_SEPARATOR)) { + throw new Error(`providerId cannot contain "${UNIQUE_MODEL_ID_SEPARATOR}": ${providerId}`) + } + return `${providerId}${UNIQUE_MODEL_ID_SEPARATOR}${modelId}` as UniqueModelId +} + +/** + * Parse a UniqueModelId into its components + * @throws Error if the format is invalid + */ +export function parseUniqueModelId(uniqueId: UniqueModelId): { + providerId: string + modelId: string +} { + const idx = uniqueId.indexOf(UNIQUE_MODEL_ID_SEPARATOR) + if (idx === -1) { + throw new Error(`Invalid UniqueModelId format: ${uniqueId}`) + } + return { + providerId: uniqueId.slice(0, idx), + modelId: uniqueId.slice(idx + UNIQUE_MODEL_ID_SEPARATOR.length) + } +} + +/** + * Check if a string is a valid UniqueModelId + */ +export function isUniqueModelId(value: string): value is UniqueModelId { + return value.includes(UNIQUE_MODEL_ID_SEPARATOR) +} + +// ═══════════════════════════════════════════════════════════════════════════════ +// UI Tag Constants +// ═══════════════════════════════════════════════════════════════════════════════ + +/** Capabilities surfaced as filter tags in the UI */ +export const UI_CAPABILITY_TAGS = [ + MODEL_CAPABILITY.IMAGE_RECOGNITION, + MODEL_CAPABILITY.IMAGE_GENERATION, + MODEL_CAPABILITY.AUDIO_RECOGNITION, + MODEL_CAPABILITY.AUDIO_GENERATION, + MODEL_CAPABILITY.VIDEO_GENERATION, + MODEL_CAPABILITY.EMBEDDING, + MODEL_CAPABILITY.REASONING, + MODEL_CAPABILITY.FUNCTION_CALL, + MODEL_CAPABILITY.WEB_SEARCH, + MODEL_CAPABILITY.RERANK +] as const + +/** A capability that is shown as a UI tag */ +export type ModelCapabilityTag = (typeof UI_CAPABILITY_TAGS)[number] + +/** All UI-visible model tags: capability-derived + business tags */ +export type ModelTag = ModelCapabilityTag | 'free' + +/** All possible ModelTag values (for iteration) */ +export const ALL_MODEL_TAGS: readonly ModelTag[] = [...UI_CAPABILITY_TAGS, 'free'] as const + +export type ThinkingTokenLimits = z.infer + +/** DB form: supportedEfforts is optional */ +export const ReasoningConfigSchema = z.object({ + /** Reasoning type: must match a known reasoning variant */ + type: z.string().regex(/^[a-z][a-z0-9-]*$/, { + message: 'Reasoning type must be lowercase alphanumeric with hyphens' + }), + ...CommonReasoningFieldsSchema +}) +export type ReasoningConfig = z.infer + +/** Runtime form: extends DB form — supportedEfforts required, adds defaultEffort */ +export const RuntimeReasoningSchema = ReasoningConfigSchema.required({ supportedEfforts: true }).extend({ + /** Default effort level */ + defaultEffort: z.enum(ReasoningEffort).optional() +}) + +export type RuntimeReasoning = z.infer + +export type ParameterSupport = z.infer + +/** Runtime form: strict parameter support with more fields (not derivable from DB form — different shape) */ +export const RuntimeParameterSupportSchema = z.object({ + temperature: z + .object({ + supported: z.boolean(), + min: z.number(), + max: z.number(), + default: z.number().optional() + }) + .optional(), + topP: z + .object({ + supported: z.boolean(), + min: z.number(), + max: z.number(), + default: z.number().optional() + }) + .optional(), + topK: z + .object({ + supported: z.boolean(), + min: z.number(), + max: z.number() + }) + .optional(), + frequencyPenalty: z.boolean().optional(), + presencePenalty: z.boolean().optional(), + maxTokens: z.boolean(), + stopSequences: z.boolean(), + systemMessage: z.boolean() +}) +export type RuntimeParameterSupport = z.infer + +/** Pricing tier imported from catalog (source of truth) */ +export const PricingTierSchema = PricePerTokenSchema +export type PricingTier = z.infer + +export const RuntimeModelPricingSchema = z.object({ + input: PricePerTokenSchema, + output: PricePerTokenSchema, + cacheRead: PricePerTokenSchema.optional(), + cacheWrite: PricePerTokenSchema.optional(), + perImage: z + .object({ + price: z.number(), + unit: z.enum(['image', 'pixel']).optional() + }) + .optional(), + perMinute: z + .object({ + price: z.number() + }) + .optional() +}) +export type RuntimeModelPricing = z.infer + +export const ModelSchema = z.object({ + /** Unique identifier: "providerId::modelId" */ + id: z.string() as z.ZodType, + /** Provider ID */ + providerId: z.string(), + /** API Model ID - The actual ID used when calling the provider's API */ + apiModelId: z.string().optional(), + + // Display Information + /** Display name */ + name: z.string(), + /** Description */ + description: z.string().optional(), + /** UI grouping */ + group: z.string().optional(), + /** Model family */ + family: z.string().optional(), + /** Organization that owns the model */ + ownedBy: z.string().optional(), + + // Capabilities + /** Final capability list after all merges */ + capabilities: z.array(z.enum(ModelCapability)), + /** Supported input modalities */ + inputModalities: z.array(z.enum(Modality)).optional(), + /** Supported output modalities */ + outputModalities: z.array(z.enum(Modality)).optional(), + + // Configuration + /** Context window size */ + contextWindow: z.number().optional(), + /** Maximum output tokens */ + maxOutputTokens: z.number().optional(), + /** Maximum input tokens */ + maxInputTokens: z.number().optional(), + /** Supported endpoint types */ + endpointTypes: z.array(z.enum(EndpointType)).optional(), + /** Whether streaming is supported */ + supportsStreaming: z.boolean(), + /** Reasoning configuration */ + reasoning: RuntimeReasoningSchema.optional(), + /** Parameter support */ + parameterSupport: RuntimeParameterSupportSchema.optional(), + + pricing: RuntimeModelPricingSchema.optional(), + + // Status + /** Whether this model is available for use */ + isEnabled: z.boolean(), + /** Whether this model is hidden from lists */ + isHidden: z.boolean(), + /** Replacement model if this one is deprecated */ + replaceWith: (z.string() as z.ZodType).optional() +}) + +export type Model = z.infer diff --git a/packages/shared/data/types/provider.ts b/packages/shared/data/types/provider.ts new file mode 100644 index 00000000000..d373fbf02b4 --- /dev/null +++ b/packages/shared/data/types/provider.ts @@ -0,0 +1,239 @@ +/** + * Provider - Merged runtime provider type + * + * This is the "final state" after merging user config with preset. + * Consumers don't need to know the source - they just use the merged config. + * + * Data source priority: + * 1. user_provider (user configuration) + * 2. providers.json (catalog preset) + * + * Zod schemas are the single source of truth — all types derived via z.infer<> + */ + +import { EndpointType } from '@cherrystudio/provider-catalog' +import * as z from 'zod' + +// ─── Schemas formerly from provider-catalog/schemas ───────────────────────── + +const EndpointTypeSchema = z.enum(EndpointType) + +/** API feature flags controlling request construction at the SDK level */ +const CatalogApiFeaturesSchema = z.object({ + arrayContent: z.boolean().optional(), + streamOptions: z.boolean().optional(), + developerRole: z.boolean().optional(), + serviceTier: z.boolean().optional(), + verbosity: z.boolean().optional(), + enableThinking: z.boolean().optional() +}) + +/** Provider website schema (type used for catalog ProviderWebsite type) */ +const ProviderWebsiteSchema = z.object({ + website: z.object({ + official: z.string().url().optional(), + docs: z.string().url().optional(), + apiKey: z.string().url().optional(), + models: z.string().url().optional() + }) +}) + +export type OpenAIServiceTier = 'auto' | 'default' | 'flex' | 'priority' | null | undefined +export type GroqServiceTier = 'auto' | 'on_demand' | 'flex' | undefined | null +export type ServiceTier = OpenAIServiceTier | GroqServiceTier + +export const OpenAIServiceTiers = { + auto: 'auto', + default: 'default', + flex: 'flex', + priority: 'priority' +} as const + +export const GroqServiceTiers = { + auto: 'auto', + on_demand: 'on_demand', + flex: 'flex' +} as const + +export function isOpenAIServiceTier(tier: string | null | undefined): tier is OpenAIServiceTier { + return tier === null || tier === undefined || Object.hasOwn(OpenAIServiceTiers, tier) +} + +export function isGroqServiceTier(tier: string | undefined | null): tier is GroqServiceTier { + return tier === null || tier === undefined || Object.hasOwn(GroqServiceTiers, tier) +} + +export function isServiceTier(tier: string | null | undefined): tier is ServiceTier { + return isGroqServiceTier(tier) || isOpenAIServiceTier(tier) +} + +export const ApiKeyEntrySchema = z.object({ + /** UUID for referencing this key */ + id: z.string(), + /** Actual key value (encrypted in storage) */ + key: z.string(), + /** User-friendly label */ + label: z.string().optional(), + /** Whether this key is enabled */ + isEnabled: z.boolean(), + /** Creation timestamp */ + createdAt: z.number().optional() +}) + +export type ApiKeyEntry = z.infer +export const RuntimeApiKeySchema = ApiKeyEntrySchema.omit({ key: true }) +export type RuntimeApiKey = z.infer + +export const AuthTypeSchema = z.enum(['api-key', 'oauth', 'iam-aws', 'iam-gcp', 'iam-azure']) +export type AuthType = z.infer + +const AuthConfigApiKey = z.object({ + type: z.literal('api-key'), + headerName: z.string().optional(), + prefix: z.string().optional(), + /** Whether the provider requires an API key (false for local providers like Ollama) */ + required: z.boolean().optional() +}) + +const AuthConfigOAuth = z.object({ + type: z.literal('oauth'), + clientId: z.string(), + refreshToken: z.string().optional(), + accessToken: z.string().optional(), + expiresAt: z.number().optional() +}) + +const AuthConfigIamAws = z.object({ + type: z.literal('iam-aws'), + region: z.string(), + accessKeyId: z.string().optional(), + secretAccessKey: z.string().optional() +}) + +const AuthConfigIamGcp = z.object({ + type: z.literal('iam-gcp'), + project: z.string(), + location: z.string(), + credentials: z.record(z.string(), z.unknown()).optional() +}) + +const AuthConfigIamAzure = z.object({ + type: z.literal('iam-azure'), + apiVersion: z.string(), + deploymentId: z.string().optional() +}) + +export const AuthConfigSchema = z.discriminatedUnion('type', [ + AuthConfigApiKey, + AuthConfigOAuth, + AuthConfigIamAws, + AuthConfigIamGcp, + AuthConfigIamAzure +]) +export type AuthConfig = z.infer + +export const ApiFeaturesSchema = CatalogApiFeaturesSchema +export type ApiFeatures = z.infer + +export const RuntimeApiFeaturesSchema = ApiFeaturesSchema.required() +export type RuntimeApiFeatures = z.infer + +export type ProviderWebsite = z.infer + +/** Flat website links schema for runtime Provider (without the catalog wrapper) */ +export const ProviderWebsitesSchema = z.object({ + official: z.string().optional(), + apiKey: z.string().optional(), + docs: z.string().optional(), + models: z.string().optional() +}) + +export type ProviderWebsites = z.infer + +export const ProviderSettingsSchema = z.object({ + // OpenAI / Groq + serviceTier: z.string().optional(), + verbosity: z.string().optional(), + + // Azure-specific + apiVersion: z.string().optional(), + + // Anthropic + cacheControl: z + .object({ + enabled: z.boolean(), + tokenThreshold: z.number().optional(), + cacheSystemMessage: z.boolean().optional(), + cacheLastNMessages: z.number().optional() + }) + .optional(), + + // Ollama / LMStudio / GPUStack + keepAliveTime: z.number().optional(), + + // Common + rateLimit: z.number().optional(), + timeout: z.number().optional(), + extraHeaders: z.record(z.string(), z.string()).optional(), + + // User notes + notes: z.string().optional(), + + // GitHub Copilot auth state (stored here because v2 Provider has no isAuthed column) + isAuthed: z.boolean().optional(), + oauthUsername: z.string().optional(), + oauthAvatar: z.string().optional() +}) + +export type ProviderSettings = z.infer + +export const ProviderSchema = z.object({ + /** Provider ID */ + id: z.string(), + /** Associated preset provider ID (if any) */ + presetProviderId: z.string().optional(), + /** Display name */ + name: z.string(), + /** Description */ + description: z.string().optional(), + /** Base URL mapping (endpoint type → baseURL), sparse — only populated endpoints have entries */ + baseUrls: z.record(EndpointTypeSchema, z.url()).optional() as z.ZodOptional< + z.ZodType>> + >, + modelsApiUrls: z + .object({ + default: z.url().optional(), + embedding: z.url().optional(), + reranker: z.url().optional() + }) + .optional(), + /** Default text generation endpoint type */ + defaultChatEndpoint: EndpointTypeSchema.optional(), + /** API Keys (without actual key values) */ + apiKeys: z.array(RuntimeApiKeySchema), + /** Authentication type (no sensitive data) */ + authType: AuthTypeSchema, + /** Merged API feature support */ + apiFeatures: RuntimeApiFeaturesSchema, + /** Provider settings */ + settings: ProviderSettingsSchema, + /** Website links (official, apiKey, docs, models) */ + websites: ProviderWebsitesSchema.optional(), + /** How this provider's API expects reasoning parameters (e.g. 'openai-chat', 'anthropic', 'enable-thinking') */ + reasoningFormatType: z.string().optional(), + /** Whether this provider is enabled */ + isEnabled: z.boolean() +}) + +export type Provider = z.infer + +export const DEFAULT_API_FEATURES: RuntimeApiFeatures = { + arrayContent: true, + streamOptions: true, + developerRole: false, + serviceTier: false, + verbosity: false, + enableThinking: true +} + +export const DEFAULT_PROVIDER_SETTINGS: ProviderSettings = {} diff --git a/packages/shared/data/utils/modelMerger.ts b/packages/shared/data/utils/modelMerger.ts new file mode 100644 index 00000000000..c95e251c3f3 --- /dev/null +++ b/packages/shared/data/utils/modelMerger.ts @@ -0,0 +1,446 @@ +/** + * Model and Provider configuration merging utilities + * + * These utilities merge configurations from different sources with + * the correct priority order. + */ + +import type { + ProtoModelConfig, + ProtoProviderConfig, + ProtoProviderModelOverride, + ProtoProviderReasoningFormat, + ProtoReasoningSupport +} from '@cherrystudio/provider-catalog' +import type { Modality, ModelCapability, ReasoningEffort as ReasoningEffortType } from '@cherrystudio/provider-catalog' +import { EndpointType, ReasoningEffort } from '@cherrystudio/provider-catalog' +import * as z from 'zod' + +import type { Model, RuntimeModelPricing, RuntimeReasoning } from '../types/model' +import { createUniqueModelId } from '../types/model' +import type { Provider, ProviderSettings, RuntimeApiFeatures } from '../types/provider' +import { + ApiFeaturesSchema, + ApiKeyEntrySchema, + DEFAULT_API_FEATURES, + DEFAULT_PROVIDER_SETTINGS, + ProviderSettingsSchema +} from '../types/provider' + +export type { ProtoModelConfig as CatalogModel, ProtoProviderModelOverride as CatalogProviderModelOverride } + +export { DEFAULT_API_FEATURES, DEFAULT_PROVIDER_SETTINGS } + +/** + * Apply capability override to a base capability list + * + * @param base - Base capability list + * @param override - Override operations (add/remove/force) + * @returns Merged capability list + */ +export function applyCapabilityOverride( + base: ModelCapability[], + override: { add: ModelCapability[]; remove: ModelCapability[]; force: ModelCapability[] } | null | undefined +): ModelCapability[] { + if (!override) { + return [...base] + } + + // Force completely replaces the base + if (override.force && override.force.length > 0) { + return [...override.force] + } + + let result = [...base] + + // Add new capabilities + if (override.add.length) { + result = Array.from(new Set([...result, ...override.add])) + } + + // Remove capabilities + if (override.remove.length) { + const removeSet = new Set(override.remove) + result = result.filter((c) => !removeSet.has(c)) + } + + return result +} + +const UserProviderRowSchema = z.object({ + providerId: z.string(), + presetProviderId: z.string().nullish(), + name: z.string(), + baseUrls: z.record(z.string(), z.string()).nullish(), + defaultChatEndpoint: z.nativeEnum(EndpointType).nullish(), + apiKeys: z.array(ApiKeyEntrySchema.pick({ id: true, key: true, label: true, isEnabled: true })).nullish(), + authConfig: z.object({ type: z.string() }).catchall(z.unknown()).nullish(), + apiFeatures: ApiFeaturesSchema.nullish(), + providerSettings: ProviderSettingsSchema.partial().nullish(), + isEnabled: z.boolean().nullish(), + sortOrder: z.number().nullish() +}) + +type UserProviderRow = z.infer + +const UserModelRowSchema = z.object({ + providerId: z.string(), + modelId: z.string(), + presetModelId: z.string().nullable(), + name: z.string().nullish(), + description: z.string().nullish(), + group: z.string().nullish(), + capabilities: z.array(z.number()).nullish(), + inputModalities: z.array(z.number()).nullish(), + outputModalities: z.array(z.number()).nullish(), + endpointTypes: z.array(z.number()).nullish(), + customEndpointUrl: z.string().nullish(), + contextWindow: z.number().nullish(), + maxOutputTokens: z.number().nullish(), + supportsStreaming: z.boolean().nullish(), + reasoning: z.record(z.string(), z.unknown()).nullish(), + parameterSupport: z.record(z.string(), z.unknown()).nullish(), + isEnabled: z.boolean().nullish(), + isHidden: z.boolean().nullish(), + sortOrder: z.number().nullish(), + notes: z.string().nullish() +}) + +type UserModelRow = z.infer + +/** + * Merge model configurations from all sources + * + * Priority: userModel > catalogOverride > presetModel + * + * @param userModel - User model from SQLite (or null) + * @param catalogOverride - Catalog provider-model override (or null) + * @param presetModel - Preset model from catalog (or null) + * @param providerId - Provider ID for the result + * @returns Merged Model + */ +export function mergeModelConfig( + userModel: UserModelRow | null, + catalogOverride: ProtoProviderModelOverride | null, + presetModel: ProtoModelConfig | null, + providerId: string, + reasoningFormatType?: string +): Model { + // Case 1: Fully custom user model (no preset association) + if (userModel && !userModel.presetModelId) { + return { + id: createUniqueModelId(providerId, userModel.modelId), + providerId, + name: userModel.name ?? userModel.modelId, + description: userModel.description ?? undefined, + group: userModel.group ?? undefined, + capabilities: (userModel.capabilities ?? []) as ModelCapability[], + inputModalities: (userModel.inputModalities ?? undefined) as Modality[] | undefined, + outputModalities: (userModel.outputModalities ?? undefined) as Modality[] | undefined, + contextWindow: userModel.contextWindow ?? undefined, + maxOutputTokens: userModel.maxOutputTokens ?? undefined, + endpointTypes: (userModel.endpointTypes ?? undefined) as EndpointType[] | undefined, + supportsStreaming: userModel.supportsStreaming ?? true, + reasoning: userModel.reasoning as RuntimeReasoning | undefined, + isEnabled: userModel.isEnabled ?? true, + isHidden: userModel.isHidden ?? false + } + } + + // Case 2: Preset model (may have catalog override and user override) + if (!presetModel) { + throw new Error('Preset model not found for merge') + } + + const modelId = presetModel.id + + // Start from preset + let capabilities: ModelCapability[] = [...presetModel.capabilities] + let inputModalities: Modality[] | undefined = presetModel.inputModalities.length + ? [...presetModel.inputModalities] + : undefined + let outputModalities: Modality[] | undefined = presetModel.outputModalities.length + ? [...presetModel.outputModalities] + : undefined + let endpointTypes: EndpointType[] | undefined = undefined + let name = presetModel.name ?? presetModel.id + let description = presetModel.description + let contextWindow = presetModel.contextWindow + let maxOutputTokens = presetModel.maxOutputTokens + let maxInputTokens = presetModel.maxInputTokens + let reasoning: RuntimeReasoning | undefined + let pricing: RuntimeModelPricing | undefined + let replaceWith: string | undefined + + // Extract reasoning config from proto ReasoningSupport + provider's reasoning format type + if (presetModel.reasoning) { + reasoning = extractRuntimeReasoning(presetModel.reasoning, reasoningFormatType) + } + + // Extract pricing + if (presetModel.pricing) { + pricing = { + input: { + perMillionTokens: presetModel.pricing.input?.perMillionTokens ?? null, + currency: presetModel.pricing.input?.currency + }, + output: { + perMillionTokens: presetModel.pricing.output?.perMillionTokens ?? null, + currency: presetModel.pricing.output?.currency + }, + cacheRead: presetModel.pricing.cacheRead + ? { + perMillionTokens: presetModel.pricing.cacheRead.perMillionTokens ?? null, + currency: presetModel.pricing.cacheRead.currency + } + : undefined, + cacheWrite: presetModel.pricing.cacheWrite + ? { + perMillionTokens: presetModel.pricing.cacheWrite.perMillionTokens ?? null, + currency: presetModel.pricing.cacheWrite.currency + } + : undefined + } + } + + // Apply catalog override + if (catalogOverride) { + if (catalogOverride.capabilities) { + capabilities = applyCapabilityOverride(capabilities, catalogOverride.capabilities) + } + if (catalogOverride.limits?.contextWindow != null) { + contextWindow = catalogOverride.limits.contextWindow + } + if (catalogOverride.limits?.maxOutputTokens != null) { + maxOutputTokens = catalogOverride.limits.maxOutputTokens + } + if (catalogOverride.limits?.maxInputTokens != null) { + maxInputTokens = catalogOverride.limits.maxInputTokens + } + if (catalogOverride.reasoning) { + const overrideReasoning = extractRuntimeReasoning(catalogOverride.reasoning, reasoningFormatType) + reasoning = { + ...overrideReasoning, + thinkingTokenLimits: overrideReasoning.thinkingTokenLimits ?? reasoning?.thinkingTokenLimits, + interleaved: overrideReasoning.interleaved ?? reasoning?.interleaved + } + } + if (catalogOverride.endpointTypes.length) { + endpointTypes = [...catalogOverride.endpointTypes] + } + if (catalogOverride.inputModalities.length) { + inputModalities = [...catalogOverride.inputModalities] + } + if (catalogOverride.outputModalities.length) { + outputModalities = [...catalogOverride.outputModalities] + } + if (catalogOverride.replaceWith) { + replaceWith = catalogOverride.replaceWith + } + } + + // Apply user override + if (userModel) { + if (userModel.capabilities) { + capabilities = [...userModel.capabilities] as ModelCapability[] + } + if (userModel.endpointTypes) { + endpointTypes = [...userModel.endpointTypes] as EndpointType[] + } + if (userModel.inputModalities) { + inputModalities = [...userModel.inputModalities] as Modality[] + } + if (userModel.outputModalities) { + outputModalities = [...userModel.outputModalities] as Modality[] + } + if (userModel.name) { + name = userModel.name + } + if (userModel.description) { + description = userModel.description + } + if (userModel.contextWindow != null) { + contextWindow = userModel.contextWindow + } + if (userModel.maxOutputTokens != null) { + maxOutputTokens = userModel.maxOutputTokens + } + if (userModel.reasoning) { + reasoning = userModel.reasoning as RuntimeReasoning + } + } + + return { + id: createUniqueModelId(providerId, modelId), + providerId, + // Use api_model_id from catalog override if available, otherwise fall back to model id + apiModelId: catalogOverride?.apiModelId, + name, + description, + group: userModel?.group ?? undefined, + family: presetModel.family, + ownedBy: presetModel.ownedBy, + capabilities, + inputModalities, + outputModalities, + contextWindow, + maxOutputTokens, + maxInputTokens, + endpointTypes, + supportsStreaming: userModel?.supportsStreaming ?? true, + reasoning, + pricing, + isEnabled: userModel?.isEnabled ?? !(catalogOverride?.disabled ?? false), + isHidden: userModel?.isHidden ?? false, + replaceWith: replaceWith ? createUniqueModelId(providerId, replaceWith) : undefined + } +} + +// ═══════════════════════════════════════════════════════════════════════════════ +// Provider Merge Utilities +// ═══════════════════════════════════════════════════════════════════════════════ + +/** + * Merge provider configurations + * + * Priority: userProvider > presetProvider + * + * @param userProvider - User provider from SQLite (or null) + * @param presetProvider - Preset provider from catalog (or null) + * @returns Merged Provider + */ +export function mergeProviderConfig( + userProvider: UserProviderRow | null, + presetProvider: ProtoProviderConfig | null +): Provider { + if (!userProvider && !presetProvider) { + throw new Error('At least one of userProvider or presetProvider must be provided') + } + + const providerId = userProvider?.providerId ?? presetProvider!.id + + // Merge baseUrls — proto uses map, convert to Record + const presetBaseUrls: Record = {} + if (presetProvider?.baseUrls) { + for (const [k, v] of Object.entries(presetProvider.baseUrls)) { + presetBaseUrls[k] = v + } + } + const baseUrls: Record = { + ...presetBaseUrls, + ...userProvider?.baseUrls + } + + // Merge API features (catalog now uses the same field names) + const apiFeatures: RuntimeApiFeatures = { + ...DEFAULT_API_FEATURES, + ...presetProvider?.apiFeatures, + ...userProvider?.apiFeatures + } + + // Merge settings + const settings: ProviderSettings = { + ...DEFAULT_PROVIDER_SETTINGS, + ...userProvider?.providerSettings + } + + // Process API keys (strip actual key values for security) + const apiKeys = + userProvider?.apiKeys?.map((k) => ({ + id: k.id, + label: k.label, + isEnabled: k.isEnabled + })) ?? [] + + // Determine auth type + let authType: Provider['authType'] = 'api-key' + if (userProvider?.authConfig?.type) { + authType = userProvider.authConfig.type as Provider['authType'] + } + + return { + id: providerId, + presetProviderId: userProvider?.presetProviderId ?? undefined, + name: userProvider?.name ?? presetProvider?.name ?? providerId, + description: presetProvider?.description, + baseUrls, + defaultChatEndpoint: userProvider?.defaultChatEndpoint ?? presetProvider?.defaultChatEndpoint, + apiKeys, + authType, + apiFeatures, + settings, + reasoningFormatType: extractReasoningFormatType(presetProvider?.reasoningFormat), + isEnabled: userProvider?.isEnabled ?? true + } +} + +// ═══════════════════════════════════════════════════════════════════════════════ +// Helper Functions +// ═══════════════════════════════════════════════════════════════════════════════ + +/** Map proto ProviderReasoningFormat.format.case to runtime reasoning type string */ +const REASONING_FORMAT_CASE_TO_TYPE: Record = { + openaiChat: 'openai-chat', + openaiResponses: 'openai-responses', + anthropic: 'anthropic', + gemini: 'gemini', + openrouter: 'openrouter', + enableThinking: 'enable-thinking', + thinkingType: 'thinking-type', + dashscope: 'dashscope', + selfHosted: 'self-hosted' +} + +/** Default effort levels per reasoning format type (when not specified in catalog) */ +const DEFAULT_EFFORTS: Record = { + 'openai-chat': [ + ReasoningEffort.NONE, + ReasoningEffort.MINIMAL, + ReasoningEffort.LOW, + ReasoningEffort.MEDIUM, + ReasoningEffort.HIGH + ], + 'openai-responses': [ + ReasoningEffort.NONE, + ReasoningEffort.MINIMAL, + ReasoningEffort.LOW, + ReasoningEffort.MEDIUM, + ReasoningEffort.HIGH + ], + anthropic: [], + gemini: [ReasoningEffort.LOW, ReasoningEffort.MEDIUM, ReasoningEffort.HIGH], + 'enable-thinking': [ReasoningEffort.NONE, ReasoningEffort.LOW, ReasoningEffort.MEDIUM, ReasoningEffort.HIGH], + 'thinking-type': [ReasoningEffort.NONE, ReasoningEffort.AUTO] +} + +/** + * Extract runtime reasoning type string from proto ProviderReasoningFormat + */ +function extractReasoningFormatType(format: ProtoProviderReasoningFormat | undefined): string | undefined { + if (!format?.format.case) return undefined + return REASONING_FORMAT_CASE_TO_TYPE[format.format.case] +} + +/** + * Convert proto ReasoningSupport to runtime RuntimeReasoning + * The `type` comes from the provider's reasoningFormat, not from the model. + */ +function extractRuntimeReasoning( + reasoning: ProtoReasoningSupport, + reasoningFormatType: string | undefined +): RuntimeReasoning { + const type = reasoningFormatType ?? '' + + // Get supported efforts, with fallback based on provider format type + let supportedEfforts: ReasoningEffortType[] = [...(reasoning.supportedEfforts ?? [])] + if (supportedEfforts.length === 0) { + supportedEfforts = DEFAULT_EFFORTS[type] ?? [] + } + + return { + type, + supportedEfforts, + thinkingTokenLimits: reasoning.thinkingTokenLimits, + interleaved: reasoning.interleaved + } +} From eaf9af3f4c9e8a414e305b03a27b2bc7f3abb3c0 Mon Sep 17 00:00:00 2001 From: jidan745le <420511176@qq.com> Date: Sat, 4 Apr 2026 18:12:07 +0800 Subject: [PATCH 02/29] feat(data): migrate provider/model data and finalize provider APIs Add v2 provider-model migration mappings and registration, then align provider handlers/services with lifecycle DI and API contracts so migration generation and node typecheck both pass. Signed-off-by: jidan745le <420511176@qq.com> Made-with: Cursor --- docs/en/references/data/provider-catalog.md | 474 ++++++++++ electron.vite.config.ts | 2 + .../sqlite-drizzle/0007_lumpy_reavers.sql | 55 ++ package.json | 4 +- packages/shared/data/api/schemas/index.ts | 4 + pnpm-lock.yaml | 834 ++++++++++++++++-- src/main/data/api/handlers/index.ts | 6 + src/main/data/api/handlers/models.ts | 59 ++ src/main/data/api/handlers/providers.ts | 105 +++ src/main/data/db/schemas/userModel.ts | 179 ++++ src/main/data/db/schemas/userProvider.ts | 104 +++ .../data/migration/v2/core/MigrationEngine.ts | 6 + .../v2/migrators/ProviderModelMigrator.ts | 192 ++++ src/main/data/migration/v2/migrators/index.ts | 3 + .../mappings/ProviderModelMappings.ts | 425 +++++++++ src/main/data/services/ModelService.ts | 352 ++++++++ .../data/services/ProviderCatalogService.ts | 584 ++++++++++++ src/main/data/services/ProviderService.ts | 369 ++++++++ tsconfig.json | 4 +- tsconfig.node.json | 2 + tsconfig.web.json | 2 + 21 files changed, 3713 insertions(+), 52 deletions(-) create mode 100644 docs/en/references/data/provider-catalog.md create mode 100644 migrations/sqlite-drizzle/0007_lumpy_reavers.sql create mode 100644 src/main/data/api/handlers/models.ts create mode 100644 src/main/data/api/handlers/providers.ts create mode 100644 src/main/data/db/schemas/userModel.ts create mode 100644 src/main/data/db/schemas/userProvider.ts create mode 100644 src/main/data/migration/v2/migrators/ProviderModelMigrator.ts create mode 100644 src/main/data/migration/v2/migrators/mappings/ProviderModelMappings.ts create mode 100644 src/main/data/services/ModelService.ts create mode 100644 src/main/data/services/ProviderCatalogService.ts create mode 100644 src/main/data/services/ProviderService.ts diff --git a/docs/en/references/data/provider-catalog.md b/docs/en/references/data/provider-catalog.md new file mode 100644 index 00000000000..c85999a37ca --- /dev/null +++ b/docs/en/references/data/provider-catalog.md @@ -0,0 +1,474 @@ +# Provider Catalog Reference + +This document describes the Provider/Model catalog system architecture, schemas, and data flows. + +## Overview + +The catalog system manages AI model and provider configurations with a three-layer merge architecture: + +1. **Preset Layer** (read-only, bundled in app) - Catalog definitions +2. **Override Layer** (read-only) - Provider-specific model overrides +3. **User Layer** (SQLite, writable) - User customizations + +``` +┌─────────────────────────────────────────────────────────────────────────────────┐ +│ Data Layer Architecture │ +├─────────────────────────────────────────────────────────────────────────────────┤ +│ │ +│ Preset Layer (Read-only) User Layer (SQLite, Writable) │ +│ ════════════════════════ ═══════════════════════════ │ +│ │ +│ providers.json user_provider │ +│ • Provider configurations • Endpoint overrides │ +│ • Endpoint mappings • Multi API Key (1:N) │ +│ • API compatibility • API features override │ +│ │ +│ models.json user_model (merged table) │ +│ • Base model definitions • presetModelId → override │ +│ • Capabilities, modalities • presetModelId null → custom │ +│ • Context windows, pricing • Source tracking │ +│ │ +│ provider-models.json │ +│ • Provider-model mappings │ +│ • Provider-level overrides │ +│ • Variant configurations │ +│ │ +└─────────────────────────────────────────────────────────────────────────────────┘ +``` + +## Merge Priority + +When resolving a model or provider configuration: + +**Models**: `user_model` > `provider-models.json` > `models.json` + +**Providers**: `user_provider` > `providers.json` + +--- + +## Preset Schemas + +Location: `packages/provider-catalog/src/schemas/` + +### Provider Schema (`provider.ts`) + +Defines how to connect to AI service providers. + +```typescript +// Endpoint types encode format information +// CHAT_COMPLETIONS → OpenAI format +// MESSAGES → Anthropic format +// RESPONSES → OpenAI Responses API +// GENERATE_CONTENT → Gemini format + +const EndpointTypeSchema = z.enum([ + // Text generation + 'CHAT_COMPLETIONS', // OpenAI /v1/chat/completions + 'TEXT_COMPLETIONS', // OpenAI /v1/completions (legacy) + 'MESSAGES', // Anthropic /v1/messages + 'RESPONSES', // OpenAI /v1/responses + 'GENERATE_CONTENT', // Gemini /v1beta/models/{model}:generateContent + + // Embeddings + 'EMBEDDINGS', // /v1/embeddings + 'RERANK', // /v1/rerank + + // Images + 'IMAGE_GENERATION', // /v1/images/generations + 'IMAGE_EDIT', // /v1/images/edits + + // Audio + 'AUDIO_TRANSCRIPTION', // /v1/audio/transcriptions + 'AUDIO_TRANSLATION', // /v1/audio/translations + 'TEXT_TO_SPEECH', // /v1/audio/speech + + // Video + 'VIDEO_GENERATION' +]) + +const ProviderConfigSchema = z.object({ + id: z.string(), // Provider ID + name: z.string(), // Display name + description: z.string().optional(), + + // Endpoint configuration: type → full URL + endpoints: z.record(z.string(), z.string().url()), + // Example: + // { + // 'CHAT_COMPLETIONS': 'https://api.openai.com/v1/chat/completions', + // 'EMBEDDINGS': 'https://api.openai.com/v1/embeddings' + // } + + default_chat_endpoint: EndpointTypeSchema.optional(), + + api_compatibility: z.object({ + supports_array_content: z.boolean().optional(), // default: true + supports_stream_options: z.boolean().optional(), // default: true + supports_developer_role: z.boolean().optional(), // default: true + supports_service_tier: z.boolean().optional(), // default: false + supports_thinking_control: z.boolean().optional() // default: true + }).optional(), + + website: z.string().url().optional(), + models_api_url: z.string().url().optional(), // Models list API + metadata: z.record(z.string(), z.any()).optional() +}) +``` + +### Model Schema (`model.ts`) + +Defines model capabilities and configurations. + +```typescript +const ModelCapabilityTypeSchema = z.enum([ + 'FUNCTION_CALL', // Function calling + 'REASONING', // Extended thinking + 'IMAGE_RECOGNITION', // Vision understanding + 'IMAGE_GENERATION', // Image creation + 'AUDIO_RECOGNITION', // Audio understanding + 'AUDIO_GENERATION', // Speech synthesis + 'EMBEDDING', // Vector embeddings + 'RERANK', // Result reranking + 'AUDIO_TRANSCRIPT', // Speech-to-text + 'VIDEO_RECOGNITION', // Video understanding + 'VIDEO_GENERATION', // Video creation + 'STRUCTURED_OUTPUT', // JSON mode + 'FILE_INPUT', // File attachments + 'WEB_SEARCH', // Built-in search + 'CODE_EXECUTION', // Code sandbox + 'FILE_SEARCH', // File search + 'COMPUTER_USE' // Computer control +]) + +const ModalitySchema = z.enum(['TEXT', 'VISION', 'AUDIO', 'VIDEO', 'VECTOR']) + +const ModelConfigSchema = z.object({ + id: z.string(), // Model ID for API calls + name: z.string().optional(), // Display name + description: z.string().optional(), + + capabilities: z.array(ModelCapabilityTypeSchema).optional(), + input_modalities: z.array(ModalitySchema).optional(), + output_modalities: z.array(ModalitySchema).optional(), + + context_window: z.number().optional(), + max_output_tokens: z.number().optional(), + max_input_tokens: z.number().optional(), + + pricing: ModelPricingSchema.optional(), + reasoning: ReasoningSchema.optional(), + parameters: ParameterSupportSchema.optional(), + + family: z.string().optional(), // e.g., "GPT-4", "Claude 3" + publisher: z.string().optional(), // e.g., "anthropic", "openai" + open_weights: z.boolean().optional(), // Weights publicly available + alias: z.array(z.string()).optional(), // Date version aliases + + metadata: z.record(z.string(), z.any()).optional() +}) +``` + +### Provider-Models Schema (`provider-models.ts`) + +Defines provider-specific model overrides. + +```typescript +const CapabilityOverrideSchema = z.object({ + add: z.array(ModelCapabilityTypeSchema).optional(), // Add capabilities + remove: z.array(ModelCapabilityTypeSchema).optional(), // Remove capabilities + force: z.array(ModelCapabilityTypeSchema).optional() // Complete replacement +}) + +const ProviderModelOverrideSchema = z.object({ + provider_id: z.string(), + model_id: z.string(), + + // Variant identifier for same model with different configurations + // Examples: 'free', 'thinking', 'nitro', 'search' + model_variant: z.string().optional(), + + capabilities: CapabilityOverrideSchema.optional(), + limits: z.object({ + context_window: z.number().optional(), + max_output_tokens: z.number().optional(), + max_input_tokens: z.number().optional(), + rate_limit: z.number().optional() // Requests per minute + }).optional(), + + pricing: ModelPricingSchema.partial().optional(), + reasoning: ReasoningSchema.optional(), + parameters: ParameterSupportSchema.partial().optional(), + + // Endpoint type overrides (when model uses different endpoints than provider default) + endpoint_types: z.array(EndpointTypeSchema).optional(), + // Modality overrides (when provider supports different modalities than base model) + input_modalities: z.array(ModalitySchema).optional(), + output_modalities: z.array(ModalitySchema).optional(), + + disabled: z.boolean().optional(), + replace_with: z.string().optional(), + + reason: z.string().optional(), // Override reason + priority: z.number().default(0) // Higher = takes precedence +}) +``` + +--- + +## Runtime Types + +Location: `packages/shared/data/types/` + +### UniqueModelId + +Format: `providerId::modelId` + +```typescript +type UniqueModelId = `${string}::${string}` + +// Create: createUniqueModelId('anthropic', 'claude-3-5-sonnet') +// → 'anthropic::claude-3-5-sonnet' + +// Parse: parseUniqueModelId('anthropic::claude-3-5-sonnet') +// → { providerId: 'anthropic', modelId: 'claude-3-5-sonnet' } +``` + +Uses `::` separator to avoid conflicts with model IDs containing `:` (e.g., `openrouter:anthropic/claude-3`). + +### RuntimeModel + +The merged "final state" model configuration for consumers. + +```typescript +// Type-safe union types (mirroring catalog Zod enums) +type Modality = 'TEXT' | 'VISION' | 'AUDIO' | 'VIDEO' | 'VECTOR' +type EndpointType = + | 'CHAT_COMPLETIONS' | 'TEXT_COMPLETIONS' | 'MESSAGES' + | 'RESPONSES' | 'GENERATE_CONTENT' + | 'EMBEDDINGS' | 'RERANK' + | 'IMAGE_GENERATION' | 'IMAGE_EDIT' + | 'AUDIO_TRANSCRIPTION' | 'AUDIO_TRANSLATION' | 'TEXT_TO_SPEECH' + | 'VIDEO_GENERATION' + +interface RuntimeReasoning { + type: string // 'openai-chat', 'anthropic', 'gemini', etc. + supportedEfforts: string[] + defaultEffort?: string + thinkingTokenLimits?: { min?: number; max?: number; default?: number } + interleaved?: boolean // Supports interleaved thinking output +} + +interface RuntimeModel { + uniqueId: UniqueModelId // "anthropic::claude-3-5-sonnet" + id: string // "claude-3-5-sonnet" + providerId: string // "anthropic" + + name: string + description?: string + group?: string // UI grouping + family?: string // "Claude 3" + ownedBy?: string + + capabilities: ModelCapability[] + inputModalities?: Modality[] // Supported input: TEXT, VISION, AUDIO, VIDEO + outputModalities?: Modality[] // Supported output: TEXT, VISION, AUDIO, VIDEO, VECTOR + + contextWindow?: number + maxOutputTokens?: number + maxInputTokens?: number + + endpointTypes?: EndpointType[] // Supported endpoint types (array, model may support multiple) + supportsStreaming: boolean + + reasoning?: RuntimeReasoning + parameters?: RuntimeParameterSupport + pricing?: RuntimeModelPricing + + isEnabled: boolean + isHidden: boolean + replaceWith?: UniqueModelId +} +``` + +### RuntimeProvider + +The merged "final state" provider configuration. + +```typescript +interface RuntimeProvider { + id: string + source: 'preset' | 'user' | 'merged' + presetProviderId?: string + + name: string + description?: string + + endpoints: Record + defaultChatEndpoint?: string + + apiKeys: RuntimeApiKey[] + activeApiKeyId?: string + authType: 'api-key' | 'oauth' | 'iam-aws' | 'iam-gcp' | 'iam-azure' + + apiCompatibility: RuntimeApiCompatibility + settings: RuntimeProviderSettings + + isEnabled: boolean + isAuthenticated: boolean +} +``` + +--- + +## User Database Schemas + +Location: `src/main/data/db/schemas/` + +### user_provider Table + +Stores user's provider configurations. + +| Column | Type | Description | +|--------|------|-------------| +| id | UUID | Primary key | +| providerId | TEXT | User-defined unique ID | +| presetProviderId | TEXT | Links to catalog preset | +| name | TEXT | Display name | +| endpoints | JSON | Endpoint URL overrides | +| defaultChatEndpoint | TEXT | Default text generation endpoint | +| apiKeys | JSON | Array of ApiKeyEntry | +| authConfig | JSON | Authentication configuration | +| apiCompatibility | JSON | API compatibility overrides | +| providerSettings | JSON | Provider-specific settings | +| isEnabled | BOOLEAN | Whether enabled | +| sortOrder | INTEGER | UI ordering | + +**Design principle**: One provider instance = One API host (1:1 relationship) + +### user_model Table + +Stores all user models with fully resolved configurations. Capabilities are resolved once at add-time from catalog, so no runtime merge is needed. + +| Column | Type | Description | +|--------|------|-------------| +| providerId | TEXT | Provider ID (part of PK) | +| modelId | TEXT | Model ID (part of PK) | +| presetModelId | TEXT | Traceability marker (which preset this came from) | +| name | TEXT | Display name | +| description | TEXT | Description | +| group | TEXT | UI grouping | +| capabilities | JSON | Complete capability list (resolved at add time) | +| inputModalities | JSON | Supported input modalities (e.g., TEXT, VISION, AUDIO) | +| outputModalities | JSON | Supported output modalities (e.g., TEXT, VISION, VECTOR) | +| endpointTypes | JSON | Endpoint type overrides (array) | +| customEndpointUrl | TEXT | Complete URL override | +| contextWindow | INTEGER | Context window override | +| maxOutputTokens | INTEGER | Max output override | +| supportsStreaming | BOOLEAN | Streaming support | +| reasoning | JSON | Reasoning configuration (includes `interleaved` flag) | +| parameters | JSON | Parameter support | +| isEnabled | BOOLEAN | Whether enabled | +| isHidden | BOOLEAN | Whether hidden in lists | +| sortOrder | INTEGER | UI ordering | + +**Note**: `presetModelId` is a traceability marker only — it records which preset model was used as the template, but is not used for runtime merging. + +--- + +## Merge Utilities + +Location: `packages/shared/data/utils/modelMerger.ts` + +### mergeModelConfig + +Merges model configurations with proper priority. + +```typescript +function mergeModelConfig( + userModel: UserModel | null, + catalogOverride: CatalogProviderModelOverride | null, + presetModel: CatalogModel | null, + providerId: string +): RuntimeModel + +// Priority: userModel > catalogOverride > presetModel +``` + +### mergeProviderConfig + +Merges provider configurations. + +```typescript +function mergeProviderConfig( + userProvider: UserProvider | null, + presetProvider: CatalogProvider | null +): RuntimeProvider + +// Priority: userProvider > presetProvider +``` + +### applyCapabilityOverride + +Applies catalog provider-model capability modifications (not user-level). + +```typescript +function applyCapabilityOverride( + base: string[], + override: { add?: string[]; remove?: string[]; force?: string[] } +): string[] + +// 'force' completely replaces base +// Otherwise: add new, then remove specified +``` + +--- + +## Model ID Variants + +### Variant Types + +| Type | Example | Handling | +|------|---------|----------| +| Pricing variant | `:free`, `:nitro`, `-free` | Separate provider-models entry | +| Capability variant | `-thinking`, `-search` | capabilities.add in provider-models | +| Date version | `-20251101` | alias array in models.json | +| Provider prefix | `anthropic/`, `google/` | Strip during import | + +### Normalization Rules + +1. Strip provider prefixes: `anthropic/claude-3` → `claude-3` +2. Strip pricing suffixes: `claude-3:free` → `claude-3` (with variant entry) +3. Preserve capability variants: `claude-3-thinking` → separate handling +4. Track date versions: `gpt-4-turbo-2024-04-09` → in `alias` array + +--- + +## Data Files + +| File | Description | +|------|-------------| +| `packages/provider-catalog/data/providers.json` | Provider configurations | +| `packages/provider-catalog/data/models.json` | Base model definitions | +| `packages/provider-catalog/data/provider-models.json` | Provider-model overrides | +| `packages/provider-catalog/data/openrouter-models.json` | OpenRouter import data | +| `packages/provider-catalog/data/aihubmix-models.json` | AIHubMix import data | +| `packages/provider-catalog/data/modelsdev-models.json` | models.dev import data | + +--- + +## API Compatibility Defaults + +| Feature | Default | Description | +|---------|---------|-------------| +| `supports_array_content` | true | Array format for content | +| `supports_stream_options` | true | stream_options parameter | +| `supports_developer_role` | true | Developer role in messages | +| `supports_service_tier` | false | service_tier parameter | +| `supports_thinking_control` | true | Thinking control parameters | + +--- + +## See Also + +- [Data Management Overview](./README.md) - System selection and patterns +- [Catalog Web UI](../../packages/provider-catalog/web/) - Review and edit interface diff --git a/electron.vite.config.ts b/electron.vite.config.ts index 1e039ba596c..e55a9d9793c 100644 --- a/electron.vite.config.ts +++ b/electron.vite.config.ts @@ -36,6 +36,7 @@ export default defineConfig({ '@logger': resolve('src/main/services/LoggerService'), '@mcp-trace/trace-core': resolve('packages/mcp-trace/trace-core'), '@mcp-trace/trace-node': resolve('packages/mcp-trace/trace-node'), + '@cherrystudio/provider-catalog': resolve('packages/provider-catalog/src'), '@test-mocks': resolve('tests/__mocks__') } }, @@ -116,6 +117,7 @@ export default defineConfig({ '@cherrystudio/ai-core': resolve('packages/aiCore/src'), '@cherrystudio/extension-table-plus': resolve('packages/extension-table-plus/src'), '@cherrystudio/ai-sdk-provider': resolve('packages/ai-sdk-provider/src'), + '@cherrystudio/provider-catalog': resolve('packages/provider-catalog/src'), '@cherrystudio/ui/icons': resolve('packages/ui/src/components/icons'), '@cherrystudio/ui': resolve('packages/ui/src'), '@test-mocks': resolve('tests/__mocks__') diff --git a/migrations/sqlite-drizzle/0007_lumpy_reavers.sql b/migrations/sqlite-drizzle/0007_lumpy_reavers.sql new file mode 100644 index 00000000000..cbc9be07109 --- /dev/null +++ b/migrations/sqlite-drizzle/0007_lumpy_reavers.sql @@ -0,0 +1,55 @@ +CREATE TABLE `user_model` ( + `provider_id` text NOT NULL, + `model_id` text NOT NULL, + `preset_model_id` text, + `name` text, + `description` text, + `group` text, + `capabilities` text, + `input_modalities` text, + `output_modalities` text, + `endpoint_types` text, + `custom_endpoint_url` text, + `context_window` integer, + `max_output_tokens` integer, + `supports_streaming` integer, + `reasoning` text, + `parameters` text, + `pricing` text, + `is_enabled` integer DEFAULT true, + `is_hidden` integer DEFAULT false, + `is_deprecated` integer DEFAULT false, + `sort_order` integer DEFAULT 0, + `notes` text, + `user_overrides` text, + `created_at` integer, + `updated_at` integer, + PRIMARY KEY(`provider_id`, `model_id`) +); +--> statement-breakpoint +CREATE INDEX `user_model_preset_idx` ON `user_model` (`preset_model_id`);--> statement-breakpoint +CREATE INDEX `user_model_provider_enabled_idx` ON `user_model` (`provider_id`,`is_enabled`);--> statement-breakpoint +CREATE INDEX `user_model_provider_sort_idx` ON `user_model` (`provider_id`,`sort_order`);--> statement-breakpoint +CREATE TABLE `user_provider` ( + `id` text PRIMARY KEY NOT NULL, + `provider_id` text NOT NULL, + `preset_provider_id` text, + `name` text NOT NULL, + `base_urls` text, + `models_api_urls` text, + `default_chat_endpoint` text, + `api_keys` text DEFAULT '[]', + `auth_config` text, + `api_features` text, + `provider_settings` text, + `reasoning_format_type` text, + `websites` text, + `is_enabled` integer DEFAULT true, + `sort_order` integer DEFAULT 0, + `created_at` integer, + `updated_at` integer +); +--> statement-breakpoint +CREATE UNIQUE INDEX `user_provider_providerId_unique` ON `user_provider` (`provider_id`);--> statement-breakpoint +CREATE INDEX `user_provider_preset_idx` ON `user_provider` (`preset_provider_id`);--> statement-breakpoint +CREATE INDEX `user_provider_enabled_sort_idx` ON `user_provider` (`is_enabled`,`sort_order`); \ No newline at end of file diff --git a/package.json b/package.json index 7c455f0eaa3..f3434379d68 100644 --- a/package.json +++ b/package.json @@ -94,6 +94,7 @@ "@napi-rs/system-ocr": "1.0.2", "@paymoapp/electron-shutdown-handler": "1.1.2", "cron-parser": "^5.0.8", + "drizzle-zod": "^0.8.3", "express": "5.1.0", "font-list": "2.0.0", "graceful-fs": "4.2.11", @@ -161,6 +162,7 @@ "@cherrystudio/embedjs-utils": "0.1.31", "@cherrystudio/extension-table-plus": "workspace:^", "@cherrystudio/openai": "6.15.0", + "@cherrystudio/provider-catalog": "workspace:*", "@cherrystudio/ui": "workspace:*", "@codemirror/lang-json": "6.0.2", "@codemirror/lint": "6.9.5", @@ -263,8 +265,8 @@ "@types/react-dom": "^19.2.3", "@types/react-transition-group": "^4.4.12", "@types/react-window": "^1", - "@types/stream-json": "^1", "@types/semver": "^7.7.1", + "@types/stream-json": "^1", "@types/swagger-jsdoc": "^6", "@types/swagger-ui-express": "^4.1.8", "@types/tinycolor2": "^1", diff --git a/packages/shared/data/api/schemas/index.ts b/packages/shared/data/api/schemas/index.ts index 9c8fa746fa6..c3bcadce11b 100644 --- a/packages/shared/data/api/schemas/index.ts +++ b/packages/shared/data/api/schemas/index.ts @@ -26,6 +26,8 @@ import type { KnowledgeSchemas } from './knowledges' import type { MCPServerSchemas } from './mcpServers' import type { MessageSchemas } from './messages' import type { MiniappSchemas } from './miniapps' +import type { ModelSchemas } from './models' +import type { ProviderSchemas } from './providers' import type { TestSchemas } from './test' import type { TopicSchemas } from './topics' import type { TranslateSchemas } from './translate' @@ -47,6 +49,8 @@ export type ApiSchemas = AssertValidSchemas< TestSchemas & TopicSchemas & MessageSchemas & + ModelSchemas & + ProviderSchemas & TranslateSchemas & FileProcessingSchemas & MCPServerSchemas & diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ff067eb5836..92fc5fbfebb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -110,6 +110,9 @@ importers: cron-parser: specifier: ^5.0.8 version: 5.5.0 + drizzle-zod: + specifier: ^0.8.3 + version: 0.8.3(drizzle-orm@0.44.7(@libsql/client@0.14.0)(@opentelemetry/api@1.9.0))(zod@4.3.4) express: specifier: 5.1.0 version: 5.1.0 @@ -306,6 +309,9 @@ importers: '@cherrystudio/openai': specifier: 6.15.0 version: 6.15.0(ws@8.20.0)(zod@4.3.4) + '@cherrystudio/provider-catalog': + specifier: workspace:* + version: link:packages/provider-catalog '@cherrystudio/ui': specifier: workspace:* version: link:packages/ui @@ -1406,10 +1412,53 @@ importers: version: 12.1.1(eslint@9.39.2(jiti@2.6.1)) eslint-plugin-unused-imports: specifier: ^4.1.4 - version: 4.3.0(@typescript-eslint/eslint-plugin@8.51.0(@typescript-eslint/parser@8.51.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.2))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.2))(eslint@9.39.2(jiti@2.6.1)) + version: 4.3.0(@typescript-eslint/eslint-plugin@8.51.0(@typescript-eslint/parser@8.51.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)) tsdown: specifier: ^0.20.3 - version: 0.20.3(@typescript/native-preview@7.0.0-dev.20260204.1)(typescript@5.9.2) + version: 0.20.3(@typescript/native-preview@7.0.0-dev.20260204.1)(typescript@5.9.3) + + packages/provider-catalog: + dependencies: + '@bufbuild/protobuf': + specifier: ^2.11.0 + version: 2.11.0 + class-variance-authority: + specifier: ^0.7.1 + version: 0.7.1 + json-schema: + specifier: ^0.4.0 + version: 0.4.0 + lucide-react: + specifier: ^0.563.0 + version: 0.563.0(react@19.2.3) + devDependencies: + '@bufbuild/buf': + specifier: ^1.66.0 + version: 1.67.0 + '@bufbuild/protoc-gen-es': + specifier: ^2.11.0 + version: 2.11.0(@bufbuild/protobuf@2.11.0) + '@types/json-schema': + specifier: ^7.0.15 + version: 7.0.15 + '@types/node': + specifier: ^24.10.2 + version: 24.10.4 + dotenv: + specifier: ^17.2.3 + version: 17.4.0 + tsdown: + specifier: ^0.16.6 + version: 0.16.8(@typescript/native-preview@7.0.0-dev.20260204.1)(typescript@5.9.3) + typescript: + specifier: ^5.9.3 + version: 5.9.3 + vitest: + specifier: ^4.0.13 + version: 4.1.2(@opentelemetry/api@1.9.0)(@types/node@24.10.4)(@vitest/ui@3.2.4(vitest@3.2.4))(jsdom@26.1.0)(msw@2.12.7(@types/node@24.10.4)(typescript@5.9.3))(rolldown-vite@7.3.0(@types/node@24.10.4)(esbuild@0.25.12)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2)) + zod: + specifier: ^4.1.12 + version: 4.3.6 packages/ui: dependencies: @@ -2115,24 +2164,28 @@ packages: engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] + libc: [musl] '@biomejs/cli-linux-arm64@2.2.4': resolution: {integrity: sha512-M/Iz48p4NAzMXOuH+tsn5BvG/Jb07KOMTdSVwJpicmhN309BeEyRyQX+n1XDF0JVSlu28+hiTQ2L4rZPvu7nMw==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] + libc: [glibc] '@biomejs/cli-linux-x64-musl@2.2.4': resolution: {integrity: sha512-m41nFDS0ksXK2gwXL6W6yZTYPMH0LughqbsxInSKetoH6morVj43szqKx79Iudkp8WRT5SxSh7qVb8KCUiewGg==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] + libc: [musl] '@biomejs/cli-linux-x64@2.2.4': resolution: {integrity: sha512-orr3nnf2Dpb2ssl6aihQtvcKtLySLta4E2UcXdp7+RTa7mfJjBgIsbS0B9GC8gVu0hjOu021aU8b3/I1tn+pVQ==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] + libc: [glibc] '@biomejs/cli-win32-arm64@2.2.4': resolution: {integrity: sha512-NXnfTeKHDFUWfxAefa57DiGmu9VyKi0cDqFpdI+1hJWQjGJhJutHPX0b5m+eXvTKOaf+brU+P0JrQAZMb5yYaQ==} @@ -2161,9 +2214,72 @@ packages: openai: ^4.62.1 zod: ^3.23.8 + '@bufbuild/buf-darwin-arm64@1.67.0': + resolution: {integrity: sha512-9h/1E2FNCSIt9m4wriGiXt8gHrg8VBOOpmUPVr68axZxb17krPQrIZBPsx05yNpbyvSrPj26/jO2aoqpZsG1vw==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + + '@bufbuild/buf-darwin-x64@1.67.0': + resolution: {integrity: sha512-9kNu0JBR+TQvxCD6NBooy03g8sLNZGEd0umkWHzdO/05HuV/J6GecMGx1kJ2MYlZQHM4/MljfIuYQUblP1nP4A==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + + '@bufbuild/buf-linux-aarch64@1.67.0': + resolution: {integrity: sha512-hlA20Oot20nW/9CzPBMPPPMfUarKvzqni+Njgrw8T43IFoQWQv8iIRoWWOgOQTGCm4PmjYwiojzEHOEaaKrzTg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + + '@bufbuild/buf-linux-armv7@1.67.0': + resolution: {integrity: sha512-hO9FEEtloITNaxW89rzKUjAsgnX1+rth7IZbK0Z+ohatXdanYg7Kv66yWffytaYf2iHltTbY6W/H4C3x0Uimbg==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + + '@bufbuild/buf-linux-x64@1.67.0': + resolution: {integrity: sha512-KBOWZ0NbhJSfXLM3JEX2AEs32jyHvTKD7wkIYudqOTxPUqwM1MXUg7m2Xw5nP1pcKH4RKS5HFijPMeOW/XUQ8Q==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + + '@bufbuild/buf-win32-arm64@1.67.0': + resolution: {integrity: sha512-ARGPwOv0lkUp3FU7bUMpYzqoJInx2qkk1ECBEC9XZMnRKmhCbyzmBoBKChBBJhEyDFdzPivhjg//zk5AlQ3bFA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + + '@bufbuild/buf-win32-x64@1.67.0': + resolution: {integrity: sha512-x9fkxEbjb2U4petBbESvNx+sfSQJONJxKOQzPfEKALksqRlvh7ktoHrYbygErnRZBSTNgrXzAqFI1GxMGEGSLQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + + '@bufbuild/buf@1.67.0': + resolution: {integrity: sha512-BLfgGmNFiHM79PcaafFNiP/+xxbdyFp1neDDdJd6R0tu7McO+WgJHM6vyNYRm7vXOSgO1uUPE4X3YFdBgcWk2Q==} + engines: {node: '>=12'} + hasBin: true + '@bufbuild/protobuf@2.10.2': resolution: {integrity: sha512-uFsRXwIGyu+r6AMdz+XijIIZJYpoWeYzILt5yZ2d3mCjQrWUTVpVD9WL/jZAbvp+Ed04rOhrsk7FiTcEDseB5A==} + '@bufbuild/protobuf@2.11.0': + resolution: {integrity: sha512-sBXGT13cpmPR5BMgHE6UEEfEaShh5Ror6rfN3yEK5si7QVrtZg8LEPQb0VVhiLRUslD2yLnXtnRzG035J/mZXQ==} + + '@bufbuild/protoc-gen-es@2.11.0': + resolution: {integrity: sha512-VzQuwEQDXipbZ1soWUuAWm1Z0C3B/IDWGeysnbX6ogJ6As91C2mdvAND/ekQ4YIWgen4d5nqLfIBOWLqCCjYUA==} + engines: {node: '>=20'} + hasBin: true + peerDependencies: + '@bufbuild/protobuf': 2.11.0 + peerDependenciesMeta: + '@bufbuild/protobuf': + optional: true + + '@bufbuild/protoplugin@2.11.0': + resolution: {integrity: sha512-lyZVNFUHArIOt4W0+dwYBe5GBwbKzbOy8ObaloEqsw9Mmiwv2O48TwddDoHN4itylC+BaEGqFdI1W8WQt2vWJQ==} + '@buttercup/fetch@0.2.1': resolution: {integrity: sha512-sCgECOx8wiqY8NN1xN22BqqKzXYIG2AicNLlakOAI4f0WgyLVUbAigMf8CZhBtJxdudTcB1gD5lciqi44jwJvg==} @@ -3488,78 +3604,92 @@ packages: resolution: {integrity: sha512-RXwd0CgG+uPRX5YYrkzKyalt2OJYRiJQ8ED/fi1tq9WQW2jsQIn0tqrlR5l5dr/rjqq6AHAxURhj2DVjyQWSOA==} cpu: [arm64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-arm@1.2.0': resolution: {integrity: sha512-mWd2uWvDtL/nvIzThLq3fr2nnGfyr/XMXlq8ZJ9WMR6PXijHlC3ksp0IpuhK6bougvQrchUAfzRLnbsen0Cqvw==} cpu: [arm] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-ppc64@1.2.0': resolution: {integrity: sha512-Xod/7KaDDHkYu2phxxfeEPXfVXFKx70EAFZ0qyUdOjCcxbjqyJOEUpDe6RIyaunGxT34Anf9ue/wuWOqBW2WcQ==} cpu: [ppc64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-s390x@1.2.0': resolution: {integrity: sha512-eMKfzDxLGT8mnmPJTNMcjfO33fLiTDsrMlUVcp6b96ETbnJmd4uvZxVJSKPQfS+odwfVaGifhsB07J1LynFehw==} cpu: [s390x] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-x64@1.2.0': resolution: {integrity: sha512-ZW3FPWIc7K1sH9E3nxIGB3y3dZkpJlMnkk7z5tu1nSkBoCgw2nSRTFHI5pB/3CQaJM0pdzMF3paf9ckKMSE9Tg==} cpu: [x64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linuxmusl-arm64@1.2.0': resolution: {integrity: sha512-UG+LqQJbf5VJ8NWJ5Z3tdIe/HXjuIdo4JeVNADXBFuG7z9zjoegpzzGIyV5zQKi4zaJjnAd2+g2nna8TZvuW9Q==} cpu: [arm64] os: [linux] + libc: [musl] '@img/sharp-libvips-linuxmusl-x64@1.2.0': resolution: {integrity: sha512-SRYOLR7CXPgNze8akZwjoGBoN1ThNZoqpOgfnOxmWsklTGVfJiGJoC/Lod7aNMGA1jSsKWM1+HRX43OP6p9+6Q==} cpu: [x64] os: [linux] + libc: [musl] '@img/sharp-linux-arm64@0.34.3': resolution: {integrity: sha512-QdrKe3EvQrqwkDrtuTIjI0bu6YEJHTgEeqdzI3uWJOH6G1O8Nl1iEeVYRGdj1h5I21CqxSvQp1Yv7xeU3ZewbA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] + libc: [glibc] '@img/sharp-linux-arm@0.34.3': resolution: {integrity: sha512-oBK9l+h6KBN0i3dC8rYntLiVfW8D8wH+NPNT3O/WBHeW0OQWCjfWksLUaPidsrDKpJgXp3G3/hkmhptAW0I3+A==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm] os: [linux] + libc: [glibc] '@img/sharp-linux-ppc64@0.34.3': resolution: {integrity: sha512-GLtbLQMCNC5nxuImPR2+RgrviwKwVql28FWZIW1zWruy6zLgA5/x2ZXk3mxj58X/tszVF69KK0Is83V8YgWhLA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [ppc64] os: [linux] + libc: [glibc] '@img/sharp-linux-s390x@0.34.3': resolution: {integrity: sha512-3gahT+A6c4cdc2edhsLHmIOXMb17ltffJlxR0aC2VPZfwKoTGZec6u5GrFgdR7ciJSsHT27BD3TIuGcuRT0KmQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [s390x] os: [linux] + libc: [glibc] '@img/sharp-linux-x64@0.34.3': resolution: {integrity: sha512-8kYso8d806ypnSq3/Ly0QEw90V5ZoHh10yH0HnrzOCr6DKAPI6QVHvwleqMkVQ0m+fc7EH8ah0BB0QPuWY6zJQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] + libc: [glibc] '@img/sharp-linuxmusl-arm64@0.34.3': resolution: {integrity: sha512-vAjbHDlr4izEiXM1OTggpCcPg9tn4YriK5vAjowJsHwdBIdx0fYRsURkxLG2RLm9gyBq66gwtWI8Gx0/ov+JKQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] + libc: [musl] '@img/sharp-linuxmusl-x64@0.34.3': resolution: {integrity: sha512-gCWUn9547K5bwvOn9l5XGAEjVTTRji4aPTqLzGXHvIr6bIDZKNTA34seMPgM0WmSf+RYBH411VavCejp3PkOeQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] + libc: [musl] '@img/sharp-wasm32@0.34.3': resolution: {integrity: sha512-+CyRcpagHMGteySaWos8IbnXcHgfDn7pO2fiC2slJxvNq9gDipYBN42/RagzctVRKgxATmfqOSulgZv5e1RdMg==} @@ -4324,30 +4454,35 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] '@napi-rs/canvas-linux-arm64-musl@0.1.97': resolution: {integrity: sha512-kKmSkQVnWeqg7qdsiXvYxKhAFuHz3tkBjW/zyQv5YKUPhotpaVhpBGv5LqCngzyuRV85SXoe+OFj+Tv0a0QXkQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] '@napi-rs/canvas-linux-riscv64-gnu@0.1.97': resolution: {integrity: sha512-Jc7I3A51jnEOIAXeLsN/M/+Z28LUeakcsXs07FLq9prXc0eYOtVwsDEv913Gr+06IRo34gJJVgT0TXvmz+N2VA==} engines: {node: '>= 10'} cpu: [riscv64] os: [linux] + libc: [glibc] '@napi-rs/canvas-linux-x64-gnu@0.1.97': resolution: {integrity: sha512-iDUBe7AilfuBSRbSa8/IGX38Mf+iCSBqoVKLSQ5XaY2JLOaqz1TVyPFEyIck7wT6mRQhQt5sN6ogfjIDfi74tg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] '@napi-rs/canvas-linux-x64-musl@0.1.97': resolution: {integrity: sha512-AKLFd/v0Z5fvgqBDqhvqtAdx+fHMJ5t9JcUNKq4FIZ5WH+iegGm8HPdj00NFlCSnm83Fp3Ln8I2f7uq1aIiWaA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] '@napi-rs/canvas-win32-arm64-msvc@0.1.97': resolution: {integrity: sha512-u883Yr6A6fO7Vpsy9YE4FVCIxzzo5sO+7pIUjjoDLjS3vQaNMkVzx5bdIpEL+ob+gU88WDK4VcxYMZ6nmnoX9A==} @@ -4440,24 +4575,28 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] '@neplex/vectorizer-linux-arm64-musl@0.0.5': resolution: {integrity: sha512-r2a85bAkgwSxAbQTSHnzXaDZCyABgVTYf6f0OSh1oGHHIc9pC97VUZbmQLtGFeIQLQR9j4nKjF1MlOHmnV4EDA==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] '@neplex/vectorizer-linux-x64-gnu@0.0.5': resolution: {integrity: sha512-8pdPe27RNXHwkvYiK3vj5b3/Yi8rWgJzUsBdT/Jm2bjk5c32wiV454yT0fLZQjRB1DCAK2DvyHjf6eZ0R9HaJg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] '@neplex/vectorizer-linux-x64-musl@0.0.5': resolution: {integrity: sha512-VP/DHuX40I/9KzSFRctxksXzJBGwbPE/E30NCAcPA1mS6iApovWsZe3la5dA9A5kStaKh9wTJcZuVEGL8tGIMg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] '@neplex/vectorizer-win32-arm64-msvc@0.0.5': resolution: {integrity: sha512-VfQRITnqvjABiIcnx5b/9XjyktTbpDHzY2nVt5wplOqGM88f6fPn2JYiia7IEdv2BA/1+oN/Bcw75eq12mW8Ug==} @@ -4644,6 +4783,9 @@ packages: '@oxc-project/types@0.95.0': resolution: {integrity: sha512-vACy7vhpMPhjEJhULNxrdR0D943TkA/MigMpJCHmBHvMXxRStRi/dPtTlfQ3uDwWSzRpT8z+7ImjZVf8JWBocQ==} + '@oxc-project/types@0.99.0': + resolution: {integrity: sha512-LLDEhXB7g1m5J+woRSgfKsFPS3LhR9xRhTeIoEBm5WrkwMxn6eZ0Ld0c0K5eHB57ChZX6I3uSmmLjZ8pcjlRcw==} + '@oxlint-tsgolint/darwin-arm64@0.17.4': resolution: {integrity: sha512-XEA7vl/T1+wiVnMq2MR6u5OYr2pwKHiAPgklxpK8tPrjQ1ci/amNmwI8ECn6TPXSCsC8SJsSN5xvzXm5H3dTfw==} cpu: [arm64] @@ -4721,48 +4863,56 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [glibc] '@oxlint/binding-linux-arm64-musl@1.56.0': resolution: {integrity: sha512-rkTZkBfJ4TYLjansjSzL6mgZOdN5IvUnSq3oNJSLwBcNvy3dlgQtpHPrRxrCEbbcp7oQ6If0tkNaqfOsphYZ9g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [musl] '@oxlint/binding-linux-ppc64-gnu@1.56.0': resolution: {integrity: sha512-uqL1kMH3u69/e1CH2EJhP3CP28jw2ExLsku4o8RVAZ7fySo9zOyI2fy9pVlTAp4voBLVgzndXi3SgtdyCTa2aA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] + libc: [glibc] '@oxlint/binding-linux-riscv64-gnu@1.56.0': resolution: {integrity: sha512-j0CcMBOgV6KsRaBdsebIeiy7hCjEvq2KdEsiULf2LZqAq0v1M1lWjelhCV57LxsqaIGChXFuFJ0RiFrSRHPhSg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] + libc: [glibc] '@oxlint/binding-linux-riscv64-musl@1.56.0': resolution: {integrity: sha512-7VDOiL8cDG3DQ/CY3yKjbV1c4YPvc4vH8qW09Vv+5ukq3l/Kcyr6XGCd5NvxUmxqDb2vjMpM+eW/4JrEEsUetA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] + libc: [musl] '@oxlint/binding-linux-s390x-gnu@1.56.0': resolution: {integrity: sha512-JGRpX0M+ikD3WpwJ7vKcHKV6Kg0dT52BW2Eu2BupXotYeqGXBrbY+QPkAyKO6MNgKozyTNaRh3r7g+VWgyAQYQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] + libc: [glibc] '@oxlint/binding-linux-x64-gnu@1.56.0': resolution: {integrity: sha512-dNaICPvtmuxFP/VbqdofrLqdS3bM/AKJN3LMJD52si44ea7Be1cBk6NpfIahaysG9Uo+L98QKddU9CD5L8UHnQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [glibc] '@oxlint/binding-linux-x64-musl@1.56.0': resolution: {integrity: sha512-pF1vOtM+GuXmbklM1hV8WMsn6tCNPvkUzklj/Ej98JhlanbmA2RB1BILgOpwSuCTRTIYx2MXssmEyQQ90QF5aA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [musl] '@oxlint/binding-openharmony-arm64@1.56.0': resolution: {integrity: sha512-bp8NQ4RE6fDIFLa4bdBiOA+TAvkNkg+rslR+AvvjlLTYXLy9/uKAYLQudaQouWihLD/hgkrXIKKzXi5IXOewwg==} @@ -5918,6 +6068,12 @@ packages: cpu: [arm64] os: [android] + '@rolldown/binding-android-arm64@1.0.0-beta.52': + resolution: {integrity: sha512-MBGIgysimZPqTDcLXI+i9VveijkP5C3EAncEogXhqfax6YXj1Tr2LY3DVuEOMIjWfMPMhtQSPup4fSTAmgjqIw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + '@rolldown/binding-android-arm64@1.0.0-beta.53': resolution: {integrity: sha512-Ok9V8o7o6YfSdTTYA/uHH30r3YtOxLD6G3wih/U9DO0ucBBFq8WPt/DslU53OgfteLRHITZny9N/qCUxMf9kjQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -5936,6 +6092,12 @@ packages: cpu: [arm64] os: [darwin] + '@rolldown/binding-darwin-arm64@1.0.0-beta.52': + resolution: {integrity: sha512-MmKeoLnKu1d9j6r19K8B+prJnIZ7u+zQ+zGQ3YHXGnr41rzE3eqQLovlkvoZnRoxDGPA4ps0pGiwXy6YE3lJyg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + '@rolldown/binding-darwin-arm64@1.0.0-beta.53': resolution: {integrity: sha512-yIsKqMz0CtRnVa6x3Pa+mzTihr4Ty+Z6HfPbZ7RVbk1Uxnco4+CUn7Qbm/5SBol1JD/7nvY8rphAgyAi7Lj6Vg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -5954,6 +6116,12 @@ packages: cpu: [x64] os: [darwin] + '@rolldown/binding-darwin-x64@1.0.0-beta.52': + resolution: {integrity: sha512-qpHedvQBmIjT8zdnjN3nWPR2qjQyJttbXniCEKKdHeAbZG9HyNPBUzQF7AZZGwmS9coQKL+hWg9FhWzh2dZ2IA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + '@rolldown/binding-darwin-x64@1.0.0-beta.53': resolution: {integrity: sha512-GTXe+mxsCGUnJOFMhfGWmefP7Q9TpYUseHvhAhr21nCTgdS8jPsvirb0tJwM3lN0/u/cg7bpFNa16fQrjKrCjQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -5972,6 +6140,12 @@ packages: cpu: [x64] os: [freebsd] + '@rolldown/binding-freebsd-x64@1.0.0-beta.52': + resolution: {integrity: sha512-dDp7WbPapj/NVW0LSiH/CLwMhmLwwKb3R7mh2kWX+QW85X1DGVnIEyKh9PmNJjB/+suG1dJygdtdNPVXK1hylg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + '@rolldown/binding-freebsd-x64@1.0.0-beta.53': resolution: {integrity: sha512-9Tmp7bBvKqyDkMcL4e089pH3RsjD3SUungjmqWtyhNOxoQMh0fSmINTyYV8KXtE+JkxYMPWvnEt+/mfpVCkk8w==} engines: {node: ^20.19.0 || >=22.12.0} @@ -5990,6 +6164,12 @@ packages: cpu: [arm] os: [linux] + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.52': + resolution: {integrity: sha512-9e4l6vy5qNSliDPqNfR6CkBOAx6PH7iDV4OJiEJzajajGrVy8gc/IKKJUsoE52G8ud8MX6r3PMl97NfwgOzB7g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.53': resolution: {integrity: sha512-a1y5fiB0iovuzdbjUxa7+Zcvgv+mTmlGGC4XydVIsyl48eoxgaYkA3l9079hyTyhECsPq+mbr0gVQsFU11OJAQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -6007,72 +6187,112 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.52': + resolution: {integrity: sha512-V48oDR84feRU2KRuzpALp594Uqlx27+zFsT6+BgTcXOtu7dWy350J1G28ydoCwKB+oxwsRPx2e7aeQnmd3YJbQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.53': resolution: {integrity: sha512-bpIGX+ov9PhJYV+wHNXl9rzq4F0QvILiURn0y0oepbQx+7stmQsKA0DhPGwmhfvF856wq+gbM8L92SAa/CBcLg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [glibc] '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.3': resolution: {integrity: sha512-kWXkoxxarYISBJ4bLNf5vFkEbb4JvccOwxWDxuK9yee8lg5XA7OpvlTptfRuwEvYcOZf+7VS69Uenpmpyo5Bjw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [glibc] '@rolldown/binding-linux-arm64-musl@1.0.0-beta.45': resolution: {integrity: sha512-tdy8ThO/fPp40B81v0YK3QC+KODOmzJzSUOO37DinQxzlTJ026gqUSOM8tzlVixRbQJltgVDCTYF8HNPRErQTA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [musl] + + '@rolldown/binding-linux-arm64-musl@1.0.0-beta.52': + resolution: {integrity: sha512-ENLmSQCWqSA/+YN45V2FqTIemg7QspaiTjlm327eUAMeOLdqmSOVVyrQexJGNTQ5M8sDYCgVAig2Kk01Ggmqaw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] '@rolldown/binding-linux-arm64-musl@1.0.0-beta.53': resolution: {integrity: sha512-bGe5EBB8FVjHBR1mOLOPEFg1Lp3//7geqWkU5NIhxe+yH0W8FVrQ6WRYOap4SUTKdklD/dC4qPLREkMMQ855FA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [musl] '@rolldown/binding-linux-arm64-musl@1.0.0-rc.3': resolution: {integrity: sha512-Z03/wrqau9Bicfgb3Dbs6SYTHliELk2PM2LpG2nFd+cGupTMF5kanLEcj2vuuJLLhptNyS61rtk7SOZ+lPsTUA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [musl] '@rolldown/binding-linux-x64-gnu@1.0.0-beta.45': resolution: {integrity: sha512-lS082ROBWdmOyVY/0YB3JmsiClaWoxvC+dA8/rbhyB9VLkvVEaihLEOr4CYmrMse151C4+S6hCw6oa1iewox7g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-gnu@1.0.0-beta.52': + resolution: {integrity: sha512-klahlb2EIFltSUubn/VLjuc3qxp1E7th8ukayPfdkcKvvYcQ5rJztgx8JsJSuAKVzKtNTqUGOhy4On71BuyV8g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] '@rolldown/binding-linux-x64-gnu@1.0.0-beta.53': resolution: {integrity: sha512-qL+63WKVQs1CMvFedlPt0U9PiEKJOAL/bsHMKUDS6Vp2Q+YAv/QLPu8rcvkfIMvQ0FPU2WL0aX4eWwF6e/GAnA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [glibc] '@rolldown/binding-linux-x64-gnu@1.0.0-rc.3': resolution: {integrity: sha512-iSXXZsQp08CSilff/DCTFZHSVEpEwdicV3W8idHyrByrcsRDVh9sGC3sev6d8BygSGj3vt8GvUKBPCoyMA4tgQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [glibc] '@rolldown/binding-linux-x64-musl@1.0.0-beta.45': resolution: {integrity: sha512-Hi73aYY0cBkr1/SvNQqH8Cd+rSV6S9RB5izCv0ySBcRnd/Wfn5plguUoGYwBnhHgFbh6cPw9m2dUVBR6BG1gxA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [musl] + + '@rolldown/binding-linux-x64-musl@1.0.0-beta.52': + resolution: {integrity: sha512-UuA+JqQIgqtkgGN2c/AQ5wi8M6mJHrahz/wciENPTeI6zEIbbLGoth5XN+sQe2pJDejEVofN9aOAp0kaazwnVg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] '@rolldown/binding-linux-x64-musl@1.0.0-beta.53': resolution: {integrity: sha512-VGl9JIGjoJh3H8Mb+7xnVqODajBmrdOOb9lxWXdcmxyI+zjB2sux69br0hZJDTyLJfvBoYm439zPACYbCjGRmw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [musl] '@rolldown/binding-linux-x64-musl@1.0.0-rc.3': resolution: {integrity: sha512-qaj+MFudtdCv9xZo9znFvkgoajLdc+vwf0Kz5N44g+LU5XMe+IsACgn3UG7uTRlCCvhMAGXm1XlpEA5bZBrOcw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [musl] '@rolldown/binding-openharmony-arm64@1.0.0-beta.45': resolution: {integrity: sha512-fljEqbO7RHHogNDxYtTzr+GNjlfOx21RUyGmF+NrkebZ8emYYiIqzPxsaMZuRx0rgZmVmliOzEp86/CQFDKhJQ==} @@ -6080,6 +6300,12 @@ packages: cpu: [arm64] os: [openharmony] + '@rolldown/binding-openharmony-arm64@1.0.0-beta.52': + resolution: {integrity: sha512-1BNQW8u4ro8bsN1+tgKENJiqmvc+WfuaUhXzMImOVSMw28pkBKdfZtX2qJPADV3terx+vNJtlsgSGeb3+W6Jiw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + '@rolldown/binding-openharmony-arm64@1.0.0-beta.53': resolution: {integrity: sha512-B4iIserJXuSnNzA5xBLFUIjTfhNy7d9sq4FUMQY3GhQWGVhS2RWWzzDnkSU6MUt7/aHUrep0CdQfXUJI9D3W7A==} engines: {node: ^20.19.0 || >=22.12.0} @@ -6097,6 +6323,11 @@ packages: engines: {node: '>=14.0.0'} cpu: [wasm32] + '@rolldown/binding-wasm32-wasi@1.0.0-beta.52': + resolution: {integrity: sha512-K/p7clhCqJOQpXGykrFaBX2Dp9AUVIDHGc+PtFGBwg7V+mvBTv/tsm3LC3aUmH02H2y3gz4y+nUTQ0MLpofEEg==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + '@rolldown/binding-wasm32-wasi@1.0.0-beta.53': resolution: {integrity: sha512-BUjAEgpABEJXilGq/BPh7jeU3WAJ5o15c1ZEgHaDWSz3LB881LQZnbNJHmUiM4d1JQWMYYyR1Y490IBHi2FPJg==} engines: {node: '>=14.0.0'} @@ -6113,6 +6344,12 @@ packages: cpu: [arm64] os: [win32] + '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.52': + resolution: {integrity: sha512-a4EkXBtnYYsKipjS7QOhEBM4bU5IlR9N1hU+JcVEVeuTiaslIyhWVKsvf7K2YkQHyVAJ+7/A9BtrGqORFcTgng==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.53': resolution: {integrity: sha512-s27uU7tpCWSjHBnxyVXHt3rMrQdJq5MHNv3BzsewCIroIw3DJFjMH1dzCPPMUFxnh1r52Nf9IJ/eWp6LDoyGcw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -6131,12 +6368,24 @@ packages: cpu: [ia32] os: [win32] + '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.52': + resolution: {integrity: sha512-5ZXcYyd4GxPA6QfbGrNcQjmjbuLGvfz6728pZMsQvGHI+06LT06M6TPtXvFvLgXtexc+OqvFe1yAIXJU1gob/w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + '@rolldown/binding-win32-x64-msvc@1.0.0-beta.45': resolution: {integrity: sha512-wiU40G1nQo9rtfvF9jLbl79lUgjfaD/LTyUEw2Wg/gdF5OhjzpKMVugZQngO+RNdwYaNj+Fs+kWBWfp4VXPMHA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] + '@rolldown/binding-win32-x64-msvc@1.0.0-beta.52': + resolution: {integrity: sha512-tzpnRQXJrSzb8Z9sm97UD3cY0toKOImx+xRKsDLX4zHaAlRXWh7jbaKBePJXEN7gNw7Nm03PBNwphdtA8KSUYQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + '@rolldown/binding-win32-x64-msvc@1.0.0-beta.53': resolution: {integrity: sha512-cjWL/USPJ1g0en2htb4ssMjIycc36RvdQAx1WlXnS6DpULswiUTVXPDesTifSKYSyvx24E0YqQkEm0K/M2Z/AA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -6155,6 +6404,9 @@ packages: '@rolldown/pluginutils@1.0.0-beta.45': resolution: {integrity: sha512-Le9ulGCrD8ggInzWw/k2J8QcbPz7eGIOWqfJ2L+1R0Opm7n6J37s2hiDWlh6LJN0Lk9L5sUzMvRHKW7UxBZsQA==} + '@rolldown/pluginutils@1.0.0-beta.52': + resolution: {integrity: sha512-/L0htLJZbaZFL1g9OHOblTxbCYIGefErJjtYOwgl9ZqNx27P3L0SDfjhhHIss32gu5NWgnxuT2a2Hnnv6QGHKA==} + '@rolldown/pluginutils@1.0.0-beta.53': resolution: {integrity: sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==} @@ -6204,71 +6456,85 @@ packages: resolution: {integrity: sha512-Rn3n+FUk2J5VWx+ywrG/HGPTD9jXNbicRtTM11e/uorplArnXZYsVifnPPqNNP5BsO3roI4n8332ukpY/zN7rQ==} cpu: [arm] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.55.1': resolution: {integrity: sha512-grPNWydeKtc1aEdrJDWk4opD7nFtQbMmV7769hiAaYyUKCT1faPRm2av8CX1YJsZ4TLAZcg9gTR1KvEzoLjXkg==} cpu: [arm] os: [linux] + libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.55.1': resolution: {integrity: sha512-a59mwd1k6x8tXKcUxSyISiquLwB5pX+fJW9TkWU46lCqD/GRDe9uDN31jrMmVP3feI3mhAdvcCClhV8V5MhJFQ==} cpu: [arm64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.55.1': resolution: {integrity: sha512-puS1MEgWX5GsHSoiAsF0TYrpomdvkaXm0CofIMG5uVkP6IBV+ZO9xhC5YEN49nsgYo1DuuMquF9+7EDBVYu4uA==} cpu: [arm64] os: [linux] + libc: [musl] '@rollup/rollup-linux-loong64-gnu@4.55.1': resolution: {integrity: sha512-r3Wv40in+lTsULSb6nnoudVbARdOwb2u5fpeoOAZjFLznp6tDU8kd+GTHmJoqZ9lt6/Sys33KdIHUaQihFcu7g==} cpu: [loong64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-loong64-musl@4.55.1': resolution: {integrity: sha512-MR8c0+UxAlB22Fq4R+aQSPBayvYa3+9DrwG/i1TKQXFYEaoW3B5b/rkSRIypcZDdWjWnpcvxbNaAJDcSbJU3Lw==} cpu: [loong64] os: [linux] + libc: [musl] '@rollup/rollup-linux-ppc64-gnu@4.55.1': resolution: {integrity: sha512-3KhoECe1BRlSYpMTeVrD4sh2Pw2xgt4jzNSZIIPLFEsnQn9gAnZagW9+VqDqAHgm1Xc77LzJOo2LdigS5qZ+gw==} cpu: [ppc64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-ppc64-musl@4.55.1': resolution: {integrity: sha512-ziR1OuZx0vdYZZ30vueNZTg73alF59DicYrPViG0NEgDVN8/Jl87zkAPu4u6VjZST2llgEUjaiNl9JM6HH1Vdw==} cpu: [ppc64] os: [linux] + libc: [musl] '@rollup/rollup-linux-riscv64-gnu@4.55.1': resolution: {integrity: sha512-uW0Y12ih2XJRERZ4jAfKamTyIHVMPQnTZcQjme2HMVDAHY4amf5u414OqNYC+x+LzRdRcnIG1YodLrrtA8xsxw==} cpu: [riscv64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.55.1': resolution: {integrity: sha512-u9yZ0jUkOED1BFrqu3BwMQoixvGHGZ+JhJNkNKY/hyoEgOwlqKb62qu+7UjbPSHYjiVy8kKJHvXKv5coH4wDeg==} cpu: [riscv64] os: [linux] + libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.55.1': resolution: {integrity: sha512-/0PenBCmqM4ZUd0190j7J0UsQ/1nsi735iPRakO8iPciE7BQ495Y6msPzaOmvx0/pn+eJVVlZrNrSh4WSYLxNg==} cpu: [s390x] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.45.1': resolution: {integrity: sha512-+E/lYl6qu1zqgPEnTrs4WysQtvc/Sh4fC2nByfFExqgYrqkKWp1tWIbe+ELhixnenSpBbLXNi6vbEEJ8M7fiHw==} cpu: [x64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.55.1': resolution: {integrity: sha512-a8G4wiQxQG2BAvo+gU6XrReRRqj+pLS2NGXKm8io19goR+K8lw269eTrPkSdDTALwMmJp4th2Uh0D8J9bEV1vg==} cpu: [x64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-musl@4.55.1': resolution: {integrity: sha512-bD+zjpFrMpP/hqkfEcnjXWHMw5BIghGisOKPj+2NaNDuVT+8Ds4mPf3XcPHuat1tz89WRL+1wbcxKY3WSbiT7w==} cpu: [x64] os: [linux] + libc: [musl] '@rollup/rollup-openbsd-x64@4.55.1': resolution: {integrity: sha512-eLXw0dOiqE4QmvikfQ6yjgkg/xDM+MdU9YJuP4ySTibXU0oAvnEWXt7UDJmD4UkYialMfOGFPJnIHSe/kdzPxg==} @@ -6758,24 +7024,28 @@ packages: engines: {node: '>=10'} cpu: [arm64] os: [linux] + libc: [glibc] '@swc/core-linux-arm64-musl@1.15.8': resolution: {integrity: sha512-koiCqL09EwOP1S2RShCI7NbsQuG6r2brTqUYE7pV7kZm9O17wZ0LSz22m6gVibpwEnw8jI3IE1yYsQTVpluALw==} engines: {node: '>=10'} cpu: [arm64] os: [linux] + libc: [musl] '@swc/core-linux-x64-gnu@1.15.8': resolution: {integrity: sha512-4p6lOMU3bC+Vd5ARtKJ/FxpIC5G8v3XLoPEZ5s7mLR8h7411HWC/LmTXDHcrSXRC55zvAVia1eldy6zDLz8iFQ==} engines: {node: '>=10'} cpu: [x64] os: [linux] + libc: [glibc] '@swc/core-linux-x64-musl@1.15.8': resolution: {integrity: sha512-z3XBnbrZAL+6xDGAhJoN4lOueIxC/8rGrJ9tg+fEaeqLEuAtHSW2QHDHxDwkxZMjuF/pZ6MUTjHjbp8wLbuRLA==} engines: {node: '>=10'} cpu: [x64] os: [linux] + libc: [musl] '@swc/core-win32-arm64-msvc@1.15.8': resolution: {integrity: sha512-djQPJ9Rh9vP8GTS/Df3hcc6XP6xnG5c8qsngWId/BLA9oX6C7UzCPAn74BG/wGb9a6j4w3RINuoaieJB3t+7iQ==} @@ -6862,24 +7132,28 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-arm64-musl@4.1.18': resolution: {integrity: sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] '@tailwindcss/oxide-linux-x64-gnu@4.1.18': resolution: {integrity: sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-x64-musl@4.1.18': resolution: {integrity: sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] '@tailwindcss/oxide-wasm32-wasi@4.1.18': resolution: {integrity: sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==} @@ -7731,6 +8005,11 @@ packages: resolution: {integrity: sha512-o1R4QGAjCWG0LOqpDOBuH9H+BgaM+7Ps8AuvHJkf4V1tmTFqeGMXGNbv02f3HQIZiI3KMqsRGoBGe/LvCc4SFg==} hasBin: true + '@typescript/vfs@1.6.4': + resolution: {integrity: sha512-PJFXFS4ZJKiJ9Qiuix6Dz/OwEIqHD7Dme1UwZhTK11vR+5dqW2ACbdndWQexBzCx+CPuMe5WBYQWCsFyGlQLlQ==} + peerDependencies: + typescript: '*' + '@uiw/codemirror-extensions-basic-setup@4.25.7': resolution: {integrity: sha512-tPV/AGjF4yM22D5mnyH7EuYBkWO05wF5Y4x3lmQJo6LuHmhjh0RQsVDjqeIgNOkXT3UO9OdkL4dzxw465/JZVg==} peerDependencies: @@ -7918,6 +8197,9 @@ packages: '@vitest/expect@3.2.4': resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} + '@vitest/expect@4.1.2': + resolution: {integrity: sha512-gbu+7B0YgUJ2nkdsRJrFFW6X7NTP44WlhiclHniUhxADQJH5Szt9mZ9hWnJPJ8YwOK5zUOSSlSvyzRf0u1DSBQ==} + '@vitest/mocker@3.2.4': resolution: {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==} peerDependencies: @@ -7929,18 +8211,41 @@ packages: vite: optional: true + '@vitest/mocker@4.1.2': + resolution: {integrity: sha512-Ize4iQtEALHDttPRCmN+FKqOl2vxTiNUhzobQFFt/BM1lRUTG7zRCLOykG/6Vo4E4hnUdfVLo5/eqKPukcWW7Q==} + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + '@vitest/pretty-format@3.2.4': resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} + '@vitest/pretty-format@4.1.2': + resolution: {integrity: sha512-dwQga8aejqeuB+TvXCMzSQemvV9hNEtDDpgUKDzOmNQayl2OG241PSWeJwKRH3CiC+sESrmoFd49rfnq7T4RnA==} + '@vitest/runner@3.2.4': resolution: {integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==} + '@vitest/runner@4.1.2': + resolution: {integrity: sha512-Gr+FQan34CdiYAwpGJmQG8PgkyFVmARK8/xSijia3eTFgVfpcpztWLuP6FttGNfPLJhaZVP/euvujeNYar36OQ==} + '@vitest/snapshot@3.2.4': resolution: {integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==} + '@vitest/snapshot@4.1.2': + resolution: {integrity: sha512-g7yfUmxYS4mNxk31qbOYsSt2F4m1E02LFqO53Xpzg3zKMhLAPZAjjfyl9e6z7HrW6LvUdTwAQR3HHfLjpko16A==} + '@vitest/spy@3.2.4': resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} + '@vitest/spy@4.1.2': + resolution: {integrity: sha512-DU4fBnbVCJGNBwVA6xSToNXrkZNSiw59H8tcuUspVMsBDBST4nfvsPsEHDHGtWRRnqBERBQu7TrTKskmjqTXKA==} + '@vitest/ui@3.2.4': resolution: {integrity: sha512-hGISOaP18plkzbWEcP/QvtRW1xDXF2+96HbEX6byqQhAUbiS5oH6/9JwW+QsQCIYON2bI6QZBF+2PvOmrRZ9wA==} peerDependencies: @@ -7949,6 +8254,9 @@ packages: '@vitest/utils@3.2.4': resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} + '@vitest/utils@4.1.2': + resolution: {integrity: sha512-xw2/TiX82lQHA06cgbqRKFb5lCAy3axQ4H4SoUFhUsg+wztiet+co86IAMDtF6Vm1hc7J6j09oh/rgDn+JdKIQ==} + '@vitest/web-worker@3.2.4': resolution: {integrity: sha512-JXK3lMyZHDrJ/BrJmxSZxe3RYT9oy2juxN4kpdrQ8NL8iibz352lXbcrnqG4WuSoBDwhjgghgvmIpsTv9Be7eA==} peerDependencies: @@ -8531,6 +8839,10 @@ packages: resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} engines: {node: '>=18'} + chai@6.2.2: + resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} + engines: {node: '>=18'} + chalk@1.1.3: resolution: {integrity: sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==} engines: {node: '>=0.10.0'} @@ -8601,6 +8913,10 @@ packages: resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} engines: {node: '>= 14.16.0'} + chokidar@5.0.0: + resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} + engines: {node: '>= 20.19.0'} + chownr@1.1.4: resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} @@ -9414,6 +9730,10 @@ packages: resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} engines: {node: '>=12'} + dotenv@17.4.0: + resolution: {integrity: sha512-kCKF62fwtzwYm0IGBNjRUjtJgMfGapII+FslMHIjMR5KTnwEmBmWLDRSnc3XSNP8bNy34tekgQyDT0hr7pERRQ==} + engines: {node: '>=12'} + dotenv@8.6.0: resolution: {integrity: sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==} engines: {node: '>=10'} @@ -9514,6 +9834,12 @@ packages: sqlite3: optional: true + drizzle-zod@0.8.3: + resolution: {integrity: sha512-66yVOuvGhKJnTdiqj1/Xaaz9/qzOdRJADpDa68enqS6g3t0kpNkwNYjUuaeXgZfO/UWuIM9HIhSlJ6C5ZraMww==} + peerDependencies: + drizzle-orm: '>=0.36.0' + zod: ^3.25.0 || ^4.0.0 + dts-resolver@2.1.3: resolution: {integrity: sha512-bihc7jPC90VrosXNzK0LTE2cuLP6jr0Ro8jk+kMugHReJVLIpHz/xadeq3MhuwyO4TD4OA3L1Q8pBBFRc08Tsw==} engines: {node: '>=20.19.0'} @@ -9694,6 +10020,9 @@ packages: es-module-lexer@1.7.0: resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + es-module-lexer@2.0.0: + resolution: {integrity: sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==} + es-object-atoms@1.1.1: resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} engines: {node: '>= 0.4'} @@ -10374,11 +10703,11 @@ packages: glob@7.1.6: resolution: {integrity: sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==} - deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + deprecated: Glob versions prior to v9 are no longer supported glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + deprecated: Glob versions prior to v9 are no longer supported global-agent@3.0.0: resolution: {integrity: sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==} @@ -11314,24 +11643,28 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] lightningcss-linux-arm64-musl@1.30.2: resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [musl] lightningcss-linux-x64-gnu@1.30.2: resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [glibc] lightningcss-linux-x64-musl@1.30.2: resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [musl] lightningcss-win32-arm64-msvc@1.30.2: resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==} @@ -11519,6 +11852,11 @@ packages: peerDependencies: react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + lucide-react@0.563.0: + resolution: {integrity: sha512-8dXPB2GI4dI8jV4MgUDGBeLdGk8ekfqVZ0BdLcrRzocGgG75ltNEmWS+gE7uokKF/0oSUuczNDT+g9hFJ23FkA==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + luxon@3.7.2: resolution: {integrity: sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==} engines: {node: '>=12'} @@ -13277,6 +13615,10 @@ packages: resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} engines: {node: '>= 14.18.0'} + readdirp@5.0.0: + resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} + engines: {node: '>= 20.19.0'} + readline2@1.0.1: resolution: {integrity: sha512-8/td4MmwUB6PkZUbV25uKz7dfrmjYWxsW8DVfibWdlHRk/l/DfHKn4pU+dfcoGLFgWOdyGCzINRQD7jn+Bv+/g==} @@ -13526,6 +13868,25 @@ packages: vue-tsc: optional: true + rolldown-plugin-dts@0.18.4: + resolution: {integrity: sha512-7UpdiICFd/BhdjKtDPeakCFRk6pbkTGFe0Z6u01egt4c8aoO+JoPGF1Smc+JRuCH2s5j5hBdteBi0e10G0xQdQ==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@ts-macro/tsc': ^0.3.6 + '@typescript/native-preview': '>=7.0.0-dev.20250601.1' + rolldown: ^1.0.0-beta.51 + typescript: ^5.0.0 + vue-tsc: ~3.1.0 + peerDependenciesMeta: + '@ts-macro/tsc': + optional: true + '@typescript/native-preview': + optional: true + typescript: + optional: true + vue-tsc: + optional: true + rolldown-plugin-dts@0.22.1: resolution: {integrity: sha512-5E0AiM5RSQhU6cjtkDFWH6laW4IrMu0j1Mo8x04Xo1ALHmaRMs9/7zej7P3RrryVHW/DdZAp85MA7Be55p0iUw==} engines: {node: '>=20.19.0'} @@ -13591,6 +13952,11 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} hasBin: true + rolldown@1.0.0-beta.52: + resolution: {integrity: sha512-Hbnpljue+JhMJrlOjQ1ixp9me7sUec7OjFvS+A1Qm8k8Xyxmw3ZhxFu7LlSXW1s9AX3POE9W9o2oqCEeR5uDmg==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + rolldown@1.0.0-beta.53: resolution: {integrity: sha512-Qd9c2p0XKZdgT5AYd+KgAMggJ8ZmCs3JnS9PTMWkyUfteKlfmKtxJbWTHkVakxwXs1Ub7jrRYVeFeF7N0sQxyw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -13931,6 +14297,9 @@ packages: std-env@3.10.0: resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + std-env@4.0.0: + resolution: {integrity: sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ==} + storybook@10.3.4: resolution: {integrity: sha512-866YXZy9k59tLPl9SN3KZZOFeBC/swxkuBVtW8iQjJIzfCrvk7zXQd8RSQ4ignmCdArVvY4lGMCAT4yNaZSt1g==} hasBin: true @@ -14173,7 +14542,6 @@ packages: tar@6.2.1: resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} engines: {node: '>=10'} - deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me tar@7.5.9: resolution: {integrity: sha512-BTLcK0xsDh2+PUe9F6c2TlRp4zOOBMTkoQHQIWSIzI0R7KG46uEwq4OPk2W7bZcprBMsuaeFsqwYr7pjh6CuHg==} @@ -14276,6 +14644,10 @@ packages: resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} engines: {node: '>=14.0.0'} + tinyrainbow@3.1.0: + resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} + engines: {node: '>=14.0.0'} + tinyspy@4.0.4: resolution: {integrity: sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==} engines: {node: '>=14.0.0'} @@ -14427,6 +14799,31 @@ packages: unrun: optional: true + tsdown@0.16.8: + resolution: {integrity: sha512-6ANw9mgU9kk7SvTBKvpDu/DVJeAFECiLUSeL5M7f5Nm5H97E7ybxmXT4PQ23FySYn32y6OzjoAH/lsWCbGzfLA==} + engines: {node: '>=20.19.0'} + hasBin: true + peerDependencies: + '@arethetypeswrong/core': ^0.18.1 + '@vitejs/devtools': ^0.0.0-alpha.18 + publint: ^0.3.0 + typescript: ^5.0.0 + unplugin-lightningcss: ^0.4.0 + unplugin-unused: ^0.5.0 + peerDependenciesMeta: + '@arethetypeswrong/core': + optional: true + '@vitejs/devtools': + optional: true + publint: + optional: true + typescript: + optional: true + unplugin-lightningcss: + optional: true + unplugin-unused: + optional: true + tsdown@0.20.3: resolution: {integrity: sha512-qWOUXSbe4jN8JZEgrkc/uhJpC8VN2QpNu3eZkBWwNuTEjc/Ik1kcc54ycfcQ5QPRHeu9OQXaLfCI3o7pEJgB2w==} engines: {node: '>=20.19.0'} @@ -14523,13 +14920,18 @@ packages: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' + typescript@5.4.5: + resolution: {integrity: sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==} + engines: {node: '>=14.17'} + hasBin: true + typescript@5.8.3: resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} engines: {node: '>=14.17'} hasBin: true - typescript@5.9.2: - resolution: {integrity: sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==} + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} engines: {node: '>=14.17'} hasBin: true @@ -14844,6 +15246,41 @@ packages: jsdom: optional: true + vitest@4.1.2: + resolution: {integrity: sha512-xjR1dMTVHlFLh98JE3i/f/WePqJsah4A0FK9cc8Ehp9Udk0AZk6ccpIZhh1qJ/yxVWRZ+Q54ocnD8TXmkhspGg==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@opentelemetry/api': ^1.9.0 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.1.2 + '@vitest/browser-preview': 4.1.2 + '@vitest/browser-webdriverio': 4.1.2 + '@vitest/ui': 4.1.2 + happy-dom: '*' + jsdom: '*' + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@opentelemetry/api': + optional: true + '@types/node': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + void-elements@3.1.0: resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==} engines: {node: '>=0.10.0'} @@ -16203,8 +16640,57 @@ snapshots: - encoding - utf-8-validate + '@bufbuild/buf-darwin-arm64@1.67.0': + optional: true + + '@bufbuild/buf-darwin-x64@1.67.0': + optional: true + + '@bufbuild/buf-linux-aarch64@1.67.0': + optional: true + + '@bufbuild/buf-linux-armv7@1.67.0': + optional: true + + '@bufbuild/buf-linux-x64@1.67.0': + optional: true + + '@bufbuild/buf-win32-arm64@1.67.0': + optional: true + + '@bufbuild/buf-win32-x64@1.67.0': + optional: true + + '@bufbuild/buf@1.67.0': + optionalDependencies: + '@bufbuild/buf-darwin-arm64': 1.67.0 + '@bufbuild/buf-darwin-x64': 1.67.0 + '@bufbuild/buf-linux-aarch64': 1.67.0 + '@bufbuild/buf-linux-armv7': 1.67.0 + '@bufbuild/buf-linux-x64': 1.67.0 + '@bufbuild/buf-win32-arm64': 1.67.0 + '@bufbuild/buf-win32-x64': 1.67.0 + '@bufbuild/protobuf@2.10.2': {} + '@bufbuild/protobuf@2.11.0': {} + + '@bufbuild/protoc-gen-es@2.11.0(@bufbuild/protobuf@2.11.0)': + dependencies: + '@bufbuild/protoplugin': 2.11.0 + optionalDependencies: + '@bufbuild/protobuf': 2.11.0 + transitivePeerDependencies: + - supports-color + + '@bufbuild/protoplugin@2.11.0': + dependencies: + '@bufbuild/protobuf': 2.11.0 + '@typescript/vfs': 1.6.4(typescript@5.4.5) + typescript: 5.4.5 + transitivePeerDependencies: + - supports-color + '@buttercup/fetch@0.2.1': optionalDependencies: node-fetch: 3.3.2 @@ -16630,6 +17116,11 @@ snapshots: ws: 8.20.0 zod: 4.3.4 + '@cherrystudio/openai@6.15.0(ws@8.20.0)(zod@4.3.6)': + optionalDependencies: + ws: 8.20.0 + zod: 4.3.6 + '@chevrotain/cst-dts-gen@11.1.2': dependencies: '@chevrotain/gast': 11.1.2 @@ -17274,7 +17765,7 @@ snapshots: '@eslint-react/eff': 1.53.1 '@typescript-eslint/utils': 8.51.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.8.3) ts-pattern: 5.9.0 - zod: 4.3.4 + zod: 4.3.6 transitivePeerDependencies: - eslint - supports-color @@ -17286,7 +17777,7 @@ snapshots: '@eslint-react/kit': 1.53.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.8.3) '@typescript-eslint/utils': 8.51.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.8.3) ts-pattern: 5.9.0 - zod: 4.3.4 + zod: 4.3.6 transitivePeerDependencies: - eslint - supports-color @@ -18682,7 +19173,7 @@ snapshots: openapi-types: 12.1.3 uuid: 10.0.0 yaml: 2.8.2 - zod: 4.3.4 + zod: 4.3.6 optionalDependencies: cheerio: 1.1.2 langsmith: 0.4.4(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))(openai@6.15.0(ws@8.20.0)(zod@4.3.4)) @@ -18796,8 +19287,8 @@ snapshots: dependencies: '@langchain/core': 1.0.2(patch_hash=8dc787a82cebafe8b23c8826f25f29aca64fc8b43a0a1878e0010782e4da96ed)(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0))(openai@6.15.0(ws@8.20.0)(zod@4.3.4)) js-tiktoken: 1.0.21 - openai: '@cherrystudio/openai@6.15.0(ws@8.20.0)(zod@4.3.4)' - zod: 4.3.4 + openai: '@cherrystudio/openai@6.15.0(ws@8.20.0)(zod@4.3.6)' + zod: 4.3.6 transitivePeerDependencies: - ws @@ -19407,6 +19898,8 @@ snapshots: '@oxc-project/types@0.95.0': {} + '@oxc-project/types@0.99.0': {} + '@oxlint-tsgolint/darwin-arm64@0.17.4': optional: true @@ -20960,6 +21453,9 @@ snapshots: '@rolldown/binding-android-arm64@1.0.0-beta.45': optional: true + '@rolldown/binding-android-arm64@1.0.0-beta.52': + optional: true + '@rolldown/binding-android-arm64@1.0.0-beta.53': optional: true @@ -20969,6 +21465,9 @@ snapshots: '@rolldown/binding-darwin-arm64@1.0.0-beta.45': optional: true + '@rolldown/binding-darwin-arm64@1.0.0-beta.52': + optional: true + '@rolldown/binding-darwin-arm64@1.0.0-beta.53': optional: true @@ -20978,6 +21477,9 @@ snapshots: '@rolldown/binding-darwin-x64@1.0.0-beta.45': optional: true + '@rolldown/binding-darwin-x64@1.0.0-beta.52': + optional: true + '@rolldown/binding-darwin-x64@1.0.0-beta.53': optional: true @@ -20987,6 +21489,9 @@ snapshots: '@rolldown/binding-freebsd-x64@1.0.0-beta.45': optional: true + '@rolldown/binding-freebsd-x64@1.0.0-beta.52': + optional: true + '@rolldown/binding-freebsd-x64@1.0.0-beta.53': optional: true @@ -20996,6 +21501,9 @@ snapshots: '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.45': optional: true + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.52': + optional: true + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.53': optional: true @@ -21005,6 +21513,9 @@ snapshots: '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.45': optional: true + '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.52': + optional: true + '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.53': optional: true @@ -21014,6 +21525,9 @@ snapshots: '@rolldown/binding-linux-arm64-musl@1.0.0-beta.45': optional: true + '@rolldown/binding-linux-arm64-musl@1.0.0-beta.52': + optional: true + '@rolldown/binding-linux-arm64-musl@1.0.0-beta.53': optional: true @@ -21023,6 +21537,9 @@ snapshots: '@rolldown/binding-linux-x64-gnu@1.0.0-beta.45': optional: true + '@rolldown/binding-linux-x64-gnu@1.0.0-beta.52': + optional: true + '@rolldown/binding-linux-x64-gnu@1.0.0-beta.53': optional: true @@ -21032,6 +21549,9 @@ snapshots: '@rolldown/binding-linux-x64-musl@1.0.0-beta.45': optional: true + '@rolldown/binding-linux-x64-musl@1.0.0-beta.52': + optional: true + '@rolldown/binding-linux-x64-musl@1.0.0-beta.53': optional: true @@ -21041,6 +21561,9 @@ snapshots: '@rolldown/binding-openharmony-arm64@1.0.0-beta.45': optional: true + '@rolldown/binding-openharmony-arm64@1.0.0-beta.52': + optional: true + '@rolldown/binding-openharmony-arm64@1.0.0-beta.53': optional: true @@ -21052,6 +21575,11 @@ snapshots: '@napi-rs/wasm-runtime': 1.1.1 optional: true + '@rolldown/binding-wasm32-wasi@1.0.0-beta.52': + dependencies: + '@napi-rs/wasm-runtime': 1.1.1 + optional: true + '@rolldown/binding-wasm32-wasi@1.0.0-beta.53': dependencies: '@napi-rs/wasm-runtime': 1.1.1 @@ -21065,6 +21593,9 @@ snapshots: '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.45': optional: true + '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.52': + optional: true + '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.53': optional: true @@ -21074,9 +21605,15 @@ snapshots: '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.45': optional: true + '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.52': + optional: true + '@rolldown/binding-win32-x64-msvc@1.0.0-beta.45': optional: true + '@rolldown/binding-win32-x64-msvc@1.0.0-beta.52': + optional: true + '@rolldown/binding-win32-x64-msvc@1.0.0-beta.53': optional: true @@ -21087,6 +21624,8 @@ snapshots: '@rolldown/pluginutils@1.0.0-beta.45': {} + '@rolldown/pluginutils@1.0.0-beta.52': {} + '@rolldown/pluginutils@1.0.0-beta.53': {} '@rolldown/pluginutils@1.0.0-rc.3': {} @@ -22740,19 +23279,19 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/eslint-plugin@8.51.0(@typescript-eslint/parser@8.51.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.2))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.2)': + '@typescript-eslint/eslint-plugin@8.51.0(@typescript-eslint/parser@8.51.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.51.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.2) + '@typescript-eslint/parser': 8.51.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) '@typescript-eslint/scope-manager': 8.51.0 - '@typescript-eslint/type-utils': 8.51.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.2) - '@typescript-eslint/utils': 8.51.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.2) + '@typescript-eslint/type-utils': 8.51.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/utils': 8.51.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) '@typescript-eslint/visitor-keys': 8.51.0 eslint: 9.39.2(jiti@2.6.1) ignore: 7.0.5 natural-compare: 1.4.0 - ts-api-utils: 2.4.0(typescript@5.9.2) - typescript: 5.9.2 + ts-api-utils: 2.4.0(typescript@5.9.3) + typescript: 5.9.3 transitivePeerDependencies: - supports-color optional: true @@ -22769,15 +23308,15 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.51.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.2)': + '@typescript-eslint/parser@8.51.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': dependencies: '@typescript-eslint/scope-manager': 8.51.0 '@typescript-eslint/types': 8.51.0 - '@typescript-eslint/typescript-estree': 8.51.0(typescript@5.9.2) + '@typescript-eslint/typescript-estree': 8.51.0(typescript@5.9.3) '@typescript-eslint/visitor-keys': 8.51.0 debug: 4.4.3 eslint: 9.39.2(jiti@2.6.1) - typescript: 5.9.2 + typescript: 5.9.3 transitivePeerDependencies: - supports-color optional: true @@ -22791,12 +23330,12 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.51.0(typescript@5.9.2)': + '@typescript-eslint/project-service@8.51.0(typescript@5.9.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.51.0(typescript@5.9.2) + '@typescript-eslint/tsconfig-utils': 8.51.0(typescript@5.9.3) '@typescript-eslint/types': 8.51.0 debug: 4.4.3 - typescript: 5.9.2 + typescript: 5.9.3 transitivePeerDependencies: - supports-color optional: true @@ -22810,9 +23349,9 @@ snapshots: dependencies: typescript: 5.8.3 - '@typescript-eslint/tsconfig-utils@8.51.0(typescript@5.9.2)': + '@typescript-eslint/tsconfig-utils@8.51.0(typescript@5.9.3)': dependencies: - typescript: 5.9.2 + typescript: 5.9.3 optional: true '@typescript-eslint/type-utils@8.51.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.8.3)': @@ -22827,15 +23366,15 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/type-utils@8.51.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.2)': + '@typescript-eslint/type-utils@8.51.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': dependencies: '@typescript-eslint/types': 8.51.0 - '@typescript-eslint/typescript-estree': 8.51.0(typescript@5.9.2) - '@typescript-eslint/utils': 8.51.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.2) + '@typescript-eslint/typescript-estree': 8.51.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.51.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) debug: 4.4.3 eslint: 9.39.2(jiti@2.6.1) - ts-api-utils: 2.4.0(typescript@5.9.2) - typescript: 5.9.2 + ts-api-utils: 2.4.0(typescript@5.9.3) + typescript: 5.9.3 transitivePeerDependencies: - supports-color optional: true @@ -22857,18 +23396,18 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/typescript-estree@8.51.0(typescript@5.9.2)': + '@typescript-eslint/typescript-estree@8.51.0(typescript@5.9.3)': dependencies: - '@typescript-eslint/project-service': 8.51.0(typescript@5.9.2) - '@typescript-eslint/tsconfig-utils': 8.51.0(typescript@5.9.2) + '@typescript-eslint/project-service': 8.51.0(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.51.0(typescript@5.9.3) '@typescript-eslint/types': 8.51.0 '@typescript-eslint/visitor-keys': 8.51.0 debug: 4.4.3 minimatch: 9.0.6 semver: 7.7.1 tinyglobby: 0.2.15 - ts-api-utils: 2.4.0(typescript@5.9.2) - typescript: 5.9.2 + ts-api-utils: 2.4.0(typescript@5.9.3) + typescript: 5.9.3 transitivePeerDependencies: - supports-color optional: true @@ -22884,14 +23423,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.51.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.2)': + '@typescript-eslint/utils@8.51.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': dependencies: '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@2.6.1)) '@typescript-eslint/scope-manager': 8.51.0 '@typescript-eslint/types': 8.51.0 - '@typescript-eslint/typescript-estree': 8.51.0(typescript@5.9.2) + '@typescript-eslint/typescript-estree': 8.51.0(typescript@5.9.3) eslint: 9.39.2(jiti@2.6.1) - typescript: 5.9.2 + typescript: 5.9.3 transitivePeerDependencies: - supports-color optional: true @@ -22932,6 +23471,13 @@ snapshots: '@typescript/native-preview-win32-arm64': 7.0.0-dev.20260204.1 '@typescript/native-preview-win32-x64': 7.0.0-dev.20260204.1 + '@typescript/vfs@1.6.4(typescript@5.4.5)': + dependencies: + debug: 4.4.3 + typescript: 5.4.5 + transitivePeerDependencies: + - supports-color + '@uiw/codemirror-extensions-basic-setup@4.25.7(@codemirror/autocomplete@6.20.1)(@codemirror/commands@6.10.2)(@codemirror/language@6.12.2)(@codemirror/lint@6.9.5)(@codemirror/search@6.5.11)(@codemirror/state@6.5.4)(@codemirror/view@6.39.16)': dependencies: '@codemirror/autocomplete': 6.20.1 @@ -23395,6 +23941,15 @@ snapshots: chai: 5.3.3 tinyrainbow: 2.0.0 + '@vitest/expect@4.1.2': + dependencies: + '@standard-schema/spec': 1.1.0 + '@types/chai': 5.2.3 + '@vitest/spy': 4.1.2 + '@vitest/utils': 4.1.2 + chai: 6.2.2 + tinyrainbow: 3.1.0 + '@vitest/mocker@3.2.4(msw@2.12.7(@types/node@24.10.4)(typescript@5.8.3))(rolldown-vite@7.3.0(@types/node@24.10.4)(esbuild@0.25.12)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2))': dependencies: '@vitest/spy': 3.2.4 @@ -23404,26 +23959,53 @@ snapshots: msw: 2.12.7(@types/node@24.10.4)(typescript@5.8.3) vite: rolldown-vite@7.3.0(@types/node@24.10.4)(esbuild@0.25.12)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2) + '@vitest/mocker@4.1.2(msw@2.12.7(@types/node@24.10.4)(typescript@5.9.3))(rolldown-vite@7.3.0(@types/node@24.10.4)(esbuild@0.25.12)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2))': + dependencies: + '@vitest/spy': 4.1.2 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + msw: 2.12.7(@types/node@24.10.4)(typescript@5.9.3) + vite: rolldown-vite@7.3.0(@types/node@24.10.4)(esbuild@0.25.12)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2) + '@vitest/pretty-format@3.2.4': dependencies: tinyrainbow: 2.0.0 + '@vitest/pretty-format@4.1.2': + dependencies: + tinyrainbow: 3.1.0 + '@vitest/runner@3.2.4': dependencies: '@vitest/utils': 3.2.4 pathe: 2.0.3 strip-literal: 3.1.0 + '@vitest/runner@4.1.2': + dependencies: + '@vitest/utils': 4.1.2 + pathe: 2.0.3 + '@vitest/snapshot@3.2.4': dependencies: '@vitest/pretty-format': 3.2.4 magic-string: 0.30.21 pathe: 2.0.3 + '@vitest/snapshot@4.1.2': + dependencies: + '@vitest/pretty-format': 4.1.2 + '@vitest/utils': 4.1.2 + magic-string: 0.30.21 + pathe: 2.0.3 + '@vitest/spy@3.2.4': dependencies: tinyspy: 4.0.4 + '@vitest/spy@4.1.2': {} + '@vitest/ui@3.2.4(vitest@3.2.4)': dependencies: '@vitest/utils': 3.2.4 @@ -23441,6 +24023,12 @@ snapshots: loupe: 3.2.1 tinyrainbow: 2.0.0 + '@vitest/utils@4.1.2': + dependencies: + '@vitest/pretty-format': 4.1.2 + convert-source-map: 2.0.0 + tinyrainbow: 3.1.0 + '@vitest/web-worker@3.2.4(vitest@3.2.4)': dependencies: debug: 4.4.3 @@ -24216,6 +24804,8 @@ snapshots: loupe: 3.2.1 pathval: 2.0.1 + chai@6.2.2: {} + chalk@1.1.3: dependencies: ansi-styles: 2.2.1 @@ -24309,6 +24899,10 @@ snapshots: dependencies: readdirp: 4.1.2 + chokidar@5.0.0: + dependencies: + readdirp: 5.0.0 + chownr@1.1.4: {} chownr@2.0.0: {} @@ -25159,6 +25753,8 @@ snapshots: dotenv@16.6.1: {} + dotenv@17.4.0: {} + dotenv@8.6.0: {} drizzle-kit@0.31.8: @@ -25175,6 +25771,11 @@ snapshots: '@libsql/client': 0.14.0 '@opentelemetry/api': 1.9.0 + drizzle-zod@0.8.3(drizzle-orm@0.44.7(@libsql/client@0.14.0)(@opentelemetry/api@1.9.0))(zod@4.3.4): + dependencies: + drizzle-orm: 0.44.7(@libsql/client@0.14.0)(@opentelemetry/api@1.9.0) + zod: 4.3.4 + dts-resolver@2.1.3: {} duck@0.1.12: @@ -25405,6 +26006,8 @@ snapshots: es-module-lexer@1.7.0: {} + es-module-lexer@2.0.0: {} + es-object-atoms@1.1.1: dependencies: es-errors: 1.3.0 @@ -25635,11 +26238,11 @@ snapshots: optionalDependencies: '@typescript-eslint/eslint-plugin': 8.51.0(@typescript-eslint/parser@8.51.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.8.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.8.3) - eslint-plugin-unused-imports@4.3.0(@typescript-eslint/eslint-plugin@8.51.0(@typescript-eslint/parser@8.51.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.2))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.2))(eslint@9.39.2(jiti@2.6.1)): + eslint-plugin-unused-imports@4.3.0(@typescript-eslint/eslint-plugin@8.51.0(@typescript-eslint/parser@8.51.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)): dependencies: eslint: 9.39.2(jiti@2.6.1) optionalDependencies: - '@typescript-eslint/eslint-plugin': 8.51.0(@typescript-eslint/parser@8.51.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.2))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.2) + '@typescript-eslint/eslint-plugin': 8.51.0(@typescript-eslint/parser@8.51.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) eslint-scope@8.4.0: dependencies: @@ -27508,6 +28111,10 @@ snapshots: dependencies: react: 19.2.3 + lucide-react@0.563.0(react@19.2.3): + dependencies: + react: 19.2.3 + luxon@3.7.2: {} lz-string@1.5.0: {} @@ -28356,6 +28963,32 @@ snapshots: transitivePeerDependencies: - '@types/node' + msw@2.12.7(@types/node@24.10.4)(typescript@5.9.3): + dependencies: + '@inquirer/confirm': 5.1.21(@types/node@24.10.4) + '@mswjs/interceptors': 0.40.0 + '@open-draft/deferred-promise': 2.2.0 + '@types/statuses': 2.0.6 + cookie: 1.1.1 + graphql: 16.12.0 + headers-polyfill: 4.0.3 + is-node-process: 1.2.0 + outvariant: 1.4.3 + path-to-regexp: 6.3.0 + picocolors: 1.1.1 + rettime: 0.7.0 + statuses: 2.0.2 + strict-event-emitter: 0.5.1 + tough-cookie: 6.0.0 + type-fest: 5.4.2 + until-async: 3.0.2 + yargs: 17.7.2 + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - '@types/node' + optional: true + multicast-dns@7.2.5: dependencies: dns-packet: 5.6.1 @@ -29805,6 +30438,8 @@ snapshots: readdirp@4.1.2: {} + readdirp@5.0.0: {} + readline2@1.0.1: dependencies: code-point-at: 1.1.0 @@ -30128,6 +30763,24 @@ snapshots: transitivePeerDependencies: - oxc-resolver + rolldown-plugin-dts@0.18.4(@typescript/native-preview@7.0.0-dev.20260204.1)(rolldown@1.0.0-beta.52)(typescript@5.9.3): + dependencies: + '@babel/generator': 7.28.5 + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 + ast-kit: 2.2.0 + birpc: 4.0.0 + dts-resolver: 2.1.3 + get-tsconfig: 4.13.6 + magic-string: 0.30.21 + obug: 2.1.1 + rolldown: 1.0.0-beta.52 + optionalDependencies: + '@typescript/native-preview': 7.0.0-dev.20260204.1 + typescript: 5.9.3 + transitivePeerDependencies: + - oxc-resolver + rolldown-plugin-dts@0.22.1(@typescript/native-preview@7.0.0-dev.20260204.1)(rolldown@1.0.0-rc.3)(typescript@5.8.3): dependencies: '@babel/generator': 8.0.0-rc.1 @@ -30146,7 +30799,7 @@ snapshots: transitivePeerDependencies: - oxc-resolver - rolldown-plugin-dts@0.22.1(@typescript/native-preview@7.0.0-dev.20260204.1)(rolldown@1.0.0-rc.3)(typescript@5.9.2): + rolldown-plugin-dts@0.22.1(@typescript/native-preview@7.0.0-dev.20260204.1)(rolldown@1.0.0-rc.3)(typescript@5.9.3): dependencies: '@babel/generator': 8.0.0-rc.1 '@babel/helper-validator-identifier': 8.0.0-rc.1 @@ -30160,7 +30813,7 @@ snapshots: rolldown: 1.0.0-rc.3 optionalDependencies: '@typescript/native-preview': 7.0.0-dev.20260204.1 - typescript: 5.9.2 + typescript: 5.9.3 transitivePeerDependencies: - oxc-resolver @@ -30201,6 +30854,26 @@ snapshots: '@rolldown/binding-win32-ia32-msvc': 1.0.0-beta.45 '@rolldown/binding-win32-x64-msvc': 1.0.0-beta.45 + rolldown@1.0.0-beta.52: + dependencies: + '@oxc-project/types': 0.99.0 + '@rolldown/pluginutils': 1.0.0-beta.52 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.0.0-beta.52 + '@rolldown/binding-darwin-arm64': 1.0.0-beta.52 + '@rolldown/binding-darwin-x64': 1.0.0-beta.52 + '@rolldown/binding-freebsd-x64': 1.0.0-beta.52 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-beta.52 + '@rolldown/binding-linux-arm64-gnu': 1.0.0-beta.52 + '@rolldown/binding-linux-arm64-musl': 1.0.0-beta.52 + '@rolldown/binding-linux-x64-gnu': 1.0.0-beta.52 + '@rolldown/binding-linux-x64-musl': 1.0.0-beta.52 + '@rolldown/binding-openharmony-arm64': 1.0.0-beta.52 + '@rolldown/binding-wasm32-wasi': 1.0.0-beta.52 + '@rolldown/binding-win32-arm64-msvc': 1.0.0-beta.52 + '@rolldown/binding-win32-ia32-msvc': 1.0.0-beta.52 + '@rolldown/binding-win32-x64-msvc': 1.0.0-beta.52 + rolldown@1.0.0-beta.53: dependencies: '@oxc-project/types': 0.101.0 @@ -30648,6 +31321,8 @@ snapshots: std-env@3.10.0: {} + std-env@4.0.0: {} + storybook@10.3.4(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3): dependencies: '@storybook/global': 5.0.0 @@ -31060,6 +31735,8 @@ snapshots: tinyrainbow@2.0.0: {} + tinyrainbow@3.1.0: {} + tinyspy@4.0.4: {} tldts-core@6.1.86: {} @@ -31157,9 +31834,9 @@ snapshots: dependencies: typescript: 5.8.3 - ts-api-utils@2.4.0(typescript@5.9.2): + ts-api-utils@2.4.0(typescript@5.9.3): dependencies: - typescript: 5.9.2 + typescript: 5.9.3 optional: true ts-declaration-location@1.0.7(typescript@5.8.3): @@ -31208,6 +31885,32 @@ snapshots: - supports-color - vue-tsc + tsdown@0.16.8(@typescript/native-preview@7.0.0-dev.20260204.1)(typescript@5.9.3): + dependencies: + ansis: 4.2.0 + cac: 6.7.14 + chokidar: 5.0.0 + diff: 8.0.3 + empathic: 2.0.0 + hookable: 5.5.3 + obug: 2.1.1 + rolldown: 1.0.0-beta.52 + rolldown-plugin-dts: 0.18.4(@typescript/native-preview@7.0.0-dev.20260204.1)(rolldown@1.0.0-beta.52)(typescript@5.9.3) + semver: 7.7.3 + tinyexec: 1.0.2 + tinyglobby: 0.2.15 + tree-kill: 1.2.2 + unconfig-core: 7.5.0 + unrun: 0.2.27 + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - '@ts-macro/tsc' + - '@typescript/native-preview' + - oxc-resolver + - synckit + - vue-tsc + tsdown@0.20.3(@typescript/native-preview@7.0.0-dev.20260204.1)(typescript@5.8.3): dependencies: ansis: 4.2.0 @@ -31235,7 +31938,7 @@ snapshots: - synckit - vue-tsc - tsdown@0.20.3(@typescript/native-preview@7.0.0-dev.20260204.1)(typescript@5.9.2): + tsdown@0.20.3(@typescript/native-preview@7.0.0-dev.20260204.1)(typescript@5.9.3): dependencies: ansis: 4.2.0 cac: 6.7.14 @@ -31246,7 +31949,7 @@ snapshots: obug: 2.1.1 picomatch: 4.0.3 rolldown: 1.0.0-rc.3 - rolldown-plugin-dts: 0.22.1(@typescript/native-preview@7.0.0-dev.20260204.1)(rolldown@1.0.0-rc.3)(typescript@5.9.2) + rolldown-plugin-dts: 0.22.1(@typescript/native-preview@7.0.0-dev.20260204.1)(rolldown@1.0.0-rc.3)(typescript@5.9.3) semver: 7.7.3 tinyexec: 1.0.2 tinyglobby: 0.2.15 @@ -31254,7 +31957,7 @@ snapshots: unconfig-core: 7.4.2 unrun: 0.2.27 optionalDependencies: - typescript: 5.9.2 + typescript: 5.9.3 transitivePeerDependencies: - '@ts-macro/tsc' - '@typescript/native-preview' @@ -31331,10 +32034,11 @@ snapshots: transitivePeerDependencies: - supports-color + typescript@5.4.5: {} + typescript@5.8.3: {} - typescript@5.9.2: - optional: true + typescript@5.9.3: {} ua-parser-js@1.0.41: {} @@ -31693,6 +32397,36 @@ snapshots: - tsx - yaml + vitest@4.1.2(@opentelemetry/api@1.9.0)(@types/node@24.10.4)(@vitest/ui@3.2.4(vitest@3.2.4))(jsdom@26.1.0)(msw@2.12.7(@types/node@24.10.4)(typescript@5.9.3))(rolldown-vite@7.3.0(@types/node@24.10.4)(esbuild@0.25.12)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2)): + dependencies: + '@vitest/expect': 4.1.2 + '@vitest/mocker': 4.1.2(msw@2.12.7(@types/node@24.10.4)(typescript@5.9.3))(rolldown-vite@7.3.0(@types/node@24.10.4)(esbuild@0.25.12)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2)) + '@vitest/pretty-format': 4.1.2 + '@vitest/runner': 4.1.2 + '@vitest/snapshot': 4.1.2 + '@vitest/spy': 4.1.2 + '@vitest/utils': 4.1.2 + es-module-lexer: 2.0.0 + expect-type: 1.3.0 + magic-string: 0.30.21 + obug: 2.1.1 + pathe: 2.0.3 + picomatch: 4.0.3 + std-env: 4.0.0 + tinybench: 2.9.0 + tinyexec: 1.0.2 + tinyglobby: 0.2.15 + tinyrainbow: 3.1.0 + vite: rolldown-vite@7.3.0(@types/node@24.10.4)(esbuild@0.25.12)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2) + why-is-node-running: 2.3.0 + optionalDependencies: + '@opentelemetry/api': 1.9.0 + '@types/node': 24.10.4 + '@vitest/ui': 3.2.4(vitest@3.2.4) + jsdom: 26.1.0 + transitivePeerDependencies: + - msw + void-elements@3.1.0: {} voyage-ai-provider@3.0.0(zod@4.3.4): diff --git a/src/main/data/api/handlers/index.ts b/src/main/data/api/handlers/index.ts index 83116a47450..454df708974 100644 --- a/src/main/data/api/handlers/index.ts +++ b/src/main/data/api/handlers/index.ts @@ -8,6 +8,8 @@ * - test.ts - Test API handlers * - topics.ts - Topic API handlers * - messages.ts - Message API handlers + * - models.ts - Model API handlers + * - providers.ts - Provider API handlers * - translate.ts - Translate API handlers */ @@ -18,6 +20,8 @@ import { knowledgeHandlers } from './knowledges' import { mcpServerHandlers } from './mcpServers' import { messageHandlers } from './messages' import { miniappHandlers } from './miniapps' +import { modelHandlers } from './models' +import { providerHandlers } from './providers' import { testHandlers } from './test' import { topicHandlers } from './topics' import { translateHandlers } from './translate' @@ -34,6 +38,8 @@ export const apiHandlers: ApiImplementation = { ...testHandlers, ...topicHandlers, ...messageHandlers, + ...modelHandlers, + ...providerHandlers, ...knowledgeHandlers, ...translateHandlers, ...mcpServerHandlers, diff --git a/src/main/data/api/handlers/models.ts b/src/main/data/api/handlers/models.ts new file mode 100644 index 00000000000..f9d49d1c731 --- /dev/null +++ b/src/main/data/api/handlers/models.ts @@ -0,0 +1,59 @@ +/** + * Model API Handlers + * + * Implements all model-related API endpoints including: + * - Model CRUD operations + * - Listing with filters + */ + +import { modelService } from '@data/services/ModelService' +import { catalogService } from '@data/services/ProviderCatalogService' +import type { ApiHandler, ApiMethods } from '@shared/data/api/apiTypes' +import type { ListModelsQuery } from '@shared/data/api/schemas/models' +import type { ModelSchemas } from '@shared/data/api/schemas/models' + +/** + * Handler type for a specific model endpoint + */ +type ModelHandler> = ApiHandler + +/** + * Model API handlers implementation + */ +export const modelHandlers: { + [Path in keyof ModelSchemas]: { + [Method in keyof ModelSchemas[Path]]: ModelHandler> + } +} = { + '/models': { + GET: async ({ query }) => { + const q = (query || {}) as ListModelsQuery + return await modelService.list(q) + }, + + POST: async ({ body }) => { + return await modelService.create(body) + } + }, + + '/models/resolve': { + POST: async ({ body }) => { + return catalogService.resolveModels(body.providerId, body.models) + } + }, + + '/models/:providerId/:modelId': { + GET: async ({ params }) => { + return await modelService.getByKey(params.providerId, params.modelId) + }, + + PATCH: async ({ params, body }) => { + return await modelService.update(params.providerId, params.modelId, body) + }, + + DELETE: async ({ params }) => { + await modelService.delete(params.providerId, params.modelId) + return undefined + } + } +} diff --git a/src/main/data/api/handlers/providers.ts b/src/main/data/api/handlers/providers.ts new file mode 100644 index 00000000000..cdb38d3078c --- /dev/null +++ b/src/main/data/api/handlers/providers.ts @@ -0,0 +1,105 @@ +/** + * Provider API Handlers + * + * Implements all provider-related API endpoints including: + * - Provider CRUD operations + * - Listing with filters + * + * Runtime validation uses the ORM-derived Zod schema (userProviderInsertSchema) + * so the DB table definition is the single source of truth. + */ + +import { userProviderInsertSchema } from '@data/db/schemas/userProvider' +import { catalogService } from '@data/services/ProviderCatalogService' +import { providerService } from '@data/services/ProviderService' +import type { ApiHandler, ApiMethods } from '@shared/data/api/apiTypes' +import type { CreateProviderDto, ListProvidersQuery, UpdateProviderDto } from '@shared/data/api/schemas/providers' +import type { ProviderSchemas } from '@shared/data/api/schemas/providers' + +/** + * Handler type for a specific provider endpoint + */ +type ProviderHandler> = ApiHandler + +/** + * Provider API handlers implementation + */ +export const providerHandlers: { + [Path in keyof ProviderSchemas]: { + [Method in keyof ProviderSchemas[Path]]: ProviderHandler> + } +} = { + '/providers': { + GET: async ({ query }) => { + const q = (query || {}) as ListProvidersQuery + return await providerService.list(q) + }, + + POST: async ({ body }) => { + const parsed = userProviderInsertSchema.safeParse(body) + if (!parsed.success) { + throw new Error(`Invalid provider data: ${parsed.error.message}`) + } + return await providerService.create(parsed.data as CreateProviderDto) + } + }, + + '/providers/:providerId': { + GET: async ({ params }) => { + return await providerService.getByProviderId(params.providerId) + }, + + PATCH: async ({ params, body }) => { + const parsed = userProviderInsertSchema.partial().safeParse(body) + if (!parsed.success) { + throw new Error(`Invalid provider update data: ${parsed.error.message}`) + } + return await providerService.update(params.providerId, parsed.data as UpdateProviderDto) + }, + + DELETE: async ({ params }) => { + await providerService.delete(params.providerId) + return undefined + } + }, + + '/providers/:providerId/rotated-key': { + GET: async ({ params }) => { + const apiKey = await providerService.getRotatedApiKey(params.providerId) + return { apiKey } + } + }, + + '/providers/:providerId/api-keys': { + GET: async ({ params }) => { + const keys = await providerService.getEnabledApiKeys(params.providerId) + return { keys } + }, + + POST: async ({ params, body }) => { + const { key, label } = body as { key: string; label?: string } + if (!key || typeof key !== 'string') { + throw new Error('API key value is required') + } + return await providerService.addApiKey(params.providerId, key, label) + } + }, + + '/providers/:providerId/catalog-models': { + GET: async ({ params }) => { + return catalogService.getCatalogModelsByProvider(params.providerId) + } + }, + + '/providers/:providerId/auth-config': { + GET: async ({ params }) => { + return providerService.getAuthConfig(params.providerId) + } + }, + + '/providers/:providerId/api-keys/:keyId': { + DELETE: async ({ params }) => { + return providerService.deleteApiKey(params.providerId, params.keyId) + } + } +} diff --git a/src/main/data/db/schemas/userModel.ts b/src/main/data/db/schemas/userModel.ts new file mode 100644 index 00000000000..d1c3c5c2f39 --- /dev/null +++ b/src/main/data/db/schemas/userModel.ts @@ -0,0 +1,179 @@ +/** + * User Model table schema + * + * Stores all user models with fully resolved configurations. + * Capabilities and settings are resolved once at add-time (from catalog), + * so no runtime merge is needed. + * + * - presetModelId: traceability marker (which preset this came from, if any) + * - Composite primary key: (providerId, modelId) + * + * Type definitions are sourced from @shared/data/types/model + */ +import type { + EndpointType, + Modality, + ModelCapability, + ParameterSupport, + ReasoningConfig, + RuntimeModelPricing +} from '@shared/data/types/model' +import { ParameterSupportDbSchema, ReasoningConfigSchema, RuntimeModelPricingSchema } from '@shared/data/types/model' +import { index, integer, primaryKey, sqliteTable, text } from 'drizzle-orm/sqlite-core' +import { createSchemaFactory } from 'drizzle-zod' +import * as z from 'zod' + +const { createInsertSchema, createSelectSchema } = createSchemaFactory({ zodInstance: z }) + +import { createUpdateTimestamps } from './_columnHelpers' + +// ═══════════════════════════════════════════════════════════════════════════════ +// Catalog Enrichable Fields +// ═══════════════════════════════════════════════════════════════════════════════ + +/** + * Fields that can be auto-populated by catalog enrichment. + * Used by `userOverrides` to track which fields the user has explicitly modified, + * so that catalog updates don't overwrite user customizations. + * + * The `isCatalogEnrichableField` guard ensures runtime safety. + */ +export const CATALOG_ENRICHABLE_FIELDS = [ + 'name', + 'description', + 'capabilities', + 'inputModalities', + 'outputModalities', + 'endpointTypes', + 'contextWindow', + 'maxOutputTokens', + 'supportsStreaming', + 'reasoning', + 'parameters', + 'pricing' +] as const + +export type CatalogEnrichableField = (typeof CATALOG_ENRICHABLE_FIELDS)[number] + +const CATALOG_ENRICHABLE_SET: ReadonlySet = new Set(CATALOG_ENRICHABLE_FIELDS) + +/** Check if a field name is a catalog-enrichable field */ +export function isCatalogEnrichableField(field: string): field is CatalogEnrichableField { + return CATALOG_ENRICHABLE_SET.has(field) +} + +// ═══════════════════════════════════════════════════════════════════════════════ +// Table Definition +// ═══════════════════════════════════════════════════════════════════════════════ + +export const userModelTable = sqliteTable( + 'user_model', + { + /** User Provider ID */ + providerId: text().notNull(), + + /** Model ID (composite key part) */ + modelId: text().notNull(), + + /** Associated preset model ID (for traceability) */ + presetModelId: text(), + + /** Display name (override or complete) */ + name: text(), + + /** Description */ + description: text(), + + /** UI grouping */ + group: text(), + + /** Complete capability list (resolved at add time) */ + capabilities: text({ mode: 'json' }).$type(), + + /** Supported input modalities (e.g., TEXT, VISION, AUDIO, VIDEO) */ + inputModalities: text({ mode: 'json' }).$type(), + + /** Supported output modalities (e.g., TEXT, VISION, AUDIO, VIDEO, VECTOR) */ + outputModalities: text({ mode: 'json' }).$type(), + + /** Endpoint types (optional, override Provider default) */ + endpointTypes: text({ mode: 'json' }).$type(), + + /** Custom endpoint URL (optional, complete override) */ + customEndpointUrl: text(), + + /** Context window size */ + contextWindow: integer(), + + /** Maximum output tokens */ + maxOutputTokens: integer(), + + /** Streaming support */ + supportsStreaming: integer({ mode: 'boolean' }), + + /** Reasoning configuration */ + reasoning: text({ mode: 'json' }).$type(), + + /** Parameter support */ + parameters: text({ mode: 'json' }).$type(), + + /** Pricing configuration */ + pricing: text({ mode: 'json' }).$type(), + + /** Whether this model is enabled */ + isEnabled: integer({ mode: 'boolean' }).default(true), + + /** Whether this model is hidden from lists */ + isHidden: integer({ mode: 'boolean' }).default(false), + + /** Whether this model has been deprecated by the provider (no longer in API model list) */ + isDeprecated: integer({ mode: 'boolean' }).default(false), + + /** Sort order in UI */ + sortOrder: integer().default(0), + + /** User notes */ + notes: text(), + + /** + * List of field names the user has explicitly modified. + * Catalog enrichment skips these fields to preserve user customizations. + */ + userOverrides: text({ mode: 'json' }).$type(), + + ...createUpdateTimestamps + }, + (t) => [ + primaryKey({ columns: [t.providerId, t.modelId] }), + index('user_model_preset_idx').on(t.presetModelId), + index('user_model_provider_enabled_idx').on(t.providerId, t.isEnabled), + index('user_model_provider_sort_idx').on(t.providerId, t.sortOrder) + ] +) + +// Export table type +export type UserModel = typeof userModelTable.$inferSelect +export type NewUserModel = typeof userModelTable.$inferInsert + +const jsonColumnOverrides = { + capabilities: () => z.array(z.number()).nullable(), + inputModalities: () => z.array(z.number()).nullable(), + outputModalities: () => z.array(z.number()).nullable(), + endpointTypes: () => z.array(z.number()).nullable(), + reasoning: () => ReasoningConfigSchema.nullable(), + parameters: () => ParameterSupportDbSchema.nullable(), + pricing: () => RuntimeModelPricingSchema.nullable(), + userOverrides: () => z.array(z.string()).nullable() +} + +export const userModelInsertSchema = createInsertSchema(userModelTable, jsonColumnOverrides) +export const userModelSelectSchema = createSelectSchema(userModelTable, jsonColumnOverrides) + +// ═══════════════════════════════════════════════════════════════════════════════ +// Utility Functions +// ═══════════════════════════════════════════════════════════════════════════════ + +/** Check if this is a preset override or fully custom model */ +export function isPresetOverride(model: UserModel): boolean { + return model.presetModelId != null +} diff --git a/src/main/data/db/schemas/userProvider.ts b/src/main/data/db/schemas/userProvider.ts new file mode 100644 index 00000000000..3539a8ba825 --- /dev/null +++ b/src/main/data/db/schemas/userProvider.ts @@ -0,0 +1,104 @@ +/** + * User Provider table schema + * + * Core principle: One Provider instance = One apiHost (1:1 relationship) + * One apiHost can have multiple API Keys (1:N relationship) + * + * Relationship with preset providers: + * - presetProviderId links to catalog preset provider for inherited config + * - If presetProviderId is null, this is a fully custom provider + * + */ + +import { + type ApiFeatures, + ApiFeaturesSchema, + type ApiKeyEntry, + ApiKeyEntrySchema, + type AuthConfig, + AuthConfigSchema, + type ProviderSettings, + ProviderSettingsSchema, + type ProviderWebsites, + ProviderWebsitesSchema +} from '@shared/data/types/provider' +import { index, integer, sqliteTable, text } from 'drizzle-orm/sqlite-core' +import { createSchemaFactory } from 'drizzle-zod' +import * as z from 'zod' + +const { createInsertSchema, createSelectSchema } = createSchemaFactory({ zodInstance: z }) + +import type { EndpointType } from '@shared/data/types/model' + +import { createUpdateTimestamps, uuidPrimaryKey } from './_columnHelpers' + +export const userProviderTable = sqliteTable( + 'user_provider', + { + id: uuidPrimaryKey(), + + providerId: text().notNull().unique(), + + /** Associated preset provider ID (optional) + * Links to catalog provider for inherited API format and defaults + * If null, this is a fully custom provider requiring manual endpoint config + */ + presetProviderId: text(), + + name: text().notNull(), + + baseUrls: text('base_urls', { mode: 'json' }).$type>>(), + + modelsApiUrls: text('models_api_urls', { mode: 'json' }).$type>(), + + /** Default text generation endpoint (when supporting multiple) */ + defaultChatEndpoint: text().$type(), + + /** API Keys array */ + apiKeys: text({ mode: 'json' }).$type().default([]), + + /** Unified auth configuration for different auth methods */ + authConfig: text({ mode: 'json' }).$type(), + + /** API feature support (null = use preset default) */ + apiFeatures: text('api_features', { mode: 'json' }).$type(), + + /** Provider-specific settings as JSON */ + providerSettings: text({ mode: 'json' }).$type(), + + /** How this provider's API expects reasoning parameters (e.g. 'openai-chat', 'anthropic', 'enable-thinking') */ + reasoningFormatType: text(), + + /** Website links (official, apiKey, docs, models) */ + websites: text({ mode: 'json' }).$type(), + + /** Whether this provider is enabled */ + isEnabled: integer({ mode: 'boolean' }).default(true), + + /** Sort order in UI */ + sortOrder: integer().default(0), + + ...createUpdateTimestamps + }, + (t) => [ + index('user_provider_preset_idx').on(t.presetProviderId), + index('user_provider_enabled_sort_idx').on(t.isEnabled, t.sortOrder) + ] +) + +// Export table type +export type UserProvider = typeof userProviderTable.$inferSelect +export type NewUserProvider = typeof userProviderTable.$inferInsert + +const jsonColumnOverrides = { + baseUrls: () => z.record(z.string(), z.string()).nullable(), + modelsApiUrls: () => z.record(z.string(), z.string()).nullable(), + apiKeys: () => z.array(ApiKeyEntrySchema).nullable(), + authConfig: () => AuthConfigSchema.nullable(), + apiFeatures: () => ApiFeaturesSchema.nullable(), + providerSettings: () => ProviderSettingsSchema.nullable(), + websites: () => ProviderWebsitesSchema.nullable() +} + +export const userProviderInsertSchema = createInsertSchema(userProviderTable, jsonColumnOverrides) +export const userProviderSelectSchema = createSelectSchema(userProviderTable, jsonColumnOverrides) diff --git a/src/main/data/migration/v2/core/MigrationEngine.ts b/src/main/data/migration/v2/core/MigrationEngine.ts index d46db6252c4..8dbb9ca958f 100644 --- a/src/main/data/migration/v2/core/MigrationEngine.ts +++ b/src/main/data/migration/v2/core/MigrationEngine.ts @@ -12,6 +12,8 @@ import { preferenceTable } from '@data/db/schemas/preference' import { topicTable } from '@data/db/schemas/topic' import { translateHistoryTable } from '@data/db/schemas/translateHistory' import { translateLanguageTable } from '@data/db/schemas/translateLanguage' +import { userModelTable } from '@data/db/schemas/userModel' +import { userProviderTable } from '@data/db/schemas/userProvider' import type { DbType } from '@data/db/types' import { loggerService } from '@logger' import type { @@ -266,6 +268,8 @@ export class MigrationEngine { // Tables to clear - add more as they are created // Order matters: child tables must be cleared before parent tables const tables = [ + { table: userModelTable, name: 'user_model' }, // Must clear before user_provider + { table: userProviderTable, name: 'user_provider' }, { table: messageTable, name: 'message' }, // Must clear before topic (FK reference) { table: topicTable, name: 'topic' }, { table: mcpServerTable, name: 'mcp_server' }, @@ -291,6 +295,8 @@ export class MigrationEngine { // Clear tables in dependency order (children before parents) // Messages reference topics, so delete messages first + await db.delete(userModelTable) + await db.delete(userProviderTable) await db.delete(messageTable) await db.delete(topicTable) await db.delete(mcpServerTable) diff --git a/src/main/data/migration/v2/migrators/ProviderModelMigrator.ts b/src/main/data/migration/v2/migrators/ProviderModelMigrator.ts new file mode 100644 index 00000000000..4a685d5720e --- /dev/null +++ b/src/main/data/migration/v2/migrators/ProviderModelMigrator.ts @@ -0,0 +1,192 @@ +/** + * Migrates legacy Redux llm providers/models into v2 user tables. + */ + +import { userModelTable } from '@data/db/schemas/userModel' +import { userProviderTable } from '@data/db/schemas/userProvider' +import { loggerService } from '@logger' +import type { ExecuteResult, PrepareResult, ValidateResult } from '@shared/data/migration/v2/types' +import type { Provider as LegacyProvider } from '@types' +import { sql } from 'drizzle-orm' + +import type { MigrationContext } from '../core/MigrationContext' +import { BaseMigrator } from './BaseMigrator' +import { type OldLlmSettings, transformModel, transformProvider } from './mappings/ProviderModelMappings' + +const logger = loggerService.withContext('ProviderModelMigrator') + +const BATCH_SIZE = 100 + +interface LlmState { + providers?: LegacyProvider[] + settings?: OldLlmSettings +} + +export class ProviderModelMigrator extends BaseMigrator { + readonly id = 'provider_model' + readonly name = 'Provider Model' + readonly description = 'Migrate provider and model configuration from Redux to SQLite' + readonly order = 1.75 + + private providers: LegacyProvider[] = [] + private settings: OldLlmSettings = {} + private totalModelCount = 0 + + override reset(): void { + this.providers = [] + this.settings = {} + this.totalModelCount = 0 + } + + async prepare(ctx: MigrationContext): Promise { + try { + const warnings: string[] = [] + const llmState = ctx.sources.reduxState.getCategory('llm') + + if (!llmState?.providers || !Array.isArray(llmState.providers)) { + logger.warn('No llm.providers found in Redux state') + return { + success: true, + itemCount: 0, + warnings: ['No provider data found - skipping provider/model migration'] + } + } + + this.providers = llmState.providers + this.settings = llmState.settings ?? {} + this.totalModelCount = this.providers.reduce((count, provider) => { + const uniqueModelIds = new Set((provider.models ?? []).map((model) => model.id)) + return count + uniqueModelIds.size + }, 0) + + logger.info('Preparation completed', { + providerCount: this.providers.length, + modelCount: this.totalModelCount + }) + + return { + success: true, + itemCount: this.providers.length, + warnings: warnings.length > 0 ? warnings : undefined + } + } catch (error) { + logger.error('Preparation failed', error as Error) + return { + success: false, + itemCount: 0, + warnings: [error instanceof Error ? error.message : String(error)] + } + } + } + + async execute(ctx: MigrationContext): Promise { + if (this.providers.length === 0) { + return { success: true, processedCount: 0 } + } + + let processedProviders = 0 + let processedModels = 0 + + try { + await ctx.db.transaction(async (tx) => { + for (let providerIndex = 0; providerIndex < this.providers.length; providerIndex++) { + const provider = this.providers[providerIndex] + await tx.insert(userProviderTable).values(transformProvider(provider, this.settings, providerIndex)) + processedProviders++ + + const uniqueModels = Array.from(new Map((provider.models ?? []).map((model) => [model.id, model])).values()) + + for (let modelIndex = 0; modelIndex < uniqueModels.length; modelIndex += BATCH_SIZE) { + const batch = uniqueModels + .slice(modelIndex, modelIndex + BATCH_SIZE) + .map((model, batchIndex) => transformModel(model, provider.id, modelIndex + batchIndex)) + + if (batch.length > 0) { + await tx.insert(userModelTable).values(batch) + processedModels += batch.length + } + } + + this.reportProgress( + Math.round(((providerIndex + 1) / this.providers.length) * 100), + `Migrated ${processedProviders}/${this.providers.length} providers and ${processedModels} models` + ) + } + }) + + logger.info('Execute completed', { + processedProviders, + processedModels + }) + + return { + success: true, + processedCount: processedProviders + } + } catch (error) { + logger.error('Execute failed', error as Error) + return { + success: false, + processedCount: processedProviders, + error: error instanceof Error ? error.message : String(error) + } + } + } + + async validate(ctx: MigrationContext): Promise { + try { + const errors: { key: string; message: string }[] = [] + + const providerResult = await ctx.db.select({ count: sql`count(*)` }).from(userProviderTable).get() + const modelResult = await ctx.db.select({ count: sql`count(*)` }).from(userModelTable).get() + const targetProviderCount = providerResult?.count ?? 0 + const targetModelCount = modelResult?.count ?? 0 + + if (targetProviderCount !== this.providers.length) { + errors.push({ + key: 'provider_count_mismatch', + message: `Expected ${this.providers.length} providers but found ${targetProviderCount}` + }) + } + + if (targetModelCount !== this.totalModelCount) { + errors.push({ + key: 'model_count_mismatch', + message: `Expected ${this.totalModelCount} models but found ${targetModelCount}` + }) + } + + const sampleProviders = await ctx.db.select().from(userProviderTable).limit(5).all() + for (const provider of sampleProviders) { + const sourceProvider = this.providers.find((item) => item.id === provider.providerId) + if (sourceProvider?.apiKey && (!provider.apiKeys || provider.apiKeys.length === 0)) { + errors.push({ + key: `missing_api_key_${provider.providerId}`, + message: `Provider ${provider.providerId} should include migrated API keys` + }) + } + } + + return { + success: errors.length === 0, + errors, + stats: { + sourceCount: this.providers.length, + targetCount: targetProviderCount, + skippedCount: 0 + } + } + } catch (error) { + logger.error('Validation failed', error as Error) + return { + success: false, + errors: [{ key: 'validation', message: error instanceof Error ? error.message : String(error) }], + stats: { + sourceCount: this.providers.length, + targetCount: 0, + skippedCount: 0 + } + } + } + } +} diff --git a/src/main/data/migration/v2/migrators/index.ts b/src/main/data/migration/v2/migrators/index.ts index f17947d237a..b710b435831 100644 --- a/src/main/data/migration/v2/migrators/index.ts +++ b/src/main/data/migration/v2/migrators/index.ts @@ -12,6 +12,7 @@ import { KnowledgeMigrator } from './KnowledgeMigrator' import { McpServerMigrator } from './McpServerMigrator' import { MiniAppMigrator } from './MiniAppMigrator' import { PreferencesMigrator } from './PreferencesMigrator' +import { ProviderModelMigrator } from './ProviderModelMigrator' import { TranslateMigrator } from './TranslateMigrator' // Export migrator classes @@ -23,6 +24,7 @@ export { McpServerMigrator, MiniAppMigrator, PreferencesMigrator, + ProviderModelMigrator, TranslateMigrator } @@ -35,6 +37,7 @@ export function getAllMigrators() { new PreferencesMigrator(), new MiniAppMigrator(), new McpServerMigrator(), + new ProviderModelMigrator(), new AssistantMigrator(), new KnowledgeMigrator(), new ChatMigrator(), diff --git a/src/main/data/migration/v2/migrators/mappings/ProviderModelMappings.ts b/src/main/data/migration/v2/migrators/mappings/ProviderModelMappings.ts new file mode 100644 index 00000000000..4a2f4bc40a5 --- /dev/null +++ b/src/main/data/migration/v2/migrators/mappings/ProviderModelMappings.ts @@ -0,0 +1,425 @@ +/** + * Provider/model migration transforms for Redux llm -> SQLite user tables. + */ + +import { + ENDPOINT_TYPE, + type EndpointType, + MODEL_CAPABILITY, + type ModelCapability, + normalizeModelId +} from '@cherrystudio/provider-catalog' +import type { NewUserModel } from '@data/db/schemas/userModel' +import type { NewUserProvider } from '@data/db/schemas/userProvider' +import type { RuntimeModelPricing } from '@shared/data/types/model' +import type { ApiFeatures, ApiKeyEntry, AuthConfig, ProviderSettings } from '@shared/data/types/provider' +import type { Model as LegacyModel, ModelType, Provider as LegacyProvider } from '@types' +import { v4 as uuidv4 } from 'uuid' + +/** Legacy llm.settings structure used by a few providers. */ +export interface OldLlmSettings { + ollama?: { keepAliveTime?: number } + lmstudio?: { keepAliveTime?: number } + gpustack?: { keepAliveTime?: number } + vertexai?: { + serviceAccount?: { + privateKey?: string + clientEmail?: string + } + projectId?: string + location?: string + } + awsBedrock?: { + authType?: string + accessKeyId?: string + secretAccessKey?: string + apiKey?: string + region?: string + } + cherryIn?: { + accessToken?: string + refreshToken?: string + } +} + +const CAPABILITY_MAP: Partial> = { + text: undefined, + vision: MODEL_CAPABILITY.IMAGE_RECOGNITION, + reasoning: MODEL_CAPABILITY.REASONING, + function_calling: MODEL_CAPABILITY.FUNCTION_CALL, + embedding: MODEL_CAPABILITY.EMBEDDING, + web_search: MODEL_CAPABILITY.WEB_SEARCH, + rerank: MODEL_CAPABILITY.RERANK +} + +/** Legacy string endpoint/provider-type keys → catalog numeric EndpointType */ +const ENDPOINT_MAP: Partial> = { + openai: ENDPOINT_TYPE.OPENAI_CHAT_COMPLETIONS, + 'openai-response': ENDPOINT_TYPE.OPENAI_RESPONSES, + anthropic: ENDPOINT_TYPE.ANTHROPIC_MESSAGES, + gemini: ENDPOINT_TYPE.GOOGLE_GENERATE_CONTENT, + 'image-generation': ENDPOINT_TYPE.OPENAI_IMAGE_GENERATION, + 'jina-rerank': ENDPOINT_TYPE.JINA_RERANK, + 'new-api': ENDPOINT_TYPE.OPENAI_CHAT_COMPLETIONS, + gateway: ENDPOINT_TYPE.OPENAI_CHAT_COMPLETIONS, + ollama: ENDPOINT_TYPE.OLLAMA_CHAT +} + +const REASONING_FORMAT_MAP: Partial> = { + openai: 'openai-chat', + 'openai-response': 'openai-responses', + anthropic: 'anthropic', + gemini: 'gemini', + 'new-api': 'openai-chat', + gateway: 'openai-chat', + ollama: 'openai-chat' +} + +const SYSTEM_PROVIDER_IDS = new Set([ + 'cherryin', + 'silicon', + 'aihubmix', + 'ocoolai', + 'deepseek', + 'ppio', + 'alayanew', + 'qiniu', + 'dmxapi', + 'burncloud', + 'tokenflux', + '302ai', + 'cephalon', + 'lanyun', + 'ph8', + 'openrouter', + 'ollama', + 'ovms', + 'new-api', + 'lmstudio', + 'anthropic', + 'openai', + 'azure-openai', + 'gemini', + 'vertexai', + 'github', + 'copilot', + 'zhipu', + 'yi', + 'moonshot', + 'baichuan', + 'dashscope', + 'stepfun', + 'doubao', + 'infini', + 'minimax', + 'groq', + 'together', + 'fireworks', + 'nvidia', + 'grok', + 'hyperbolic', + 'mistral', + 'jina', + 'perplexity', + 'modelscope', + 'xirang', + 'hunyuan', + 'tencent-cloud-ti', + 'baidu-cloud', + 'gpustack', + 'voyageai', + 'aws-bedrock', + 'poe', + 'aionly', + 'longcat', + 'huggingface', + 'sophnet', + 'gateway', + 'cerebras', + 'mimo', + 'gitee-ai', + 'minimax-global', + 'zai' +]) + +export function transformProvider( + legacy: LegacyProvider, + settings: OldLlmSettings, + sortOrder: number +): NewUserProvider { + const endpointType = ENDPOINT_MAP[legacy.type] + + return { + providerId: legacy.id, + presetProviderId: SYSTEM_PROVIDER_IDS.has(legacy.id) ? legacy.id : null, + name: legacy.name, + baseUrls: buildBaseUrls(legacy, endpointType), + defaultChatEndpoint: endpointType ?? null, + apiKeys: buildApiKeys(legacy.apiKey), + authConfig: buildAuthConfig(legacy, settings), + apiFeatures: buildApiFeatures(legacy), + providerSettings: buildProviderSettings(legacy, settings), + reasoningFormatType: REASONING_FORMAT_MAP[legacy.type] ?? null, + isEnabled: legacy.enabled ?? true, + sortOrder + } +} + +function buildBaseUrls(legacy: LegacyProvider, endpointType: EndpointType | undefined): NewUserProvider['baseUrls'] { + const urls: Partial> = {} + + if (legacy.apiHost && endpointType !== undefined) { + urls[endpointType] = legacy.apiHost + } + + if (legacy.anthropicApiHost) { + urls[ENDPOINT_TYPE.ANTHROPIC_MESSAGES] = legacy.anthropicApiHost + } + + return Object.keys(urls).length > 0 ? urls : null +} + +function buildApiKeys(apiKey: string): ApiKeyEntry[] { + if (!apiKey) { + return [] + } + + return apiKey + .split(',') + .map((key) => key.trim()) + .filter(Boolean) + .map((key) => ({ + id: uuidv4(), + key, + isEnabled: true, + createdAt: Date.now() + })) +} + +function buildAuthConfig(legacy: LegacyProvider, settings: OldLlmSettings): AuthConfig | null { + if (legacy.isVertex && settings.vertexai) { + const vertex = settings.vertexai + return { + type: 'iam-gcp', + project: vertex.projectId ?? '', + location: vertex.location ?? '', + credentials: vertex.serviceAccount + ? { + privateKey: vertex.serviceAccount.privateKey, + clientEmail: vertex.serviceAccount.clientEmail + } + : undefined + } + } + + if (legacy.id === 'aws-bedrock' && settings.awsBedrock) { + const aws = settings.awsBedrock + return { + type: 'iam-aws', + region: aws.region ?? '', + accessKeyId: aws.accessKeyId, + secretAccessKey: aws.secretAccessKey + } + } + + if (legacy.id === 'azure-openai' && legacy.apiVersion) { + return { + type: 'iam-azure', + apiVersion: legacy.apiVersion + } + } + + if ( + legacy.id === 'cherryin' && + settings.cherryIn && + (settings.cherryIn.accessToken || settings.cherryIn.refreshToken) + ) { + return { + type: 'oauth', + clientId: '', + accessToken: settings.cherryIn.accessToken, + refreshToken: settings.cherryIn.refreshToken + } + } + + if (legacy.authType === 'oauth') { + return { + type: 'oauth', + clientId: '' + } + } + + return { + type: 'api-key' + } +} + +function buildApiFeatures(legacy: LegacyProvider): ApiFeatures | null { + const apiOptions = legacy.apiOptions + const features: ApiFeatures = {} + let hasValue = false + + const notArrayContent = apiOptions?.isNotSupportArrayContent ?? legacy.isNotSupportArrayContent + if (notArrayContent != null) { + features.arrayContent = !notArrayContent + hasValue = true + } + + const notStreamOptions = apiOptions?.isNotSupportStreamOptions ?? legacy.isNotSupportStreamOptions + if (notStreamOptions != null) { + features.streamOptions = !notStreamOptions + hasValue = true + } + + const supportsDeveloperRole = + apiOptions?.isSupportDeveloperRole ?? + (legacy.isNotSupportDeveloperRole != null ? !legacy.isNotSupportDeveloperRole : undefined) + if (supportsDeveloperRole != null) { + features.developerRole = supportsDeveloperRole + hasValue = true + } + + const supportsServiceTier = + apiOptions?.isSupportServiceTier ?? + (legacy.isNotSupportServiceTier != null ? !legacy.isNotSupportServiceTier : undefined) + if (supportsServiceTier != null) { + features.serviceTier = supportsServiceTier + hasValue = true + } + + if (apiOptions?.isNotSupportEnableThinking != null) { + features.enableThinking = !apiOptions.isNotSupportEnableThinking + hasValue = true + } + + if (apiOptions?.isNotSupportVerbosity != null) { + features.verbosity = !apiOptions.isNotSupportVerbosity + hasValue = true + } + + return hasValue ? features : null +} + +function buildProviderSettings(legacy: LegacyProvider, llmSettings: OldLlmSettings): ProviderSettings | null { + const settings: ProviderSettings = {} + let hasValue = false + + const keepAliveSettingsKey: Partial> = { + ollama: 'ollama', + lmstudio: 'lmstudio', + gpustack: 'gpustack' + } + + const keepAliveSource = keepAliveSettingsKey[legacy.id] + if (keepAliveSource) { + const keepAliveSettings = llmSettings[keepAliveSource] as { keepAliveTime?: number } | undefined + if (keepAliveSettings?.keepAliveTime != null) { + settings.keepAliveTime = keepAliveSettings.keepAliveTime + hasValue = true + } + } + + if (legacy.serviceTier) { + settings.serviceTier = legacy.serviceTier + hasValue = true + } + + if (legacy.verbosity) { + settings.verbosity = legacy.verbosity + hasValue = true + } + + if (legacy.rateLimit != null) { + settings.rateLimit = legacy.rateLimit + hasValue = true + } + + if (legacy.extra_headers && Object.keys(legacy.extra_headers).length > 0) { + settings.extraHeaders = legacy.extra_headers + hasValue = true + } + + if (legacy.notes) { + settings.notes = legacy.notes + hasValue = true + } + + if (legacy.anthropicCacheControl) { + settings.cacheControl = { + enabled: true, + tokenThreshold: legacy.anthropicCacheControl.tokenThreshold, + cacheSystemMessage: legacy.anthropicCacheControl.cacheSystemMessage, + cacheLastNMessages: legacy.anthropicCacheControl.cacheLastNMessages + } + hasValue = true + } + + return hasValue ? settings : null +} + +export function transformModel(legacy: LegacyModel, providerId: string, sortOrder: number): NewUserModel { + const hasCustomizedCapabilities = + legacy.capabilities?.some((capability) => capability.isUserSelected !== undefined) ?? false + + return { + providerId, + modelId: legacy.id, + presetModelId: normalizeModelId(legacy.id), + name: legacy.name ?? null, + description: legacy.description ?? null, + group: legacy.group ?? null, + capabilities: mapCapabilities(legacy.capabilities), + inputModalities: null, + outputModalities: null, + endpointTypes: mapEndpointTypes(legacy.endpoint_type, legacy.supported_endpoint_types), + contextWindow: null, + maxOutputTokens: null, + supportsStreaming: legacy.supported_text_delta ?? null, + reasoning: null, + parameters: null, + pricing: mapPricing(legacy.pricing), + isEnabled: true, + isHidden: false, + sortOrder, + userOverrides: hasCustomizedCapabilities ? ['capabilities'] : null + } +} + +function mapCapabilities(capabilities?: LegacyModel['capabilities']): ModelCapability[] | null { + if (!capabilities || capabilities.length === 0) { + return null + } + + const mapped = capabilities + .map((capability) => CAPABILITY_MAP[capability.type]) + .filter((capability): capability is ModelCapability => capability !== undefined) + + return mapped.length > 0 ? Array.from(new Set(mapped)) : null +} + +function mapEndpointTypes( + endpointType?: LegacyModel['endpoint_type'], + supportedEndpointTypes?: LegacyModel['supported_endpoint_types'] +): EndpointType[] | null { + const sourceTypes = supportedEndpointTypes ?? (endpointType ? [endpointType] : []) + if (sourceTypes.length === 0) { + return null + } + + const mapped = sourceTypes + .map((type) => (type ? ENDPOINT_MAP[type] : undefined)) + .filter((type): type is EndpointType => type !== undefined) + + return mapped.length > 0 ? Array.from(new Set(mapped)) : null +} + +function mapPricing(pricing?: LegacyModel['pricing']): RuntimeModelPricing | null { + if (!pricing) { + return null + } + + return { + input: { perMillionTokens: pricing.input_per_million_tokens }, + output: { perMillionTokens: pricing.output_per_million_tokens } + } +} diff --git a/src/main/data/services/ModelService.ts b/src/main/data/services/ModelService.ts new file mode 100644 index 00000000000..e3e7b57a1c1 --- /dev/null +++ b/src/main/data/services/ModelService.ts @@ -0,0 +1,352 @@ +/** + * Model Service - handles model CRUD operations + * + * Provides business logic for: + * - Model CRUD operations + * - Row to Model conversion + * - Catalog import support + */ + +import type { EndpointType, Modality, ModelCapability } from '@cherrystudio/provider-catalog' +import type { NewUserModel, UserModel } from '@data/db/schemas/userModel' +import { isCatalogEnrichableField, userModelTable } from '@data/db/schemas/userModel' +import { loggerService } from '@logger' +import { application } from '@main/core/application' +import { DataApiErrorFactory } from '@shared/data/api' +import type { CreateModelDto, ListModelsQuery, UpdateModelDto } from '@shared/data/api/schemas/models' +import type { Model, RuntimeModelPricing, RuntimeParameterSupport, RuntimeReasoning } from '@shared/data/types/model' +import { createUniqueModelId } from '@shared/data/types/model' +import { mergeModelConfig } from '@shared/data/utils/modelMerger' +import { and, eq, inArray, type SQL } from 'drizzle-orm' + +import { catalogService } from './ProviderCatalogService' + +const logger = loggerService.withContext('DataApi:ModelService') + +/** + * Convert database row to Model entity + * + * Since user_model stores fully resolved data (merged at add-time), + * this is a direct field mapping with no runtime merge needed. + */ +function rowToRuntimeModel(row: UserModel): Model { + return { + id: createUniqueModelId(row.providerId, row.modelId), + providerId: row.providerId, + apiModelId: row.modelId, + name: row.name ?? row.modelId, + description: row.description ?? undefined, + group: row.group ?? undefined, + capabilities: (row.capabilities ?? []) as ModelCapability[], + inputModalities: (row.inputModalities ?? undefined) as Modality[] | undefined, + outputModalities: (row.outputModalities ?? undefined) as Modality[] | undefined, + contextWindow: row.contextWindow ?? undefined, + maxOutputTokens: row.maxOutputTokens ?? undefined, + endpointTypes: (row.endpointTypes ?? undefined) as EndpointType[] | undefined, + supportsStreaming: row.supportsStreaming ?? true, + reasoning: (row.reasoning ?? undefined) as RuntimeReasoning | undefined, + parameterSupport: (row.parameters ?? undefined) as RuntimeParameterSupport | undefined, + pricing: (row.pricing ?? undefined) as RuntimeModelPricing | undefined, + isEnabled: row.isEnabled ?? true, + isHidden: row.isHidden ?? false + } +} + +export class ModelService { + private static instance: ModelService + + private constructor() {} + + public static getInstance(): ModelService { + if (!ModelService.instance) { + ModelService.instance = new ModelService() + } + return ModelService.instance + } + + /** + * List models with optional filters + */ + async list(query: ListModelsQuery): Promise { + const db = application.get('DbService').getDb() + + const conditions: SQL[] = [] + + if (query.providerId) { + conditions.push(eq(userModelTable.providerId, query.providerId)) + } + + if (query.enabled !== undefined) { + conditions.push(eq(userModelTable.isEnabled, query.enabled)) + } + + const rows = await db + .select() + .from(userModelTable) + .where(conditions.length > 0 ? and(...conditions) : undefined) + .orderBy(userModelTable.sortOrder) + + let models = rows.map(rowToRuntimeModel) + + // Post-filter by capability (JSON array column, can't filter in SQL easily) + if (query.capability !== undefined) { + const cap = query.capability + models = models.filter((m) => m.capabilities.includes(cap)) + } + + return models + } + + /** + * Get a model by composite key (providerId + modelId) + */ + async getByKey(providerId: string, modelId: string): Promise { + const db = application.get('DbService').getDb() + + const [row] = await db + .select() + .from(userModelTable) + .where(and(eq(userModelTable.providerId, providerId), eq(userModelTable.modelId, modelId))) + .limit(1) + + if (!row) { + throw DataApiErrorFactory.notFound('Model', `${providerId}/${modelId}`) + } + + return rowToRuntimeModel(row) + } + + /** + * Create a new model + * + * Automatically enriches from catalog preset data when a match is found. + * DTO values take priority over catalog (user > catalogOverride > preset). + */ + async create(dto: CreateModelDto): Promise { + const db = application.get('DbService').getDb() + + // Look up catalog data for auto-enrichment + const { presetModel, catalogOverride, reasoningFormatType } = catalogService.lookupModel( + dto.providerId, + dto.modelId + ) + + let values: NewUserModel + + if (presetModel) { + // Catalog match found — merge DTO with preset data + const userRow = { + providerId: dto.providerId, + modelId: dto.modelId, + presetModelId: presetModel.id, + name: dto.name ?? null, + description: dto.description ?? null, + group: dto.group ?? null, + capabilities: dto.capabilities ?? null, + inputModalities: dto.inputModalities ?? null, + outputModalities: dto.outputModalities ?? null, + endpointTypes: dto.endpointTypes ?? null, + contextWindow: dto.contextWindow ?? null, + maxOutputTokens: dto.maxOutputTokens ?? null, + supportsStreaming: dto.supportsStreaming ?? null, + reasoning: dto.reasoning ?? null + } + + const merged = mergeModelConfig(userRow, catalogOverride, presetModel, dto.providerId, reasoningFormatType) + + values = { + providerId: dto.providerId, + modelId: dto.modelId, + presetModelId: presetModel.id, + name: merged.name, + description: merged.description ?? null, + group: merged.group ?? null, + capabilities: merged.capabilities, + inputModalities: merged.inputModalities ?? null, + outputModalities: merged.outputModalities ?? null, + endpointTypes: merged.endpointTypes ?? null, + contextWindow: merged.contextWindow ?? null, + maxOutputTokens: merged.maxOutputTokens ?? null, + supportsStreaming: merged.supportsStreaming, + reasoning: merged.reasoning ?? null, + parameters: merged.parameterSupport ?? null, + pricing: merged.pricing ?? null + } + + logger.info('Created model with catalog enrichment', { + providerId: dto.providerId, + modelId: dto.modelId, + presetModelId: presetModel.id + }) + } else { + // No catalog match — store as custom model + values = { + providerId: dto.providerId, + modelId: dto.modelId, + presetModelId: dto.presetModelId ?? null, + name: dto.name ?? null, + description: dto.description ?? null, + group: dto.group ?? null, + capabilities: dto.capabilities ?? null, + inputModalities: dto.inputModalities ?? null, + outputModalities: dto.outputModalities ?? null, + endpointTypes: dto.endpointTypes ?? null, + contextWindow: dto.contextWindow ?? null, + maxOutputTokens: dto.maxOutputTokens ?? null, + supportsStreaming: dto.supportsStreaming ?? null, + reasoning: dto.reasoning ?? null, + parameters: dto.parameterSupport ?? null, + pricing: dto.pricing ?? null + } + + logger.info('Created custom model (no catalog match)', { + providerId: dto.providerId, + modelId: dto.modelId + }) + } + + const [row] = await db.insert(userModelTable).values(values).returning() + + return rowToRuntimeModel(row) + } + + /** + * Update an existing model + */ + async update(providerId: string, modelId: string, dto: UpdateModelDto): Promise { + const db = application.get('DbService').getDb() + + // Fetch existing row (also verifies existence) + const [existing] = await db + .select() + .from(userModelTable) + .where(and(eq(userModelTable.providerId, providerId), eq(userModelTable.modelId, modelId))) + .limit(1) + + if (!existing) { + throw DataApiErrorFactory.notFound('Model', `${providerId}/${modelId}`) + } + + // Build update object + const updates: Partial = {} + + if (dto.name !== undefined) updates.name = dto.name + if (dto.description !== undefined) updates.description = dto.description + if (dto.group !== undefined) updates.group = dto.group + if (dto.capabilities !== undefined) updates.capabilities = dto.capabilities + if (dto.endpointTypes !== undefined) updates.endpointTypes = dto.endpointTypes + if (dto.supportsStreaming !== undefined) updates.supportsStreaming = dto.supportsStreaming + if (dto.contextWindow !== undefined) updates.contextWindow = dto.contextWindow + if (dto.maxOutputTokens !== undefined) updates.maxOutputTokens = dto.maxOutputTokens + if (dto.reasoning !== undefined) updates.reasoning = dto.reasoning + if (dto.pricing !== undefined) updates.pricing = dto.pricing + if (dto.isEnabled !== undefined) updates.isEnabled = dto.isEnabled + if (dto.isHidden !== undefined) updates.isHidden = dto.isHidden + if (dto.sortOrder !== undefined) updates.sortOrder = dto.sortOrder + if (dto.notes !== undefined) updates.notes = dto.notes + + // Track which catalog-enrichable fields the user explicitly changed + const changedEnrichableFields = Object.keys(dto).filter(isCatalogEnrichableField) + if (changedEnrichableFields.length > 0) { + const existingOverrides = existing.userOverrides ?? [] + updates.userOverrides = [...new Set([...existingOverrides, ...changedEnrichableFields])] + } + + const [row] = await db + .update(userModelTable) + .set(updates) + .where(and(eq(userModelTable.providerId, providerId), eq(userModelTable.modelId, modelId))) + .returning() + + logger.info('Updated model', { providerId, modelId, changes: Object.keys(dto) }) + + return rowToRuntimeModel(row) + } + + /** + * Delete a model + */ + async delete(providerId: string, modelId: string): Promise { + const db = application.get('DbService').getDb() + + // Verify model exists + await this.getByKey(providerId, modelId) + + await db + .delete(userModelTable) + .where(and(eq(userModelTable.providerId, providerId), eq(userModelTable.modelId, modelId))) + + logger.info('Deleted model', { providerId, modelId }) + } + + /** + * Batch upsert models for a provider (used by CatalogService). + * Inserts new models, updates existing ones. + * Respects `userOverrides`: fields the user has explicitly modified are not overwritten. + */ + async batchUpsert(models: NewUserModel[]): Promise { + if (models.length === 0) return + + const db = application.get('DbService').getDb() + + // Pre-fetch existing userOverrides for all affected models + const providerIds = [...new Set(models.map((m) => m.providerId))] + const existingRows = await db + .select({ + providerId: userModelTable.providerId, + modelId: userModelTable.modelId, + userOverrides: userModelTable.userOverrides + }) + .from(userModelTable) + .where(inArray(userModelTable.providerId, providerIds)) + + const overridesMap = new Map>() + for (const row of existingRows) { + if (row.userOverrides && row.userOverrides.length > 0) { + overridesMap.set(`${row.providerId}:${row.modelId}`, new Set(row.userOverrides)) + } + } + + for (const model of models) { + const userOverrides = overridesMap.get(`${model.providerId}:${model.modelId}`) + + // Build the update set, skipping user-overridden fields + const set: Partial = { + presetModelId: model.presetModelId + } + const enrichableFields = { + name: model.name, + description: model.description, + group: model.group, + capabilities: model.capabilities, + inputModalities: model.inputModalities, + outputModalities: model.outputModalities, + endpointTypes: model.endpointTypes, + contextWindow: model.contextWindow, + maxOutputTokens: model.maxOutputTokens, + supportsStreaming: model.supportsStreaming, + reasoning: model.reasoning, + parameters: model.parameters, + pricing: model.pricing + } + + for (const [field, value] of Object.entries(enrichableFields)) { + if (!userOverrides?.has(field)) { + ;(set as Record)[field] = value + } + } + + await db + .insert(userModelTable) + .values(model) + .onConflictDoUpdate({ + target: [userModelTable.providerId, userModelTable.modelId], + set + }) + } + + logger.info('Batch upserted models', { count: models.length, providerId: models[0]?.providerId }) + } +} + +export const modelService = ModelService.getInstance() diff --git a/src/main/data/services/ProviderCatalogService.ts b/src/main/data/services/ProviderCatalogService.ts new file mode 100644 index 00000000000..86c28225138 --- /dev/null +++ b/src/main/data/services/ProviderCatalogService.ts @@ -0,0 +1,584 @@ +/** + * Catalog Service - imports catalog data into SQLite + * + * Responsible for: + * - Reading catalog protobuf files (models.pb, provider-models.pb, providers.pb) + * - Merging configurations using mergeModelConfig/mergeProviderConfig + * - Writing resolved data to user_model / user_provider tables + * + * Called during app initialization or when a user adds a preset provider. + */ + +import { join } from 'node:path' + +import type { ProtoModelConfig, ProtoProviderConfig, ProtoProviderModelOverride } from '@cherrystudio/provider-catalog' +import { + EndpointType, + readModelCatalog, + readProviderCatalog, + readProviderModelCatalog +} from '@cherrystudio/provider-catalog' +import type { NewUserModel } from '@data/db/schemas/userModel' +import { userModelTable } from '@data/db/schemas/userModel' +import type { NewUserProvider } from '@data/db/schemas/userProvider' +import { loggerService } from '@logger' +import { isDev } from '@main/constant' +import { application } from '@main/core/application' +import type { Model } from '@shared/data/types/model' +import { mergeModelConfig } from '@shared/data/utils/modelMerger' +import { isNotNull } from 'drizzle-orm' + +import { modelService } from './ModelService' +import { providerService } from './ProviderService' + +const logger = loggerService.withContext('DataApi:CatalogService') + +/** Map proto ProviderReasoningFormat oneof case to runtime type string */ +const CASE_TO_TYPE: Record = { + openaiChat: 'openai-chat', + openaiResponses: 'openai-responses', + anthropic: 'anthropic', + gemini: 'gemini', + openrouter: 'openrouter', + enableThinking: 'enable-thinking', + thinkingType: 'thinking-type', + dashscope: 'dashscope', + selfHosted: 'self-hosted' +} + +export class CatalogService { + private static instance: CatalogService + + private catalogModels: ProtoModelConfig[] | null = null + private catalogProviderModels: ProtoProviderModelOverride[] | null = null + private catalogProviders: ProtoProviderConfig[] | null = null + + private constructor() {} + + public static getInstance(): CatalogService { + if (!CatalogService.instance) { + CatalogService.instance = new CatalogService() + } + return CatalogService.instance + } + + /** + * Get the path to catalog data directory + */ + private getCatalogDataPath(): string { + if (isDev) { + return join(__dirname, '..', '..', 'packages', 'provider-catalog', 'data') + } + return join(process.resourcesPath, 'packages', 'provider-catalog', 'data') + } + + /** + * Load and cache catalog models from models.pb + */ + private loadCatalogModels(): ProtoModelConfig[] { + if (this.catalogModels) return this.catalogModels + + try { + const dataPath = this.getCatalogDataPath() + const data = readModelCatalog(join(dataPath, 'models.pb')) + const models = data.models ?? [] + this.catalogModels = models + logger.info('Loaded catalog models', { count: models.length }) + return models + } catch (error) { + logger.warn('Failed to load catalog models.pb', { error }) + return [] + } + } + + /** + * Load and cache provider-model overrides from provider-models.pb + */ + private loadProviderModels(): ProtoProviderModelOverride[] { + if (this.catalogProviderModels) return this.catalogProviderModels + + try { + const dataPath = this.getCatalogDataPath() + const data = readProviderModelCatalog(join(dataPath, 'provider-models.pb')) + const overrides = data.overrides ?? [] + this.catalogProviderModels = overrides + logger.info('Loaded catalog provider-models', { count: overrides.length }) + return overrides + } catch (error) { + logger.warn('Failed to load catalog provider-models.pb', { error }) + return [] + } + } + + /** + * Load and cache catalog providers from providers.pb + */ + private loadCatalogProviders(): ProtoProviderConfig[] { + if (this.catalogProviders) return this.catalogProviders + + try { + const dataPath = this.getCatalogDataPath() + const data = readProviderCatalog(join(dataPath, 'providers.pb')) + const providers = data.providers ?? [] + this.catalogProviders = providers + return providers + } catch (error) { + logger.warn('Failed to load catalog providers.pb', { error }) + return [] + } + } + + /** + * Get the reasoning format type string for a provider from catalog data. + * Returns the proto oneof case mapped to a runtime type string. + */ + private getReasoningFormatType(providerId: string): string | undefined { + const providers = this.loadCatalogProviders() + const provider = providers.find((p) => p.id === providerId) + const formatCase = provider?.reasoningFormat?.format.case + if (!formatCase) return undefined + return CASE_TO_TYPE[formatCase] + } + + /** + * Initialize models for a specific provider + * + * Reads catalog data, merges configurations, and writes to SQLite. + * + * @param providerId - The provider ID to initialize models for + */ + async initializeProvider(providerId: string): Promise { + const catalogModels = this.loadCatalogModels() + const providerModels = this.loadProviderModels() + const reasoningFormatType = this.getReasoningFormatType(providerId) + + // Find all overrides for this provider + const overrides = providerModels.filter((pm) => pm.providerId === providerId) + + if (overrides.length === 0) { + logger.info('No catalog overrides found for provider', { providerId }) + return [] + } + + // Build a map of catalog models by ID for fast lookup + const modelMap = new Map() + for (const model of catalogModels) { + modelMap.set(model.id, model) + } + + // Merge each override with its base model + const mergedModels: Model[] = [] + const dbRows: NewUserModel[] = [] + + for (const override of overrides) { + const baseModel = modelMap.get(override.modelId) ?? null + + if (!baseModel) { + logger.warn('Base model not found for override', { + providerId, + modelId: override.modelId + }) + continue + } + + // Merge: no user override (null), catalog override, preset model + const merged = mergeModelConfig(null, override, baseModel, providerId, reasoningFormatType) + mergedModels.push(merged) + + // Convert to DB row format — capabilities/modalities are now numeric arrays + dbRows.push({ + providerId, + modelId: baseModel.id, + presetModelId: baseModel.id, + name: merged.name, + description: merged.description ?? null, + group: merged.group ?? null, + capabilities: merged.capabilities, + inputModalities: merged.inputModalities ?? null, + outputModalities: merged.outputModalities ?? null, + endpointTypes: merged.endpointTypes ?? null, + contextWindow: merged.contextWindow ?? null, + maxOutputTokens: merged.maxOutputTokens ?? null, + supportsStreaming: merged.supportsStreaming, + reasoning: merged.reasoning ?? null, + parameters: merged.parameterSupport ?? null, + isEnabled: merged.isEnabled, + isHidden: merged.isHidden + }) + } + + // Batch upsert to database + await modelService.batchUpsert(dbRows) + + logger.info('Initialized provider models from catalog', { + providerId, + count: mergedModels.length + }) + + return mergedModels + } + + /** + * Get catalog preset models for a provider (read-only, no DB writes). + */ + getCatalogModelsByProvider(providerId: string): Model[] { + const catalogModels = this.loadCatalogModels() + const providerModels = this.loadProviderModels() + const reasoningFormatType = this.getReasoningFormatType(providerId) + + const overrides = providerModels.filter((pm) => pm.providerId === providerId) + if (overrides.length === 0) { + return [] + } + + const modelMap = new Map() + for (const model of catalogModels) { + modelMap.set(model.id, model) + } + + const mergedModels: Model[] = [] + for (const override of overrides) { + const baseModel = modelMap.get(override.modelId) ?? null + if (!baseModel) { + continue + } + mergedModels.push(mergeModelConfig(null, override, baseModel, providerId, reasoningFormatType)) + } + + return mergedModels + } + + /** + * Initialize preset providers from catalog into SQLite. + * + * Reads providers.pb, maps fields to NewUserProvider, and batch upserts. + * Also seeds the cherryai provider which is not in providers.pb. + */ + async initializePresetProviders(): Promise { + const dataPath = this.getCatalogDataPath() + let rawProviders: ReturnType['providers'] = [] + + try { + const data = readProviderCatalog(join(dataPath, 'providers.pb')) + rawProviders = data.providers + } catch (error) { + logger.warn('Failed to load providers.pb for provider import', { error }) + return + } + + const dbRows: NewUserProvider[] = rawProviders.map((p) => { + // Map catalog metadata.website to runtime websites field + const catalogWebsite = p.metadata?.website + const websites = + catalogWebsite && + (catalogWebsite.official || catalogWebsite.docs || catalogWebsite.apiKey || catalogWebsite.models) + ? { + official: catalogWebsite.official || undefined, + docs: catalogWebsite.docs || undefined, + apiKey: catalogWebsite.apiKey || undefined, + models: catalogWebsite.models || undefined + } + : null + + // Proto baseUrls uses map — convert to Record + const baseUrls: Record = {} + if (p.baseUrls) { + for (const [k, v] of Object.entries(p.baseUrls)) { + baseUrls[String(k)] = v + } + } + + // Convert proto message types to plain objects for DB storage + const modelsApiUrls: Record | null = p.modelsApiUrls + ? { + ...(p.modelsApiUrls.default ? { default: p.modelsApiUrls.default } : {}), + ...(p.modelsApiUrls.embedding ? { embedding: p.modelsApiUrls.embedding } : {}), + ...(p.modelsApiUrls.reranker ? { reranker: p.modelsApiUrls.reranker } : {}) + } + : null + + const apiFeatures = p.apiFeatures + ? { + arrayContent: p.apiFeatures.arrayContent, + streamOptions: p.apiFeatures.streamOptions, + developerRole: p.apiFeatures.developerRole, + serviceTier: p.apiFeatures.serviceTier, + verbosity: p.apiFeatures.verbosity, + enableThinking: p.apiFeatures.enableThinking + } + : null + + // Extract reasoning format type from proto oneof + const formatCase = p.reasoningFormat?.format.case + const reasoningFormatType = formatCase ? (CASE_TO_TYPE[formatCase] ?? null) : null + + return { + providerId: p.id, + presetProviderId: p.id, + name: p.name, + baseUrls: Object.keys(baseUrls).length > 0 ? baseUrls : null, + modelsApiUrls: Object.keys(modelsApiUrls ?? {}).length > 0 ? modelsApiUrls : null, + defaultChatEndpoint: p.defaultChatEndpoint ?? null, + apiFeatures, + reasoningFormatType, + websites + } + }) + + dbRows.push({ + providerId: 'cherryai', + name: 'CherryAI', + baseUrls: { + [EndpointType.OPENAI_CHAT_COMPLETIONS]: 'https://api.cherry-ai.com' + }, + defaultChatEndpoint: EndpointType.OPENAI_CHAT_COMPLETIONS + }) + + await providerService.batchUpsert(dbRows) + + logger.info('Initialized preset providers from catalog', { count: dbRows.length }) + } + + /** + * Initialize all preset providers from catalog + * + * Called during app startup and after migration to seed the database with catalog data. + * Seeds provider configurations and enriches existing user models with catalog data. + */ + async initializeAllPresetProviders(): Promise { + await this.initializePresetProviders() + await this.enrichExistingModels() + + logger.info('Initialized all preset providers and enriched existing models') + } + + /** + * Enrich existing user models with catalog data + * + * For each user model that has a presetModelId, looks up the catalog model + * and updates capabilities, modalities, contextWindow, maxOutputTokens, + * reasoning, pricing, etc. + * + * This bridges the gap between: + * - Migration: inserts user models with null catalog fields + * - Catalog: has rich model metadata (capabilities, limits, pricing) + * + * Uses presetModelId to match user models to catalog models. + */ + async enrichExistingModels(): Promise { + const catalogModels = this.loadCatalogModels() + const providerModels = this.loadProviderModels() + + if (catalogModels.length === 0) { + logger.warn('No catalog models loaded, skipping model enrichment') + return + } + + // Build lookup maps + const modelMap = new Map() + for (const m of catalogModels) { + modelMap.set(m.id, m) + } + + const overridesByProvider = new Map>() + for (const pm of providerModels) { + let providerMap = overridesByProvider.get(pm.providerId) + if (!providerMap) { + providerMap = new Map() + overridesByProvider.set(pm.providerId, providerMap) + } + providerMap.set(pm.modelId, pm) + } + + // Query all user models that have a presetModelId + const db = application.get('DbService').getDb() + const userModels = await db.select().from(userModelTable).where(isNotNull(userModelTable.presetModelId)) + + if (userModels.length === 0) { + logger.info('No user models with presetModelId found, skipping enrichment') + return + } + + const updateRows: NewUserModel[] = [] + let skippedCount = 0 + + for (const row of userModels) { + const presetModelId = row.presetModelId! + const presetModel = modelMap.get(presetModelId) + + if (!presetModel) { + skippedCount++ + continue + } + + const providerOverrides = overridesByProvider.get(row.providerId) + const catalogOverride = providerOverrides?.get(presetModelId) ?? null + const reasoningFormatType = this.getReasoningFormatType(row.providerId) + + // Merge catalog data with user data + const merged = mergeModelConfig( + { + providerId: row.providerId, + modelId: row.modelId, + presetModelId, + name: row.name, + description: row.description, + group: row.group, + capabilities: row.capabilities, + inputModalities: row.inputModalities, + outputModalities: row.outputModalities, + endpointTypes: row.endpointTypes, + contextWindow: row.contextWindow, + maxOutputTokens: row.maxOutputTokens, + supportsStreaming: row.supportsStreaming, + reasoning: row.reasoning, + isEnabled: row.isEnabled, + isHidden: row.isHidden + }, + catalogOverride, + presetModel, + row.providerId, + reasoningFormatType + ) + + updateRows.push({ + providerId: row.providerId, + modelId: row.modelId, + presetModelId, + name: merged.name, + description: merged.description ?? null, + group: merged.group ?? null, + capabilities: merged.capabilities, + inputModalities: merged.inputModalities ?? null, + outputModalities: merged.outputModalities ?? null, + endpointTypes: merged.endpointTypes ?? null, + contextWindow: merged.contextWindow ?? null, + maxOutputTokens: merged.maxOutputTokens ?? null, + supportsStreaming: merged.supportsStreaming, + reasoning: merged.reasoning ?? null, + pricing: merged.pricing ?? null, + isEnabled: merged.isEnabled, + isHidden: merged.isHidden + }) + } + + if (updateRows.length > 0) { + await modelService.batchUpsert(updateRows) + } + + logger.info('Model enrichment completed', { + total: userModels.length, + enriched: updateRows.length, + skipped: skippedCount, + catalogSize: catalogModels.length + }) + } + + /** + * Look up catalog data for a single model + * + * Returns the preset base model and provider-level override (if any). + * Used by ModelService.create to auto-enrich models at save time. + */ + lookupModel( + providerId: string, + modelId: string + ): { + presetModel: ProtoModelConfig | null + catalogOverride: ProtoProviderModelOverride | null + reasoningFormatType: string | undefined + } { + const catalogModels = this.loadCatalogModels() + const providerModels = this.loadProviderModels() + + const presetModel = catalogModels.find((m) => m.id === modelId) ?? null + const catalogOverride = providerModels.find((pm) => pm.providerId === providerId && pm.modelId === modelId) ?? null + const reasoningFormatType = this.getReasoningFormatType(providerId) + + return { presetModel, catalogOverride, reasoningFormatType } + } + + /** + * Resolve raw model entries against catalog data + * + * For each raw entry, looks up catalog preset + provider override + * and produces an enriched Model via mergeModelConfig. + * Models not found in catalog are returned with minimal data. + * + * Used by the renderer to display enriched models in ManageModelsPopup + * before the user adds them. + */ + resolveModels( + providerId: string, + rawModels: Array<{ + modelId: string + name?: string + group?: string + description?: string + endpointTypes?: number[] + }> + ): Model[] { + const catalogModels = this.loadCatalogModels() + const providerModels = this.loadProviderModels() + const reasoningFormatType = this.getReasoningFormatType(providerId) + + // Build lookup maps + const modelMap = new Map() + for (const m of catalogModels) { + modelMap.set(m.id, m) + } + const overrideMap = new Map() + for (const pm of providerModels) { + if (pm.providerId === providerId) { + overrideMap.set(pm.modelId, pm) + } + } + + const results: Model[] = [] + const seen = new Set() + + for (const raw of rawModels) { + if (!raw.modelId || seen.has(raw.modelId)) continue + seen.add(raw.modelId) + + const presetModel = modelMap.get(raw.modelId) ?? null + const catalogOverride = overrideMap.get(raw.modelId) ?? null + + // Build a minimal user row from the raw entry + const userRow = { + providerId, + modelId: raw.modelId, + presetModelId: presetModel ? presetModel.id : null, + name: raw.name ?? null, + group: raw.group ?? null, + description: raw.description ?? null, + endpointTypes: raw.endpointTypes ?? null + } + + try { + if (presetModel) { + // Catalog match found — merge with preset data + results.push(mergeModelConfig(userRow, catalogOverride, presetModel, providerId, reasoningFormatType)) + } else { + // No catalog match — return as custom model (no presetModelId) + results.push(mergeModelConfig({ ...userRow, presetModelId: null }, null, null, providerId)) + } + } catch (error) { + logger.warn('Failed to resolve model', { providerId, modelId: raw.modelId, error }) + } + } + + return results + } + + /** + * Clear cached catalog data + */ + clearCache(): void { + this.catalogModels = null + this.catalogProviderModels = null + this.catalogProviders = null + } +} + +export const catalogService = CatalogService.getInstance() diff --git a/src/main/data/services/ProviderService.ts b/src/main/data/services/ProviderService.ts new file mode 100644 index 00000000000..bea28609412 --- /dev/null +++ b/src/main/data/services/ProviderService.ts @@ -0,0 +1,369 @@ +/** + * Provider Service - handles provider CRUD operations + * + * Provides business logic for: + * - Provider CRUD operations + * - Row to Provider conversion + */ + +import type { NewUserProvider, UserProvider } from '@data/db/schemas/userProvider' +import { userProviderTable } from '@data/db/schemas/userProvider' +import { loggerService } from '@logger' +import { application } from '@main/core/application' +import { DataApiErrorFactory } from '@shared/data/api' +import type { CreateProviderDto, ListProvidersQuery, UpdateProviderDto } from '@shared/data/api/schemas/providers' +import type { + ApiKeyEntry, + AuthConfig, + AuthType, + Provider, + ProviderSettings, + RuntimeApiFeatures +} from '@shared/data/types/provider' +import { DEFAULT_API_FEATURES, DEFAULT_PROVIDER_SETTINGS } from '@shared/data/types/provider' +import { eq } from 'drizzle-orm' + +const logger = loggerService.withContext('DataApi:ProviderService') + +/** + * Convert database row to Provider entity + */ +function rowToRuntimeProvider(row: UserProvider): Provider { + // Process API keys (strip actual key values for security) + // oxlint-disable-next-line no-unused-vars + const apiKeys = (row.apiKeys ?? []).map(({ key: _key, ...rest }) => rest) + + // Determine auth type + let authType: AuthType = 'api-key' + if (row.authConfig?.type) { + authType = row.authConfig.type as AuthType + } + + // Merge API features + const apiFeatures: RuntimeApiFeatures = { + ...DEFAULT_API_FEATURES, + ...row.apiFeatures + } + + // Merge settings + const settings: ProviderSettings = { + ...DEFAULT_PROVIDER_SETTINGS, + ...(row.providerSettings as Partial | null) + } + + return { + id: row.providerId, + presetProviderId: row.presetProviderId ?? undefined, + name: row.name, + baseUrls: row.baseUrls ?? undefined, + modelsApiUrls: row.modelsApiUrls ?? undefined, + defaultChatEndpoint: row.defaultChatEndpoint ?? undefined, + apiKeys, + authType, + apiFeatures, + settings, + websites: row.websites ?? undefined, + reasoningFormatType: row.reasoningFormatType ?? undefined, + isEnabled: row.isEnabled ?? true + } +} + +export class ProviderService { + private static instance: ProviderService + + private constructor() {} + + public static getInstance(): ProviderService { + if (!ProviderService.instance) { + ProviderService.instance = new ProviderService() + } + return ProviderService.instance + } + + /** + * List providers with optional filters + */ + async list(query: ListProvidersQuery): Promise { + const db = application.get('DbService').getDb() + + let rows: UserProvider[] + + if (query.enabled !== undefined) { + rows = await db + .select() + .from(userProviderTable) + .where(eq(userProviderTable.isEnabled, query.enabled)) + .orderBy(userProviderTable.sortOrder) + } else { + rows = await db.select().from(userProviderTable).orderBy(userProviderTable.sortOrder) + } + + return rows.map(rowToRuntimeProvider) + } + + /** + * Get a provider by its provider ID + */ + async getByProviderId(providerId: string): Promise { + const db = application.get('DbService').getDb() + + const [row] = await db.select().from(userProviderTable).where(eq(userProviderTable.providerId, providerId)).limit(1) + + if (!row) { + throw DataApiErrorFactory.notFound('Provider', providerId) + } + + return rowToRuntimeProvider(row) + } + + /** + * Create a new provider + */ + async create(dto: CreateProviderDto): Promise { + const db = application.get('DbService').getDb() + + const values: NewUserProvider = { + providerId: dto.providerId, + presetProviderId: dto.presetProviderId ?? null, + name: dto.name, + baseUrls: dto.baseUrls ?? null, + modelsApiUrls: dto.modelsApiUrls ?? null, + defaultChatEndpoint: dto.defaultChatEndpoint ?? null, + apiKeys: dto.apiKeys ?? [], + authConfig: dto.authConfig ?? null, + apiFeatures: dto.apiFeatures ?? null, + providerSettings: dto.providerSettings ?? null + } + + const [row] = await db.insert(userProviderTable).values(values).returning() + + logger.info('Created provider', { providerId: dto.providerId }) + + return rowToRuntimeProvider(row) + } + + /** + * Update an existing provider + */ + async update(providerId: string, dto: UpdateProviderDto): Promise { + const db = application.get('DbService').getDb() + + // Verify provider exists + await this.getByProviderId(providerId) + + // Build update object + const updates: Partial = {} + + if (dto.name !== undefined) updates.name = dto.name + if (dto.baseUrls !== undefined) updates.baseUrls = dto.baseUrls + if (dto.modelsApiUrls !== undefined) updates.modelsApiUrls = dto.modelsApiUrls + if (dto.defaultChatEndpoint !== undefined) updates.defaultChatEndpoint = dto.defaultChatEndpoint + if (dto.apiKeys !== undefined) updates.apiKeys = dto.apiKeys + if (dto.authConfig !== undefined) updates.authConfig = dto.authConfig + if (dto.apiFeatures !== undefined) updates.apiFeatures = dto.apiFeatures + if (dto.providerSettings !== undefined) updates.providerSettings = dto.providerSettings + if (dto.isEnabled !== undefined) updates.isEnabled = dto.isEnabled + if (dto.sortOrder !== undefined) updates.sortOrder = dto.sortOrder + + const [row] = await db + .update(userProviderTable) + .set(updates) + .where(eq(userProviderTable.providerId, providerId)) + .returning() + + logger.info('Updated provider', { providerId, changes: Object.keys(dto) }) + + return rowToRuntimeProvider(row) + } + + /** + * Batch upsert providers (used by CatalogService for preset providers) + * Inserts new providers, updates only preset fields on existing ones. + * Does NOT overwrite user-customized fields (apiKeys, isEnabled, sortOrder, authConfig). + */ + async batchUpsert(providers: NewUserProvider[]): Promise { + if (providers.length === 0) return + + const db = application.get('DbService').getDb() + + for (const provider of providers) { + await db + .insert(userProviderTable) + .values(provider) + .onConflictDoUpdate({ + target: [userProviderTable.providerId], + set: { + presetProviderId: provider.presetProviderId, + name: provider.name, + baseUrls: provider.baseUrls, + modelsApiUrls: provider.modelsApiUrls, + defaultChatEndpoint: provider.defaultChatEndpoint, + apiFeatures: provider.apiFeatures, + providerSettings: provider.providerSettings, + reasoningFormatType: provider.reasoningFormatType, + websites: provider.websites + } + }) + } + + logger.info('Batch upserted providers', { count: providers.length }) + } + + /** + * Get a rotated API key for a provider (round-robin across enabled keys). + * Returns empty string for providers that don't have keys. + */ + async getRotatedApiKey(providerId: string): Promise { + const db = application.get('DbService').getDb() + + const [row] = await db.select().from(userProviderTable).where(eq(userProviderTable.providerId, providerId)).limit(1) + + if (!row) { + throw DataApiErrorFactory.notFound('Provider', providerId) + } + + const enabledKeys = (row.apiKeys ?? []).filter((k) => k.isEnabled) + + if (enabledKeys.length === 0) { + return '' + } + + if (enabledKeys.length === 1) { + return enabledKeys[0].key + } + + // Round-robin using CacheService + const cache = application.get('CacheService') + const cacheKey = `provider:${providerId}:last_used_key_id` + const lastUsedKeyId = cache.get(cacheKey) + + if (!lastUsedKeyId) { + cache.set(cacheKey, enabledKeys[0].id) + return enabledKeys[0].key + } + + const currentIndex = enabledKeys.findIndex((k) => k.id === lastUsedKeyId) + const nextIndex = (currentIndex + 1) % enabledKeys.length + const nextKey = enabledKeys[nextIndex] + cache.set(cacheKey, nextKey.id) + + return nextKey.key + } + + /** + * Get all enabled API key values for a provider. + * Used by health check to test each key individually. + */ + async getEnabledApiKeys(providerId: string): Promise { + const db = application.get('DbService').getDb() + + const [row] = await db.select().from(userProviderTable).where(eq(userProviderTable.providerId, providerId)).limit(1) + + if (!row) { + throw DataApiErrorFactory.notFound('Provider', providerId) + } + + return (row.apiKeys ?? []).filter((k) => k.isEnabled) + } + + /** + * Get full auth config (includes sensitive credentials). + */ + async getAuthConfig(providerId: string): Promise { + const db = application.get('DbService').getDb() + const [row] = await db.select().from(userProviderTable).where(eq(userProviderTable.providerId, providerId)).limit(1) + + if (!row) { + throw DataApiErrorFactory.notFound('Provider', providerId) + } + + return row.authConfig ?? null + } + + /** + * Add an API key to a provider. Skips if the key value already exists. + * Returns the updated Provider. + */ + async addApiKey(providerId: string, key: string, label?: string): Promise { + const db = application.get('DbService').getDb() + + const [row] = await db.select().from(userProviderTable).where(eq(userProviderTable.providerId, providerId)).limit(1) + + if (!row) { + throw DataApiErrorFactory.notFound('Provider', providerId) + } + + const existingKeys = row.apiKeys ?? [] + + // Skip if key value already exists + if (existingKeys.some((k) => k.key === key)) { + logger.info('API key already exists, skipping', { providerId }) + return rowToRuntimeProvider(row) + } + + const newEntry = { + id: crypto.randomUUID(), + key, + label, + isEnabled: true, + createdAt: Date.now() + } + + const updatedKeys = [...existingKeys, newEntry] + + const [updated] = await db + .update(userProviderTable) + .set({ apiKeys: updatedKeys }) + .where(eq(userProviderTable.providerId, providerId)) + .returning() + + logger.info('Added API key to provider', { providerId }) + + return rowToRuntimeProvider(updated) + } + + /** + * Delete an API key by key ID and return updated provider. + */ + async deleteApiKey(providerId: string, keyId: string): Promise { + const db = application.get('DbService').getDb() + const [row] = await db.select().from(userProviderTable).where(eq(userProviderTable.providerId, providerId)).limit(1) + + if (!row) { + throw DataApiErrorFactory.notFound('Provider', providerId) + } + + const existingKeys = row.apiKeys ?? [] + const updatedKeys = existingKeys.filter((entry) => entry.id !== keyId) + + if (updatedKeys.length === existingKeys.length) { + throw DataApiErrorFactory.notFound('API key', keyId) + } + + const [updated] = await db + .update(userProviderTable) + .set({ apiKeys: updatedKeys }) + .where(eq(userProviderTable.providerId, providerId)) + .returning() + + logger.info('Deleted API key from provider', { providerId, keyId }) + + return rowToRuntimeProvider(updated) + } + + /** + * Delete a provider + */ + async delete(providerId: string): Promise { + const db = application.get('DbService').getDb() + + // Verify provider exists + await this.getByProviderId(providerId) + + await db.delete(userProviderTable).where(eq(userProviderTable.providerId, providerId)) + + logger.info('Deleted provider', { providerId }) + } +} + +export const providerService = ProviderService.getInstance() diff --git a/tsconfig.json b/tsconfig.json index 16d6bfb9f67..d234949d364 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,7 +15,9 @@ "useDefineForClassFields": true, "baseUrl": ".", "paths": { - "@renderer/*": ["src/renderer/src/*"] + "@renderer/*": ["src/renderer/src/*"], + "@shared/*": ["packages/shared/*"], + "@cherrystudio/provider-catalog": ["packages/provider-catalog/src/index.ts"] } } } diff --git a/tsconfig.node.json b/tsconfig.node.json index 7d2f5a94730..61541d35053 100644 --- a/tsconfig.node.json +++ b/tsconfig.node.json @@ -6,6 +6,7 @@ "incremental": true, "moduleResolution": "bundler", "paths": { + "@cherrystudio/provider-catalog": ["./packages/provider-catalog/src/index.ts"], "@data/*": ["./src/main/data/*"], "@logger": ["./src/main/services/LoggerService"], "@main/*": ["./src/main/*"], @@ -29,6 +30,7 @@ "src/renderer/src/services/traceApi.ts", "src/renderer/src/types/*", "packages/mcp-trace/**/*", + "packages/provider-catalog/**/*", "packages/shared/**/*", "tests/__mocks__/**/*" ] diff --git a/tsconfig.web.json b/tsconfig.web.json index 79778486c3b..38d1c1335d9 100644 --- a/tsconfig.web.json +++ b/tsconfig.web.json @@ -7,6 +7,7 @@ "jsx": "react-jsx", "moduleResolution": "bundler", "paths": { + "@cherrystudio/provider-catalog": ["./packages/provider-catalog/src/index.ts"], "@cherrystudio/ai-core": ["./packages/aiCore/src/index.ts"], "@cherrystudio/ai-core/*": ["./packages/aiCore/src/*"], "@cherrystudio/ai-core/built-in/plugins": ["./packages/aiCore/src/core/plugins/built-in/index.ts"], @@ -38,6 +39,7 @@ "packages/ai-sdk-provider/**/*", "packages/extension-table-plus/**/*", "packages/mcp-trace/**/*", + "packages/provider-catalog/**/*", "packages/shared/**/*", "packages/ui/**/*" ], From 8d7c980e10a7752bc5739b1b9eb346cbb0ba9c2a Mon Sep 17 00:00:00 2001 From: jidan745le <420511176@qq.com> Date: Sat, 4 Apr 2026 18:39:39 +0800 Subject: [PATCH 03/29] chore(changeset): add empty changeset for provider runtime PR Add an empty changeset file so CI changeset-check passes for this non-release provider/model runtime integration work. Signed-off-by: jidan745le <420511176@qq.com> Made-with: Cursor --- .changeset/provider-catalog-runtime-v2.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .changeset/provider-catalog-runtime-v2.md diff --git a/.changeset/provider-catalog-runtime-v2.md b/.changeset/provider-catalog-runtime-v2.md new file mode 100644 index 00000000000..b3b7f4209f9 --- /dev/null +++ b/.changeset/provider-catalog-runtime-v2.md @@ -0,0 +1,4 @@ +--- +--- + +Internal provider/model data API and migration integration for v2 runtime. From b9d1cda4fd359fed995a6ebf78ab5a890cdf5ac5 Mon Sep 17 00:00:00 2001 From: jidan745le <420511176@qq.com> Date: Sat, 4 Apr 2026 18:47:53 +0800 Subject: [PATCH 04/29] fix(lint): remove unnecessary type assertions in provider/model data path Drop redundant assertions flagged by oxlint and keep only required runtime-shape casts so CI test:lint passes. Signed-off-by: jidan745le <420511176@qq.com> Made-with: Cursor --- packages/shared/data/types/model.ts | 2 +- src/main/data/api/handlers/models.ts | 4 +--- src/main/data/api/handlers/providers.ts | 5 ++--- src/main/data/services/ModelService.ts | 13 ++++++------- src/main/data/services/ProviderService.ts | 2 +- 5 files changed, 11 insertions(+), 15 deletions(-) diff --git a/packages/shared/data/types/model.ts b/packages/shared/data/types/model.ts index 987e0446896..8525d291259 100644 --- a/packages/shared/data/types/model.ts +++ b/packages/shared/data/types/model.ts @@ -83,7 +83,7 @@ export function createUniqueModelId(providerId: string, modelId: string): Unique if (providerId.includes(UNIQUE_MODEL_ID_SEPARATOR)) { throw new Error(`providerId cannot contain "${UNIQUE_MODEL_ID_SEPARATOR}": ${providerId}`) } - return `${providerId}${UNIQUE_MODEL_ID_SEPARATOR}${modelId}` as UniqueModelId + return `${providerId}${UNIQUE_MODEL_ID_SEPARATOR}${modelId}` } /** diff --git a/src/main/data/api/handlers/models.ts b/src/main/data/api/handlers/models.ts index f9d49d1c731..1eb23129db4 100644 --- a/src/main/data/api/handlers/models.ts +++ b/src/main/data/api/handlers/models.ts @@ -9,7 +9,6 @@ import { modelService } from '@data/services/ModelService' import { catalogService } from '@data/services/ProviderCatalogService' import type { ApiHandler, ApiMethods } from '@shared/data/api/apiTypes' -import type { ListModelsQuery } from '@shared/data/api/schemas/models' import type { ModelSchemas } from '@shared/data/api/schemas/models' /** @@ -27,8 +26,7 @@ export const modelHandlers: { } = { '/models': { GET: async ({ query }) => { - const q = (query || {}) as ListModelsQuery - return await modelService.list(q) + return await modelService.list(query ?? {}) }, POST: async ({ body }) => { diff --git a/src/main/data/api/handlers/providers.ts b/src/main/data/api/handlers/providers.ts index cdb38d3078c..2ed9bf83173 100644 --- a/src/main/data/api/handlers/providers.ts +++ b/src/main/data/api/handlers/providers.ts @@ -13,7 +13,7 @@ import { userProviderInsertSchema } from '@data/db/schemas/userProvider' import { catalogService } from '@data/services/ProviderCatalogService' import { providerService } from '@data/services/ProviderService' import type { ApiHandler, ApiMethods } from '@shared/data/api/apiTypes' -import type { CreateProviderDto, ListProvidersQuery, UpdateProviderDto } from '@shared/data/api/schemas/providers' +import type { CreateProviderDto, UpdateProviderDto } from '@shared/data/api/schemas/providers' import type { ProviderSchemas } from '@shared/data/api/schemas/providers' /** @@ -31,8 +31,7 @@ export const providerHandlers: { } = { '/providers': { GET: async ({ query }) => { - const q = (query || {}) as ListProvidersQuery - return await providerService.list(q) + return await providerService.list(query ?? {}) }, POST: async ({ body }) => { diff --git a/src/main/data/services/ModelService.ts b/src/main/data/services/ModelService.ts index e3e7b57a1c1..1a4e7f3c355 100644 --- a/src/main/data/services/ModelService.ts +++ b/src/main/data/services/ModelService.ts @@ -7,14 +7,13 @@ * - Catalog import support */ -import type { EndpointType, Modality, ModelCapability } from '@cherrystudio/provider-catalog' import type { NewUserModel, UserModel } from '@data/db/schemas/userModel' import { isCatalogEnrichableField, userModelTable } from '@data/db/schemas/userModel' import { loggerService } from '@logger' import { application } from '@main/core/application' import { DataApiErrorFactory } from '@shared/data/api' import type { CreateModelDto, ListModelsQuery, UpdateModelDto } from '@shared/data/api/schemas/models' -import type { Model, RuntimeModelPricing, RuntimeParameterSupport, RuntimeReasoning } from '@shared/data/types/model' +import type { Model, RuntimeParameterSupport, RuntimeReasoning } from '@shared/data/types/model' import { createUniqueModelId } from '@shared/data/types/model' import { mergeModelConfig } from '@shared/data/utils/modelMerger' import { and, eq, inArray, type SQL } from 'drizzle-orm' @@ -37,16 +36,16 @@ function rowToRuntimeModel(row: UserModel): Model { name: row.name ?? row.modelId, description: row.description ?? undefined, group: row.group ?? undefined, - capabilities: (row.capabilities ?? []) as ModelCapability[], - inputModalities: (row.inputModalities ?? undefined) as Modality[] | undefined, - outputModalities: (row.outputModalities ?? undefined) as Modality[] | undefined, + capabilities: row.capabilities ?? [], + inputModalities: row.inputModalities ?? undefined, + outputModalities: row.outputModalities ?? undefined, contextWindow: row.contextWindow ?? undefined, maxOutputTokens: row.maxOutputTokens ?? undefined, - endpointTypes: (row.endpointTypes ?? undefined) as EndpointType[] | undefined, + endpointTypes: row.endpointTypes ?? undefined, supportsStreaming: row.supportsStreaming ?? true, reasoning: (row.reasoning ?? undefined) as RuntimeReasoning | undefined, parameterSupport: (row.parameters ?? undefined) as RuntimeParameterSupport | undefined, - pricing: (row.pricing ?? undefined) as RuntimeModelPricing | undefined, + pricing: row.pricing ?? undefined, isEnabled: row.isEnabled ?? true, isHidden: row.isHidden ?? false } diff --git a/src/main/data/services/ProviderService.ts b/src/main/data/services/ProviderService.ts index bea28609412..266cd4bbc2b 100644 --- a/src/main/data/services/ProviderService.ts +++ b/src/main/data/services/ProviderService.ts @@ -36,7 +36,7 @@ function rowToRuntimeProvider(row: UserProvider): Provider { // Determine auth type let authType: AuthType = 'api-key' if (row.authConfig?.type) { - authType = row.authConfig.type as AuthType + authType = row.authConfig.type } // Merge API features From 7732ab6f49b68f4d12551c1811cbf48e67ef8554 Mon Sep 17 00:00:00 2001 From: jidan745le <420511176@qq.com> Date: Sat, 4 Apr 2026 18:57:09 +0800 Subject: [PATCH 05/29] fix(migration): move provider-model schema migration to 0009 Remove duplicate 0007 migration and regenerate migration artifacts as 0009 to keep drizzle journal order consistent with existing 0007/0008 chain. Signed-off-by: jidan745le <420511176@qq.com> Made-with: Cursor --- ..._lumpy_reavers.sql => 0009_hard_naoko.sql} | 0 .../sqlite-drizzle/meta/0009_snapshot.json | 1748 +++++++++++++++++ migrations/sqlite-drizzle/meta/_journal.json | 7 + 3 files changed, 1755 insertions(+) rename migrations/sqlite-drizzle/{0007_lumpy_reavers.sql => 0009_hard_naoko.sql} (100%) create mode 100644 migrations/sqlite-drizzle/meta/0009_snapshot.json diff --git a/migrations/sqlite-drizzle/0007_lumpy_reavers.sql b/migrations/sqlite-drizzle/0009_hard_naoko.sql similarity index 100% rename from migrations/sqlite-drizzle/0007_lumpy_reavers.sql rename to migrations/sqlite-drizzle/0009_hard_naoko.sql diff --git a/migrations/sqlite-drizzle/meta/0009_snapshot.json b/migrations/sqlite-drizzle/meta/0009_snapshot.json new file mode 100644 index 00000000000..89f8ae6a4c7 --- /dev/null +++ b/migrations/sqlite-drizzle/meta/0009_snapshot.json @@ -0,0 +1,1748 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "44fae2c4-d17d-4766-a211-c34a794dc9d0", + "prevId": "4105c4f2-d9df-4048-acab-0049e5a95941", + "tables": { + "app_state": { + "name": "app_state", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "group": { + "name": "group", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "group_entity_sort_idx": { + "name": "group_entity_sort_idx", + "columns": ["entity_type", "sort_order"], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "knowledge_base": { + "name": "knowledge_base", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "dimensions": { + "name": "dimensions", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "embedding_model_id": { + "name": "embedding_model_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "rerank_model_id": { + "name": "rerank_model_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "file_processor_id": { + "name": "file_processor_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "chunk_size": { + "name": "chunk_size", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "chunk_overlap": { + "name": "chunk_overlap", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "threshold": { + "name": "threshold", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "document_count": { + "name": "document_count", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "search_mode": { + "name": "search_mode", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "hybrid_alpha": { + "name": "hybrid_alpha", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": { + "knowledge_base_search_mode_check": { + "name": "knowledge_base_search_mode_check", + "value": "\"knowledge_base\".\"search_mode\" IN ('default', 'bm25', 'hybrid') OR \"knowledge_base\".\"search_mode\" IS NULL" + } + } + }, + "knowledge_item": { + "name": "knowledge_item", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "base_id": { + "name": "base_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "group_id": { + "name": "group_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "data": { + "name": "data", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'idle'" + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "knowledge_item_base_type_created_idx": { + "name": "knowledge_item_base_type_created_idx", + "columns": ["base_id", "type", "created_at"], + "isUnique": false + }, + "knowledge_item_base_group_created_idx": { + "name": "knowledge_item_base_group_created_idx", + "columns": ["base_id", "group_id", "created_at"], + "isUnique": false + }, + "knowledge_item_baseId_id_unique": { + "name": "knowledge_item_baseId_id_unique", + "columns": ["base_id", "id"], + "isUnique": true + } + }, + "foreignKeys": { + "knowledge_item_base_id_knowledge_base_id_fk": { + "name": "knowledge_item_base_id_knowledge_base_id_fk", + "tableFrom": "knowledge_item", + "tableTo": "knowledge_base", + "columnsFrom": ["base_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "knowledge_item_base_id_group_id_knowledge_item_base_id_id_fk": { + "name": "knowledge_item_base_id_group_id_knowledge_item_base_id_id_fk", + "tableFrom": "knowledge_item", + "tableTo": "knowledge_item", + "columnsFrom": ["base_id", "group_id"], + "columnsTo": ["base_id", "id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": { + "knowledge_item_type_check": { + "name": "knowledge_item_type_check", + "value": "\"knowledge_item\".\"type\" IN ('file', 'url', 'note', 'sitemap', 'directory')" + }, + "knowledge_item_status_check": { + "name": "knowledge_item_status_check", + "value": "\"knowledge_item\".\"status\" IN ('idle', 'pending', 'ocr', 'read', 'embed', 'completed', 'failed')" + } + } + }, + "mcp_server": { + "name": "mcp_server", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "base_url": { + "name": "base_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "registry_url": { + "name": "registry_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "args": { + "name": "args", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "env": { + "name": "env", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "headers": { + "name": "headers", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "provider_url": { + "name": "provider_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "logo_url": { + "name": "logo_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "long_running": { + "name": "long_running", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "timeout": { + "name": "timeout", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "dxt_version": { + "name": "dxt_version", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "dxt_path": { + "name": "dxt_path", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "reference": { + "name": "reference", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "search_key": { + "name": "search_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "config_sample": { + "name": "config_sample", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "disabled_tools": { + "name": "disabled_tools", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "disabled_auto_approve_tools": { + "name": "disabled_auto_approve_tools", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "should_config": { + "name": "should_config", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 0 + }, + "is_active": { + "name": "is_active", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "install_source": { + "name": "install_source", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_trusted": { + "name": "is_trusted", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "trusted_at": { + "name": "trusted_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "installed_at": { + "name": "installed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "mcp_server_name_idx": { + "name": "mcp_server_name_idx", + "columns": ["name"], + "isUnique": false + }, + "mcp_server_is_active_idx": { + "name": "mcp_server_is_active_idx", + "columns": ["is_active"], + "isUnique": false + }, + "mcp_server_sort_order_idx": { + "name": "mcp_server_sort_order_idx", + "columns": ["sort_order"], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": { + "mcp_server_type_check": { + "name": "mcp_server_type_check", + "value": "\"mcp_server\".\"type\" IS NULL OR \"mcp_server\".\"type\" IN ('stdio', 'sse', 'streamableHttp', 'inMemory')" + }, + "mcp_server_install_source_check": { + "name": "mcp_server_install_source_check", + "value": "\"mcp_server\".\"install_source\" IS NULL OR \"mcp_server\".\"install_source\" IN ('builtin', 'manual', 'protocol', 'unknown')" + } + } + }, + "message": { + "name": "message", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "parent_id": { + "name": "parent_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "topic_id": { + "name": "topic_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "data": { + "name": "data", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "searchable_text": { + "name": "searchable_text", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "siblings_group_id": { + "name": "siblings_group_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 0 + }, + "assistant_id": { + "name": "assistant_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "assistant_meta": { + "name": "assistant_meta", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "model_id": { + "name": "model_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "model_meta": { + "name": "model_meta", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "trace_id": { + "name": "trace_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "stats": { + "name": "stats", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "message_parent_id_idx": { + "name": "message_parent_id_idx", + "columns": ["parent_id"], + "isUnique": false + }, + "message_topic_created_idx": { + "name": "message_topic_created_idx", + "columns": ["topic_id", "created_at"], + "isUnique": false + }, + "message_trace_id_idx": { + "name": "message_trace_id_idx", + "columns": ["trace_id"], + "isUnique": false + } + }, + "foreignKeys": { + "message_topic_id_topic_id_fk": { + "name": "message_topic_id_topic_id_fk", + "tableFrom": "message", + "tableTo": "topic", + "columnsFrom": ["topic_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "message_parent_id_message_id_fk": { + "name": "message_parent_id_message_id_fk", + "tableFrom": "message", + "tableTo": "message", + "columnsFrom": ["parent_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": { + "message_role_check": { + "name": "message_role_check", + "value": "\"message\".\"role\" IN ('user', 'assistant', 'system')" + }, + "message_status_check": { + "name": "message_status_check", + "value": "\"message\".\"status\" IN ('pending', 'success', 'error', 'paused')" + } + } + }, + "miniapp": { + "name": "miniapp", + "columns": { + "app_id": { + "name": "app_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "logo": { + "name": "logo", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'custom'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'enabled'" + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 0 + }, + "bordered": { + "name": "bordered", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": true + }, + "background": { + "name": "background", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "supported_regions": { + "name": "supported_regions", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "configuration": { + "name": "configuration", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name_key": { + "name": "name_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "miniapp_status_sort_idx": { + "name": "miniapp_status_sort_idx", + "columns": ["status", "sort_order"], + "isUnique": false + }, + "miniapp_type_idx": { + "name": "miniapp_type_idx", + "columns": ["type"], + "isUnique": false + }, + "miniapp_status_type_idx": { + "name": "miniapp_status_type_idx", + "columns": ["status", "type"], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": { + "miniapp_status_check": { + "name": "miniapp_status_check", + "value": "\"miniapp\".\"status\" IN ('enabled', 'disabled', 'pinned')" + }, + "miniapp_type_check": { + "name": "miniapp_type_check", + "value": "\"miniapp\".\"type\" IN ('default', 'custom')" + } + } + }, + "preference": { + "name": "preference", + "columns": { + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'default'" + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "preference_scope_key_pk": { + "columns": ["scope", "key"], + "name": "preference_scope_key_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "entity_tag": { + "name": "entity_tag", + "columns": { + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "entity_id": { + "name": "entity_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tag_id": { + "name": "tag_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "entity_tag_tag_id_idx": { + "name": "entity_tag_tag_id_idx", + "columns": ["tag_id"], + "isUnique": false + } + }, + "foreignKeys": { + "entity_tag_tag_id_tag_id_fk": { + "name": "entity_tag_tag_id_tag_id_fk", + "tableFrom": "entity_tag", + "tableTo": "tag", + "columnsFrom": ["tag_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "entity_tag_entity_type_entity_id_tag_id_pk": { + "columns": ["entity_type", "entity_id", "tag_id"], + "name": "entity_tag_entity_type_entity_id_tag_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tag": { + "name": "tag", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "tag_name_unique": { + "name": "tag_name_unique", + "columns": ["name"], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "topic": { + "name": "topic", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_name_manually_edited": { + "name": "is_name_manually_edited", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "assistant_id": { + "name": "assistant_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "assistant_meta": { + "name": "assistant_meta", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "prompt": { + "name": "prompt", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "active_node_id": { + "name": "active_node_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "group_id": { + "name": "group_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 0 + }, + "is_pinned": { + "name": "is_pinned", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "pinned_order": { + "name": "pinned_order", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "topic_group_updated_idx": { + "name": "topic_group_updated_idx", + "columns": ["group_id", "updated_at"], + "isUnique": false + }, + "topic_group_sort_idx": { + "name": "topic_group_sort_idx", + "columns": ["group_id", "sort_order"], + "isUnique": false + }, + "topic_updated_at_idx": { + "name": "topic_updated_at_idx", + "columns": ["updated_at"], + "isUnique": false + }, + "topic_is_pinned_idx": { + "name": "topic_is_pinned_idx", + "columns": ["is_pinned", "pinned_order"], + "isUnique": false + }, + "topic_assistant_id_idx": { + "name": "topic_assistant_id_idx", + "columns": ["assistant_id"], + "isUnique": false + } + }, + "foreignKeys": { + "topic_group_id_group_id_fk": { + "name": "topic_group_id_group_id_fk", + "tableFrom": "topic", + "tableTo": "group", + "columnsFrom": ["group_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "translate_history": { + "name": "translate_history", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "source_text": { + "name": "source_text", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "target_text": { + "name": "target_text", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "source_language": { + "name": "source_language", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "target_language": { + "name": "target_language", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "star": { + "name": "star", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "translate_history_created_at_idx": { + "name": "translate_history_created_at_idx", + "columns": ["created_at"], + "isUnique": false + }, + "translate_history_star_created_at_idx": { + "name": "translate_history_star_created_at_idx", + "columns": ["star", "created_at"], + "isUnique": false + } + }, + "foreignKeys": { + "translate_history_source_language_translate_language_lang_code_fk": { + "name": "translate_history_source_language_translate_language_lang_code_fk", + "tableFrom": "translate_history", + "tableTo": "translate_language", + "columnsFrom": ["source_language"], + "columnsTo": ["lang_code"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "translate_history_target_language_translate_language_lang_code_fk": { + "name": "translate_history_target_language_translate_language_lang_code_fk", + "tableFrom": "translate_history", + "tableTo": "translate_language", + "columnsFrom": ["target_language"], + "columnsTo": ["lang_code"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "translate_language": { + "name": "translate_language", + "columns": { + "lang_code": { + "name": "lang_code", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "emoji": { + "name": "emoji", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user_model": { + "name": "user_model", + "columns": { + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "model_id": { + "name": "model_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "preset_model_id": { + "name": "preset_model_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "group": { + "name": "group", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "capabilities": { + "name": "capabilities", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "input_modalities": { + "name": "input_modalities", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "output_modalities": { + "name": "output_modalities", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "endpoint_types": { + "name": "endpoint_types", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "custom_endpoint_url": { + "name": "custom_endpoint_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "context_window": { + "name": "context_window", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "max_output_tokens": { + "name": "max_output_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "supports_streaming": { + "name": "supports_streaming", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "reasoning": { + "name": "reasoning", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "parameters": { + "name": "parameters", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "pricing": { + "name": "pricing", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_enabled": { + "name": "is_enabled", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": true + }, + "is_hidden": { + "name": "is_hidden", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "is_deprecated": { + "name": "is_deprecated", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 0 + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_overrides": { + "name": "user_overrides", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "user_model_preset_idx": { + "name": "user_model_preset_idx", + "columns": ["preset_model_id"], + "isUnique": false + }, + "user_model_provider_enabled_idx": { + "name": "user_model_provider_enabled_idx", + "columns": ["provider_id", "is_enabled"], + "isUnique": false + }, + "user_model_provider_sort_idx": { + "name": "user_model_provider_sort_idx", + "columns": ["provider_id", "sort_order"], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "user_model_provider_id_model_id_pk": { + "columns": ["provider_id", "model_id"], + "name": "user_model_provider_id_model_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user_provider": { + "name": "user_provider", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "preset_provider_id": { + "name": "preset_provider_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "base_urls": { + "name": "base_urls", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "models_api_urls": { + "name": "models_api_urls", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "default_chat_endpoint": { + "name": "default_chat_endpoint", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "api_keys": { + "name": "api_keys", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'[]'" + }, + "auth_config": { + "name": "auth_config", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "api_features": { + "name": "api_features", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "provider_settings": { + "name": "provider_settings", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "reasoning_format_type": { + "name": "reasoning_format_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "websites": { + "name": "websites", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_enabled": { + "name": "is_enabled", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": true + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "user_provider_providerId_unique": { + "name": "user_provider_providerId_unique", + "columns": ["provider_id"], + "isUnique": true + }, + "user_provider_preset_idx": { + "name": "user_provider_preset_idx", + "columns": ["preset_provider_id"], + "isUnique": false + }, + "user_provider_enabled_sort_idx": { + "name": "user_provider_enabled_sort_idx", + "columns": ["is_enabled", "sort_order"], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} diff --git a/migrations/sqlite-drizzle/meta/_journal.json b/migrations/sqlite-drizzle/meta/_journal.json index 2a168c87f06..cfe9a3c0e03 100644 --- a/migrations/sqlite-drizzle/meta/_journal.json +++ b/migrations/sqlite-drizzle/meta/_journal.json @@ -63,6 +63,13 @@ "when": 1775137534101, "tag": "0008_wild_ultron", "breakpoints": true + }, + { + "idx": 9, + "version": "6", + "when": 1775300033193, + "tag": "0009_hard_naoko", + "breakpoints": true } ], "version": "7" From cabc987195eb615ae722e81ca7d51352582b8171 Mon Sep 17 00:00:00 2001 From: jidan745le <420511176@qq.com> Date: Sat, 4 Apr 2026 20:59:01 +0800 Subject: [PATCH 06/29] feat:udpate --- migrations/sqlite-drizzle/meta/_journal.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/migrations/sqlite-drizzle/meta/_journal.json b/migrations/sqlite-drizzle/meta/_journal.json index cfe9a3c0e03..0a9995157b9 100644 --- a/migrations/sqlite-drizzle/meta/_journal.json +++ b/migrations/sqlite-drizzle/meta/_journal.json @@ -53,7 +53,7 @@ { "idx": 7, "version": "6", - "when": 1774622546141, + "when": 1774697808333, "tag": "0007_steep_hedge_knight", "breakpoints": true }, From 47fc6f7649a692e0530a927ef463c9390cd600b5 Mon Sep 17 00:00:00 2001 From: jidan745le <420511176@qq.com> Date: Sat, 4 Apr 2026 23:31:38 +0800 Subject: [PATCH 07/29] fix(startup): call initializeAllPresetProviders on app startup The catalog service initialization was missing from the startup sequence, causing preset provider/model data (capabilities, context windows, pricing, baseUrls) to never sync from protobuf catalog files into SQLite on each launch. Signed-off-by: jidan745le <420511176@qq.com> --- src/main/index.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/index.ts b/src/main/index.ts index c417a885e47..01ef6b6f89e 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -255,6 +255,12 @@ const startApp = async () => { // (DbService, PreferenceService, CacheService, DataApiService are now ready) await bootstrapPromise + // Sync preset catalog data on every startup so that provider websites/baseUrls + // and model capabilities/modalities/contextWindow/pricing stay up-to-date + // when the catalog protobuf files are updated between app versions. + const { catalogService } = await import('@data/services/ProviderCatalogService') + await catalogService.initializeAllPresetProviders() + // Record current version for tracking // A preparation for v2 data refactoring versionService.recordCurrentVersion() From d866f37e937af1b1b191bd0d27a0b2a53c51dc82 Mon Sep 17 00:00:00 2001 From: jidan745le <420511176@qq.com> Date: Sun, 5 Apr 2026 03:40:30 -0800 Subject: [PATCH 08/29] refactor: simplify provider schema reasoning config Signed-off-by: jidan745le <420511176@qq.com> --- migrations/sqlite-drizzle/0009_hard_naoko.sql | 8 +- .../sqlite-drizzle/meta/0009_snapshot.json | 119 +++++++-------- migrations/sqlite-drizzle/meta/_journal.json | 2 +- packages/shared/data/api/schemas/providers.ts | 11 +- packages/shared/data/types/provider.ts | 21 ++- packages/shared/data/utils/modelMerger.ts | 126 +++++++++++++--- src/main/data/api/handlers/models.ts | 2 +- src/main/data/db/schemas/userProvider.ts | 17 ++- .../mappings/ProviderModelMappings.ts | 25 +++- src/main/data/services/ModelService.ts | 15 +- .../data/services/ProviderCatalogService.ts | 139 ++++++++++++++---- src/main/data/services/ProviderService.ts | 6 +- 12 files changed, 352 insertions(+), 139 deletions(-) diff --git a/migrations/sqlite-drizzle/0009_hard_naoko.sql b/migrations/sqlite-drizzle/0009_hard_naoko.sql index cbc9be07109..11d4f6d503c 100644 --- a/migrations/sqlite-drizzle/0009_hard_naoko.sql +++ b/migrations/sqlite-drizzle/0009_hard_naoko.sql @@ -31,8 +31,7 @@ CREATE INDEX `user_model_preset_idx` ON `user_model` (`preset_model_id`);--> sta CREATE INDEX `user_model_provider_enabled_idx` ON `user_model` (`provider_id`,`is_enabled`);--> statement-breakpoint CREATE INDEX `user_model_provider_sort_idx` ON `user_model` (`provider_id`,`sort_order`);--> statement-breakpoint CREATE TABLE `user_provider` ( - `id` text PRIMARY KEY NOT NULL, - `provider_id` text NOT NULL, + `provider_id` text PRIMARY KEY NOT NULL, `preset_provider_id` text, `name` text NOT NULL, `base_urls` text, @@ -42,7 +41,7 @@ CREATE TABLE `user_provider` ( `auth_config` text, `api_features` text, `provider_settings` text, - `reasoning_format_type` text, + `reasoning_format_types` text, `websites` text, `is_enabled` integer DEFAULT true, `sort_order` integer DEFAULT 0, @@ -50,6 +49,5 @@ CREATE TABLE `user_provider` ( `updated_at` integer ); --> statement-breakpoint -CREATE UNIQUE INDEX `user_provider_providerId_unique` ON `user_provider` (`provider_id`);--> statement-breakpoint CREATE INDEX `user_provider_preset_idx` ON `user_provider` (`preset_provider_id`);--> statement-breakpoint -CREATE INDEX `user_provider_enabled_sort_idx` ON `user_provider` (`is_enabled`,`sort_order`); \ No newline at end of file +CREATE INDEX `user_provider_enabled_sort_idx` ON `user_provider` (`is_enabled`,`sort_order`); diff --git a/migrations/sqlite-drizzle/meta/0009_snapshot.json b/migrations/sqlite-drizzle/meta/0009_snapshot.json index 89f8ae6a4c7..62dba627978 100644 --- a/migrations/sqlite-drizzle/meta/0009_snapshot.json +++ b/migrations/sqlite-drizzle/meta/0009_snapshot.json @@ -1,8 +1,6 @@ { "version": "6", "dialect": "sqlite", - "id": "44fae2c4-d17d-4766-a211-c34a794dc9d0", - "prevId": "4105c4f2-d9df-4048-acab-0049e5a95941", "tables": { "app_state": { "name": "app_state", @@ -969,28 +967,28 @@ "uniqueConstraints": {}, "checkConstraints": {} }, - "entity_tag": { - "name": "entity_tag", + "tag": { + "name": "tag", "columns": { - "entity_type": { - "name": "entity_type", + "id": { + "name": "id", "type": "text", - "primaryKey": false, + "primaryKey": true, "notNull": true, "autoincrement": false }, - "entity_id": { - "name": "entity_id", + "name": { + "name": "name", "type": "text", "primaryKey": false, "notNull": true, "autoincrement": false }, - "tag_id": { - "name": "tag_id", + "color": { + "name": "color", "type": "text", "primaryKey": false, - "notNull": true, + "notNull": false, "autoincrement": false }, "created_at": { @@ -1009,54 +1007,39 @@ } }, "indexes": { - "entity_tag_tag_id_idx": { - "name": "entity_tag_tag_id_idx", - "columns": ["tag_id"], - "isUnique": false - } - }, - "foreignKeys": { - "entity_tag_tag_id_tag_id_fk": { - "name": "entity_tag_tag_id_tag_id_fk", - "tableFrom": "entity_tag", - "tableTo": "tag", - "columnsFrom": ["tag_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "entity_tag_entity_type_entity_id_tag_id_pk": { - "columns": ["entity_type", "entity_id", "tag_id"], - "name": "entity_tag_entity_type_entity_id_tag_id_pk" + "tag_name_unique": { + "name": "tag_name_unique", + "columns": ["name"], + "isUnique": true } }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, "uniqueConstraints": {}, "checkConstraints": {} }, - "tag": { - "name": "tag", + "entity_tag": { + "name": "entity_tag", "columns": { - "id": { - "name": "id", + "entity_type": { + "name": "entity_type", "type": "text", - "primaryKey": true, + "primaryKey": false, "notNull": true, "autoincrement": false }, - "name": { - "name": "name", + "entity_id": { + "name": "entity_id", "type": "text", "primaryKey": false, "notNull": true, "autoincrement": false }, - "color": { - "name": "color", + "tag_id": { + "name": "tag_id", "type": "text", "primaryKey": false, - "notNull": false, + "notNull": true, "autoincrement": false }, "created_at": { @@ -1075,14 +1058,29 @@ } }, "indexes": { - "tag_name_unique": { - "name": "tag_name_unique", - "columns": ["name"], - "isUnique": true + "entity_tag_tag_id_idx": { + "name": "entity_tag_tag_id_idx", + "columns": ["tag_id"], + "isUnique": false + } + }, + "foreignKeys": { + "entity_tag_tag_id_tag_id_fk": { + "name": "entity_tag_tag_id_tag_id_fk", + "tableFrom": "entity_tag", + "tableTo": "tag", + "columnsFrom": ["tag_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "entity_tag_entity_type_entity_id_tag_id_pk": { + "columns": ["entity_type", "entity_id", "tag_id"], + "name": "entity_tag_entity_type_entity_id_tag_id_pk" } }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, "uniqueConstraints": {}, "checkConstraints": {} }, @@ -1589,17 +1587,10 @@ "user_provider": { "name": "user_provider", "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, "provider_id": { "name": "provider_id", "type": "text", - "primaryKey": false, + "primaryKey": true, "notNull": true, "autoincrement": false }, @@ -1667,8 +1658,8 @@ "notNull": false, "autoincrement": false }, - "reasoning_format_type": { - "name": "reasoning_format_type", + "reasoning_format_types": { + "name": "reasoning_format_types", "type": "text", "primaryKey": false, "notNull": false, @@ -1713,11 +1704,6 @@ } }, "indexes": { - "user_provider_providerId_unique": { - "name": "user_provider_providerId_unique", - "columns": ["provider_id"], - "isUnique": true - }, "user_provider_preset_idx": { "name": "user_provider_preset_idx", "columns": ["preset_provider_id"], @@ -1738,11 +1724,12 @@ "views": {}, "enums": {}, "_meta": { - "schemas": {}, "tables": {}, "columns": {} }, "internal": { "indexes": {} - } + }, + "id": "34f2606c-1455-47e9-831a-0756e742177b", + "prevId": "4105c4f2-d9df-4048-acab-0049e5a95941" } diff --git a/migrations/sqlite-drizzle/meta/_journal.json b/migrations/sqlite-drizzle/meta/_journal.json index 0a9995157b9..f1f506ba6af 100644 --- a/migrations/sqlite-drizzle/meta/_journal.json +++ b/migrations/sqlite-drizzle/meta/_journal.json @@ -67,7 +67,7 @@ { "idx": 9, "version": "6", - "when": 1775300033193, + "when": 1775384722093, "tag": "0009_hard_naoko", "breakpoints": true } diff --git a/packages/shared/data/api/schemas/providers.ts b/packages/shared/data/api/schemas/providers.ts index 635d7d30dc5..e8d4f793631 100644 --- a/packages/shared/data/api/schemas/providers.ts +++ b/packages/shared/data/api/schemas/providers.ts @@ -7,7 +7,14 @@ */ import type { EndpointType, Model } from '../../types/model' -import type { ApiFeatures, ApiKeyEntry, AuthConfig, Provider, ProviderSettings } from '../../types/provider' +import type { + ApiFeatures, + ApiKeyEntry, + AuthConfig, + Provider, + ProviderSettings, + ReasoningFormatType +} from '../../types/provider' export interface ListProvidersQuery { /** Filter by enabled status */ @@ -24,6 +31,8 @@ interface ProviderMutableFields { modelsApiUrls?: Record /** Default text generation endpoint (numeric EndpointType enum value) */ defaultChatEndpoint?: EndpointType + /** Reasoning format mapping by endpoint type */ + reasoningFormatTypes?: Partial> /** API keys */ apiKeys?: ApiKeyEntry[] /** Authentication configuration */ diff --git a/packages/shared/data/types/provider.ts b/packages/shared/data/types/provider.ts index d373fbf02b4..7144c1b8587 100644 --- a/packages/shared/data/types/provider.ts +++ b/packages/shared/data/types/provider.ts @@ -187,6 +187,21 @@ export const ProviderSettingsSchema = z.object({ export type ProviderSettings = z.infer +export const REASONING_FORMAT_TYPES = [ + 'openai-chat', + 'openai-responses', + 'anthropic', + 'gemini', + 'openrouter', + 'enable-thinking', + 'thinking-type', + 'dashscope', + 'self-hosted' +] as const + +export const ReasoningFormatTypeSchema = z.enum(REASONING_FORMAT_TYPES) +export type ReasoningFormatType = z.infer + export const ProviderSchema = z.object({ /** Provider ID */ id: z.string(), @@ -219,8 +234,10 @@ export const ProviderSchema = z.object({ settings: ProviderSettingsSchema, /** Website links (official, apiKey, docs, models) */ websites: ProviderWebsitesSchema.optional(), - /** How this provider's API expects reasoning parameters (e.g. 'openai-chat', 'anthropic', 'enable-thinking') */ - reasoningFormatType: z.string().optional(), + /** How this provider's API expects reasoning parameters per endpoint type */ + reasoningFormatTypes: z.record(EndpointTypeSchema, ReasoningFormatTypeSchema).optional() as z.ZodOptional< + z.ZodType>> + >, /** Whether this provider is enabled */ isEnabled: z.boolean() }) diff --git a/packages/shared/data/utils/modelMerger.ts b/packages/shared/data/utils/modelMerger.ts index c95e251c3f3..df8ea678871 100644 --- a/packages/shared/data/utils/modelMerger.ts +++ b/packages/shared/data/utils/modelMerger.ts @@ -18,13 +18,14 @@ import * as z from 'zod' import type { Model, RuntimeModelPricing, RuntimeReasoning } from '../types/model' import { createUniqueModelId } from '../types/model' -import type { Provider, ProviderSettings, RuntimeApiFeatures } from '../types/provider' +import type { Provider, ProviderSettings, ReasoningFormatType, RuntimeApiFeatures } from '../types/provider' import { ApiFeaturesSchema, ApiKeyEntrySchema, DEFAULT_API_FEATURES, DEFAULT_PROVIDER_SETTINGS, - ProviderSettingsSchema + ProviderSettingsSchema, + ReasoningFormatTypeSchema } from '../types/provider' export type { ProtoModelConfig as CatalogModel, ProtoProviderModelOverride as CatalogProviderModelOverride } @@ -73,6 +74,7 @@ const UserProviderRowSchema = z.object({ name: z.string(), baseUrls: z.record(z.string(), z.string()).nullish(), defaultChatEndpoint: z.nativeEnum(EndpointType).nullish(), + reasoningFormatTypes: z.record(z.string(), ReasoningFormatTypeSchema).nullish(), apiKeys: z.array(ApiKeyEntrySchema.pick({ id: true, key: true, label: true, isEnabled: true })).nullish(), authConfig: z.object({ type: z.string() }).catchall(z.unknown()).nullish(), apiFeatures: ApiFeaturesSchema.nullish(), @@ -124,7 +126,8 @@ export function mergeModelConfig( catalogOverride: ProtoProviderModelOverride | null, presetModel: ProtoModelConfig | null, providerId: string, - reasoningFormatType?: string + reasoningFormatTypes?: Partial> | null, + defaultChatEndpoint?: EndpointType ): Model { // Case 1: Fully custom user model (no preset association) if (userModel && !userModel.presetModelId) { @@ -172,11 +175,6 @@ export function mergeModelConfig( let pricing: RuntimeModelPricing | undefined let replaceWith: string | undefined - // Extract reasoning config from proto ReasoningSupport + provider's reasoning format type - if (presetModel.reasoning) { - reasoning = extractRuntimeReasoning(presetModel.reasoning, reasoningFormatType) - } - // Extract pricing if (presetModel.pricing) { pricing = { @@ -217,14 +215,6 @@ export function mergeModelConfig( if (catalogOverride.limits?.maxInputTokens != null) { maxInputTokens = catalogOverride.limits.maxInputTokens } - if (catalogOverride.reasoning) { - const overrideReasoning = extractRuntimeReasoning(catalogOverride.reasoning, reasoningFormatType) - reasoning = { - ...overrideReasoning, - thinkingTokenLimits: overrideReasoning.thinkingTokenLimits ?? reasoning?.thinkingTokenLimits, - interleaved: overrideReasoning.interleaved ?? reasoning?.interleaved - } - } if (catalogOverride.endpointTypes.length) { endpointTypes = [...catalogOverride.endpointTypes] } @@ -265,6 +255,25 @@ export function mergeModelConfig( if (userModel.maxOutputTokens != null) { maxOutputTokens = userModel.maxOutputTokens } + } + + const reasoningFormatType = resolveReasoningFormatType(endpointTypes, defaultChatEndpoint, reasoningFormatTypes) + + // Extract reasoning config from proto ReasoningSupport + provider's reasoning format type + if (presetModel.reasoning) { + reasoning = extractRuntimeReasoning(presetModel.reasoning, reasoningFormatType) + } + + if (catalogOverride?.reasoning) { + const overrideReasoning = extractRuntimeReasoning(catalogOverride.reasoning, reasoningFormatType) + reasoning = { + ...overrideReasoning, + thinkingTokenLimits: overrideReasoning.thinkingTokenLimits ?? reasoning?.thinkingTokenLimits, + interleaved: overrideReasoning.interleaved ?? reasoning?.interleaved + } + } + + if (userModel) { if (userModel.reasoning) { reasoning = userModel.reasoning as RuntimeReasoning } @@ -331,6 +340,16 @@ export function mergeProviderConfig( ...userProvider?.baseUrls } + const presetReasoningFormat = extractReasoningFormatType(presetProvider?.reasoningFormat) + const presetReasoningFormatTypes = buildReasoningFormatTypes( + presetProvider?.defaultChatEndpoint, + presetReasoningFormat + ) + const reasoningFormatTypes = { + ...presetReasoningFormatTypes, + ...userProvider?.reasoningFormatTypes + } + // Merge API features (catalog now uses the same field names) const apiFeatures: RuntimeApiFeatures = { ...DEFAULT_API_FEATURES, @@ -369,7 +388,7 @@ export function mergeProviderConfig( authType, apiFeatures, settings, - reasoningFormatType: extractReasoningFormatType(presetProvider?.reasoningFormat), + reasoningFormatTypes: Object.keys(reasoningFormatTypes).length > 0 ? reasoningFormatTypes : undefined, isEnabled: userProvider?.isEnabled ?? true } } @@ -379,7 +398,7 @@ export function mergeProviderConfig( // ═══════════════════════════════════════════════════════════════════════════════ /** Map proto ProviderReasoningFormat.format.case to runtime reasoning type string */ -const REASONING_FORMAT_CASE_TO_TYPE: Record = { +const REASONING_FORMAT_CASE_TO_TYPE: Record = { openaiChat: 'openai-chat', openaiResponses: 'openai-responses', anthropic: 'anthropic', @@ -391,8 +410,18 @@ const REASONING_FORMAT_CASE_TO_TYPE: Record = { selfHosted: 'self-hosted' } +const CHAT_REASONING_ENDPOINT_PRIORITY: EndpointType[] = [ + EndpointType.OPENAI_RESPONSES, + EndpointType.OPENAI_CHAT_COMPLETIONS, + EndpointType.ANTHROPIC_MESSAGES, + EndpointType.GOOGLE_GENERATE_CONTENT, + EndpointType.OLLAMA_CHAT, + EndpointType.OLLAMA_GENERATE, + EndpointType.OPENAI_TEXT_COMPLETIONS +] + /** Default effort levels per reasoning format type (when not specified in catalog) */ -const DEFAULT_EFFORTS: Record = { +const DEFAULT_EFFORTS: Partial> = { 'openai-chat': [ ReasoningEffort.NONE, ReasoningEffort.MINIMAL, @@ -413,10 +442,65 @@ const DEFAULT_EFFORTS: Record = { 'thinking-type': [ReasoningEffort.NONE, ReasoningEffort.AUTO] } +function isChatReasoningEndpointType(endpointType: EndpointType): boolean { + return CHAT_REASONING_ENDPOINT_PRIORITY.includes(endpointType) +} + +function buildReasoningFormatTypes( + defaultChatEndpoint: EndpointType | undefined, + reasoningFormatType: ReasoningFormatType | undefined +): Partial> { + if (defaultChatEndpoint === undefined || !reasoningFormatType) { + return {} + } + + return { + [defaultChatEndpoint]: reasoningFormatType + } +} + +function resolveReasoningEndpointType( + endpointTypes: EndpointType[] | undefined, + defaultChatEndpoint: EndpointType | undefined +): EndpointType | undefined { + const candidates = (endpointTypes ?? []).filter(isChatReasoningEndpointType) + + if (candidates.length === 1) { + return candidates[0] + } + + if (defaultChatEndpoint !== undefined && isChatReasoningEndpointType(defaultChatEndpoint)) { + if (candidates.length === 0 || candidates.includes(defaultChatEndpoint)) { + return defaultChatEndpoint + } + } + + for (const endpointType of CHAT_REASONING_ENDPOINT_PRIORITY) { + if (candidates.includes(endpointType)) { + return endpointType + } + } + + return undefined +} + +function resolveReasoningFormatType( + endpointTypes: EndpointType[] | undefined, + defaultChatEndpoint: EndpointType | undefined, + reasoningFormatTypes: Partial> | null | undefined +): ReasoningFormatType | undefined { + const endpointType = resolveReasoningEndpointType(endpointTypes, defaultChatEndpoint) + if (endpointType === undefined || !reasoningFormatTypes) { + return undefined + } + + return reasoningFormatTypes[endpointType] +} + /** * Extract runtime reasoning type string from proto ProviderReasoningFormat */ -function extractReasoningFormatType(format: ProtoProviderReasoningFormat | undefined): string | undefined { +function extractReasoningFormatType(format: ProtoProviderReasoningFormat | undefined): ReasoningFormatType | undefined { if (!format?.format.case) return undefined return REASONING_FORMAT_CASE_TO_TYPE[format.format.case] } @@ -427,7 +511,7 @@ function extractReasoningFormatType(format: ProtoProviderReasoningFormat | undef */ function extractRuntimeReasoning( reasoning: ProtoReasoningSupport, - reasoningFormatType: string | undefined + reasoningFormatType: ReasoningFormatType | undefined ): RuntimeReasoning { const type = reasoningFormatType ?? '' diff --git a/src/main/data/api/handlers/models.ts b/src/main/data/api/handlers/models.ts index 1eb23129db4..7a74fb75da2 100644 --- a/src/main/data/api/handlers/models.ts +++ b/src/main/data/api/handlers/models.ts @@ -36,7 +36,7 @@ export const modelHandlers: { '/models/resolve': { POST: async ({ body }) => { - return catalogService.resolveModels(body.providerId, body.models) + return await catalogService.resolveModels(body.providerId, body.models) } }, diff --git a/src/main/data/db/schemas/userProvider.ts b/src/main/data/db/schemas/userProvider.ts index 3539a8ba825..8506fbbb5d2 100644 --- a/src/main/data/db/schemas/userProvider.ts +++ b/src/main/data/db/schemas/userProvider.ts @@ -20,7 +20,9 @@ import { type ProviderSettings, ProviderSettingsSchema, type ProviderWebsites, - ProviderWebsitesSchema + ProviderWebsitesSchema, + type ReasoningFormatType, + ReasoningFormatTypeSchema } from '@shared/data/types/provider' import { index, integer, sqliteTable, text } from 'drizzle-orm/sqlite-core' import { createSchemaFactory } from 'drizzle-zod' @@ -30,14 +32,12 @@ const { createInsertSchema, createSelectSchema } = createSchemaFactory({ zodInst import type { EndpointType } from '@shared/data/types/model' -import { createUpdateTimestamps, uuidPrimaryKey } from './_columnHelpers' +import { createUpdateTimestamps } from './_columnHelpers' export const userProviderTable = sqliteTable( 'user_provider', { - id: uuidPrimaryKey(), - - providerId: text().notNull().unique(), + providerId: text().primaryKey(), /** Associated preset provider ID (optional) * Links to catalog provider for inherited API format and defaults @@ -66,8 +66,10 @@ export const userProviderTable = sqliteTable( /** Provider-specific settings as JSON */ providerSettings: text({ mode: 'json' }).$type(), - /** How this provider's API expects reasoning parameters (e.g. 'openai-chat', 'anthropic', 'enable-thinking') */ - reasoningFormatType: text(), + /** How this provider's API expects reasoning parameters for each endpoint type */ + reasoningFormatTypes: text('reasoning_format_types', { mode: 'json' }).$type< + Partial> + >(), /** Website links (official, apiKey, docs, models) */ websites: text({ mode: 'json' }).$type(), @@ -93,6 +95,7 @@ export type NewUserProvider = typeof userProviderTable.$inferInsert const jsonColumnOverrides = { baseUrls: () => z.record(z.string(), z.string()).nullable(), modelsApiUrls: () => z.record(z.string(), z.string()).nullable(), + reasoningFormatTypes: () => z.record(z.string(), ReasoningFormatTypeSchema).nullable(), apiKeys: () => z.array(ApiKeyEntrySchema).nullable(), authConfig: () => AuthConfigSchema.nullable(), apiFeatures: () => ApiFeaturesSchema.nullable(), diff --git a/src/main/data/migration/v2/migrators/mappings/ProviderModelMappings.ts b/src/main/data/migration/v2/migrators/mappings/ProviderModelMappings.ts index 4a2f4bc40a5..be18f2f55cc 100644 --- a/src/main/data/migration/v2/migrators/mappings/ProviderModelMappings.ts +++ b/src/main/data/migration/v2/migrators/mappings/ProviderModelMappings.ts @@ -12,7 +12,13 @@ import { import type { NewUserModel } from '@data/db/schemas/userModel' import type { NewUserProvider } from '@data/db/schemas/userProvider' import type { RuntimeModelPricing } from '@shared/data/types/model' -import type { ApiFeatures, ApiKeyEntry, AuthConfig, ProviderSettings } from '@shared/data/types/provider' +import type { + ApiFeatures, + ApiKeyEntry, + AuthConfig, + ProviderSettings, + ReasoningFormatType +} from '@shared/data/types/provider' import type { Model as LegacyModel, ModelType, Provider as LegacyProvider } from '@types' import { v4 as uuidv4 } from 'uuid' @@ -65,7 +71,7 @@ const ENDPOINT_MAP: Partial> = { ollama: ENDPOINT_TYPE.OLLAMA_CHAT } -const REASONING_FORMAT_MAP: Partial> = { +const REASONING_FORMAT_MAP: Partial> = { openai: 'openai-chat', 'openai-response': 'openai-responses', anthropic: 'anthropic', @@ -155,11 +161,11 @@ export function transformProvider( name: legacy.name, baseUrls: buildBaseUrls(legacy, endpointType), defaultChatEndpoint: endpointType ?? null, + reasoningFormatTypes: buildReasoningFormatTypes(endpointType, REASONING_FORMAT_MAP[legacy.type]), apiKeys: buildApiKeys(legacy.apiKey), authConfig: buildAuthConfig(legacy, settings), apiFeatures: buildApiFeatures(legacy), providerSettings: buildProviderSettings(legacy, settings), - reasoningFormatType: REASONING_FORMAT_MAP[legacy.type] ?? null, isEnabled: legacy.enabled ?? true, sortOrder } @@ -179,6 +185,19 @@ function buildBaseUrls(legacy: LegacyProvider, endpointType: EndpointType | unde return Object.keys(urls).length > 0 ? urls : null } +function buildReasoningFormatTypes( + defaultChatEndpoint: EndpointType | undefined, + reasoningFormatType: ReasoningFormatType | undefined +): NewUserProvider['reasoningFormatTypes'] { + if (defaultChatEndpoint === undefined || !reasoningFormatType) { + return null + } + + return { + [defaultChatEndpoint]: reasoningFormatType + } +} + function buildApiKeys(apiKey: string): ApiKeyEntry[] { if (!apiKey) { return [] diff --git a/src/main/data/services/ModelService.ts b/src/main/data/services/ModelService.ts index 1a4e7f3c355..6f11f3e6743 100644 --- a/src/main/data/services/ModelService.ts +++ b/src/main/data/services/ModelService.ts @@ -125,10 +125,8 @@ export class ModelService { const db = application.get('DbService').getDb() // Look up catalog data for auto-enrichment - const { presetModel, catalogOverride, reasoningFormatType } = catalogService.lookupModel( - dto.providerId, - dto.modelId - ) + const { presetModel, catalogOverride, reasoningFormatTypes, defaultChatEndpoint } = + await catalogService.lookupModel(dto.providerId, dto.modelId) let values: NewUserModel @@ -151,7 +149,14 @@ export class ModelService { reasoning: dto.reasoning ?? null } - const merged = mergeModelConfig(userRow, catalogOverride, presetModel, dto.providerId, reasoningFormatType) + const merged = mergeModelConfig( + userRow, + catalogOverride, + presetModel, + dto.providerId, + reasoningFormatTypes, + defaultChatEndpoint + ) values = { providerId: dto.providerId, diff --git a/src/main/data/services/ProviderCatalogService.ts b/src/main/data/services/ProviderCatalogService.ts index 86c28225138..77d4c88a20f 100644 --- a/src/main/data/services/ProviderCatalogService.ts +++ b/src/main/data/services/ProviderCatalogService.ts @@ -21,12 +21,14 @@ import { import type { NewUserModel } from '@data/db/schemas/userModel' import { userModelTable } from '@data/db/schemas/userModel' import type { NewUserProvider } from '@data/db/schemas/userProvider' +import { userProviderTable } from '@data/db/schemas/userProvider' import { loggerService } from '@logger' import { isDev } from '@main/constant' import { application } from '@main/core/application' import type { Model } from '@shared/data/types/model' +import type { ReasoningFormatType } from '@shared/data/types/provider' import { mergeModelConfig } from '@shared/data/utils/modelMerger' -import { isNotNull } from 'drizzle-orm' +import { eq, isNotNull } from 'drizzle-orm' import { modelService } from './ModelService' import { providerService } from './ProviderService' @@ -34,7 +36,7 @@ import { providerService } from './ProviderService' const logger = loggerService.withContext('DataApi:CatalogService') /** Map proto ProviderReasoningFormat oneof case to runtime type string */ -const CASE_TO_TYPE: Record = { +const CASE_TO_TYPE: Record = { openaiChat: 'openai-chat', openaiResponses: 'openai-responses', anthropic: 'anthropic', @@ -46,6 +48,29 @@ const CASE_TO_TYPE: Record = { selfHosted: 'self-hosted' } +function buildReasoningFormatTypes( + defaultChatEndpoint: EndpointType | undefined, + reasoningFormatType: ReasoningFormatType | undefined +): Partial> | undefined { + if (defaultChatEndpoint === undefined || !reasoningFormatType) { + return undefined + } + + return { + [defaultChatEndpoint]: reasoningFormatType + } +} + +function getSingleReasoningFormatType( + reasoningFormatTypes: Partial> | undefined +): ReasoningFormatType | undefined { + if (!reasoningFormatTypes) { + return undefined + } + + return Object.values(reasoningFormatTypes)[0] +} + export class CatalogService { private static instance: CatalogService @@ -129,15 +154,54 @@ export class CatalogService { } /** - * Get the reasoning format type string for a provider from catalog data. - * Returns the proto oneof case mapped to a runtime type string. + * Get provider reasoning config from catalog data. */ - private getReasoningFormatType(providerId: string): string | undefined { + private getCatalogReasoningConfig(providerId: string): { + defaultChatEndpoint?: EndpointType + reasoningFormatTypes?: Partial> + } { const providers = this.loadCatalogProviders() const provider = providers.find((p) => p.id === providerId) const formatCase = provider?.reasoningFormat?.format.case - if (!formatCase) return undefined - return CASE_TO_TYPE[formatCase] + const reasoningFormatType = formatCase ? CASE_TO_TYPE[formatCase] : undefined + + return { + defaultChatEndpoint: provider?.defaultChatEndpoint, + reasoningFormatTypes: buildReasoningFormatTypes(provider?.defaultChatEndpoint, reasoningFormatType) + } + } + + /** + * Get provider reasoning config, preferring persisted provider data when available. + */ + private async getEffectiveReasoningConfig(providerId: string): Promise<{ + defaultChatEndpoint?: EndpointType + reasoningFormatTypes?: Partial> + }> { + const db = application.get('DbService').getDb() + const catalogConfig = this.getCatalogReasoningConfig(providerId) + const [provider] = await db + .select({ + defaultChatEndpoint: userProviderTable.defaultChatEndpoint, + reasoningFormatTypes: userProviderTable.reasoningFormatTypes + }) + .from(userProviderTable) + .where(eq(userProviderTable.providerId, providerId)) + .limit(1) + + if (provider) { + const defaultChatEndpoint = provider.defaultChatEndpoint ?? catalogConfig.defaultChatEndpoint + const reasoningFormatTypes = + provider.reasoningFormatTypes ?? + buildReasoningFormatTypes(defaultChatEndpoint, getSingleReasoningFormatType(catalogConfig.reasoningFormatTypes)) + + return { + defaultChatEndpoint, + reasoningFormatTypes + } + } + + return catalogConfig } /** @@ -150,7 +214,7 @@ export class CatalogService { async initializeProvider(providerId: string): Promise { const catalogModels = this.loadCatalogModels() const providerModels = this.loadProviderModels() - const reasoningFormatType = this.getReasoningFormatType(providerId) + const { defaultChatEndpoint, reasoningFormatTypes } = await this.getEffectiveReasoningConfig(providerId) // Find all overrides for this provider const overrides = providerModels.filter((pm) => pm.providerId === providerId) @@ -182,7 +246,7 @@ export class CatalogService { } // Merge: no user override (null), catalog override, preset model - const merged = mergeModelConfig(null, override, baseModel, providerId, reasoningFormatType) + const merged = mergeModelConfig(null, override, baseModel, providerId, reasoningFormatTypes, defaultChatEndpoint) mergedModels.push(merged) // Convert to DB row format — capabilities/modalities are now numeric arrays @@ -224,7 +288,7 @@ export class CatalogService { getCatalogModelsByProvider(providerId: string): Model[] { const catalogModels = this.loadCatalogModels() const providerModels = this.loadProviderModels() - const reasoningFormatType = this.getReasoningFormatType(providerId) + const { defaultChatEndpoint, reasoningFormatTypes } = this.getCatalogReasoningConfig(providerId) const overrides = providerModels.filter((pm) => pm.providerId === providerId) if (overrides.length === 0) { @@ -242,7 +306,9 @@ export class CatalogService { if (!baseModel) { continue } - mergedModels.push(mergeModelConfig(null, override, baseModel, providerId, reasoningFormatType)) + mergedModels.push( + mergeModelConfig(null, override, baseModel, providerId, reasoningFormatTypes, defaultChatEndpoint) + ) } return mergedModels @@ -310,7 +376,8 @@ export class CatalogService { // Extract reasoning format type from proto oneof const formatCase = p.reasoningFormat?.format.case - const reasoningFormatType = formatCase ? (CASE_TO_TYPE[formatCase] ?? null) : null + const reasoningFormatType = formatCase ? CASE_TO_TYPE[formatCase] : undefined + const reasoningFormatTypes = buildReasoningFormatTypes(p.defaultChatEndpoint, reasoningFormatType) return { providerId: p.id, @@ -319,8 +386,8 @@ export class CatalogService { baseUrls: Object.keys(baseUrls).length > 0 ? baseUrls : null, modelsApiUrls: Object.keys(modelsApiUrls ?? {}).length > 0 ? modelsApiUrls : null, defaultChatEndpoint: p.defaultChatEndpoint ?? null, + reasoningFormatTypes: reasoningFormatTypes ?? null, apiFeatures, - reasoningFormatType, websites } }) @@ -401,6 +468,14 @@ export class CatalogService { const updateRows: NewUserModel[] = [] let skippedCount = 0 + const providerRows = await db + .select({ + providerId: userProviderTable.providerId, + defaultChatEndpoint: userProviderTable.defaultChatEndpoint, + reasoningFormatTypes: userProviderTable.reasoningFormatTypes + }) + .from(userProviderTable) + const providerConfigMap = new Map(providerRows.map((row) => [row.providerId, row])) for (const row of userModels) { const presetModelId = row.presetModelId! @@ -413,7 +488,10 @@ export class CatalogService { const providerOverrides = overridesByProvider.get(row.providerId) const catalogOverride = providerOverrides?.get(presetModelId) ?? null - const reasoningFormatType = this.getReasoningFormatType(row.providerId) + const providerConfig = providerConfigMap.get(row.providerId) + const catalogReasoningConfig = this.getCatalogReasoningConfig(row.providerId) + const defaultChatEndpoint = providerConfig?.defaultChatEndpoint ?? catalogReasoningConfig.defaultChatEndpoint + const reasoningFormatTypes = providerConfig?.reasoningFormatTypes ?? catalogReasoningConfig.reasoningFormatTypes // Merge catalog data with user data const merged = mergeModelConfig( @@ -438,7 +516,8 @@ export class CatalogService { catalogOverride, presetModel, row.providerId, - reasoningFormatType + reasoningFormatTypes, + defaultChatEndpoint ) updateRows.push({ @@ -480,22 +559,23 @@ export class CatalogService { * Returns the preset base model and provider-level override (if any). * Used by ModelService.create to auto-enrich models at save time. */ - lookupModel( + async lookupModel( providerId: string, modelId: string - ): { + ): Promise<{ presetModel: ProtoModelConfig | null catalogOverride: ProtoProviderModelOverride | null - reasoningFormatType: string | undefined - } { + defaultChatEndpoint?: EndpointType + reasoningFormatTypes?: Partial> + }> { const catalogModels = this.loadCatalogModels() const providerModels = this.loadProviderModels() const presetModel = catalogModels.find((m) => m.id === modelId) ?? null const catalogOverride = providerModels.find((pm) => pm.providerId === providerId && pm.modelId === modelId) ?? null - const reasoningFormatType = this.getReasoningFormatType(providerId) + const reasoningConfig = await this.getEffectiveReasoningConfig(providerId) - return { presetModel, catalogOverride, reasoningFormatType } + return { presetModel, catalogOverride, ...reasoningConfig } } /** @@ -508,7 +588,7 @@ export class CatalogService { * Used by the renderer to display enriched models in ManageModelsPopup * before the user adds them. */ - resolveModels( + async resolveModels( providerId: string, rawModels: Array<{ modelId: string @@ -517,10 +597,10 @@ export class CatalogService { description?: string endpointTypes?: number[] }> - ): Model[] { + ): Promise { const catalogModels = this.loadCatalogModels() const providerModels = this.loadProviderModels() - const reasoningFormatType = this.getReasoningFormatType(providerId) + const { defaultChatEndpoint, reasoningFormatTypes } = await this.getEffectiveReasoningConfig(providerId) // Build lookup maps const modelMap = new Map() @@ -558,7 +638,16 @@ export class CatalogService { try { if (presetModel) { // Catalog match found — merge with preset data - results.push(mergeModelConfig(userRow, catalogOverride, presetModel, providerId, reasoningFormatType)) + results.push( + mergeModelConfig( + userRow, + catalogOverride, + presetModel, + providerId, + reasoningFormatTypes, + defaultChatEndpoint + ) + ) } else { // No catalog match — return as custom model (no presetModelId) results.push(mergeModelConfig({ ...userRow, presetModelId: null }, null, null, providerId)) diff --git a/src/main/data/services/ProviderService.ts b/src/main/data/services/ProviderService.ts index 266cd4bbc2b..4efe26b2127 100644 --- a/src/main/data/services/ProviderService.ts +++ b/src/main/data/services/ProviderService.ts @@ -63,7 +63,7 @@ function rowToRuntimeProvider(row: UserProvider): Provider { apiFeatures, settings, websites: row.websites ?? undefined, - reasoningFormatType: row.reasoningFormatType ?? undefined, + reasoningFormatTypes: row.reasoningFormatTypes ?? undefined, isEnabled: row.isEnabled ?? true } } @@ -129,6 +129,7 @@ export class ProviderService { baseUrls: dto.baseUrls ?? null, modelsApiUrls: dto.modelsApiUrls ?? null, defaultChatEndpoint: dto.defaultChatEndpoint ?? null, + reasoningFormatTypes: dto.reasoningFormatTypes ?? null, apiKeys: dto.apiKeys ?? [], authConfig: dto.authConfig ?? null, apiFeatures: dto.apiFeatures ?? null, @@ -158,6 +159,7 @@ export class ProviderService { if (dto.baseUrls !== undefined) updates.baseUrls = dto.baseUrls if (dto.modelsApiUrls !== undefined) updates.modelsApiUrls = dto.modelsApiUrls if (dto.defaultChatEndpoint !== undefined) updates.defaultChatEndpoint = dto.defaultChatEndpoint + if (dto.reasoningFormatTypes !== undefined) updates.reasoningFormatTypes = dto.reasoningFormatTypes if (dto.apiKeys !== undefined) updates.apiKeys = dto.apiKeys if (dto.authConfig !== undefined) updates.authConfig = dto.authConfig if (dto.apiFeatures !== undefined) updates.apiFeatures = dto.apiFeatures @@ -198,9 +200,9 @@ export class ProviderService { baseUrls: provider.baseUrls, modelsApiUrls: provider.modelsApiUrls, defaultChatEndpoint: provider.defaultChatEndpoint, + reasoningFormatTypes: provider.reasoningFormatTypes, apiFeatures: provider.apiFeatures, providerSettings: provider.providerSettings, - reasoningFormatType: provider.reasoningFormatType, websites: provider.websites } }) From 068b883af45e7c62035cabd0e47e763fbc3f4447 Mon Sep 17 00:00:00 2001 From: jidan745le <420511176@qq.com> Date: Sun, 5 Apr 2026 22:16:19 -0800 Subject: [PATCH 09/29] feat(renderer): add v2 domain hooks, utility functions and shim Signed-off-by: jidan745le <420511176@qq.com> --- .../src/data/__tests__/accessors.v2.test.ts | 129 ++++++ src/renderer/src/data/accessors.v2.ts | 40 ++ .../__tests__/useDefaultModel.v2.test.ts | 240 +++++++++++ .../data/hooks/__tests__/useModels.test.ts | 184 +++++++++ .../data/hooks/__tests__/useProviders.test.ts | 388 ++++++++++++++++++ .../src/data/hooks/useDefaultModel.v2.ts | 60 +++ src/renderer/src/data/hooks/useModels.ts | 57 +++ src/renderer/src/data/hooks/useProviders.ts | 161 ++++++++ .../src/utils/__tests__/provider.v2.test.ts | 300 ++++++++++++++ src/renderer/src/utils/provider.v2.ts | 152 +++++++ src/renderer/src/utils/v1ProviderShim.ts | 109 +++++ 11 files changed, 1820 insertions(+) create mode 100644 src/renderer/src/data/__tests__/accessors.v2.test.ts create mode 100644 src/renderer/src/data/accessors.v2.ts create mode 100644 src/renderer/src/data/hooks/__tests__/useDefaultModel.v2.test.ts create mode 100644 src/renderer/src/data/hooks/__tests__/useModels.test.ts create mode 100644 src/renderer/src/data/hooks/__tests__/useProviders.test.ts create mode 100644 src/renderer/src/data/hooks/useDefaultModel.v2.ts create mode 100644 src/renderer/src/data/hooks/useModels.ts create mode 100644 src/renderer/src/data/hooks/useProviders.ts create mode 100644 src/renderer/src/utils/__tests__/provider.v2.test.ts create mode 100644 src/renderer/src/utils/provider.v2.ts create mode 100644 src/renderer/src/utils/v1ProviderShim.ts diff --git a/src/renderer/src/data/__tests__/accessors.v2.test.ts b/src/renderer/src/data/__tests__/accessors.v2.test.ts new file mode 100644 index 00000000000..ae56b7d18dd --- /dev/null +++ b/src/renderer/src/data/__tests__/accessors.v2.test.ts @@ -0,0 +1,129 @@ +import { DataApiError, ErrorCode } from '@shared/data/api/apiErrors' +import { mockDataApiService } from '@test-mocks/renderer/DataApiService' +import { beforeEach, describe, expect, it, vi } from 'vitest' + +import { + getEnabledProvidersAsync, + getModelAsync, + getModelByUniqueIdAsync, + getProviderByIdAsync, + getProvidersAsync +} from '../accessors.v2' + +const mockProviders = [ + { id: 'openai', name: 'OpenAI', isEnabled: true }, + { id: 'anthropic', name: 'Anthropic', isEnabled: true } +] + +const mockModel = { + id: 'openai::gpt-4o', + providerId: 'openai', + name: 'GPT-4o', + capabilities: [], + supportsStreaming: true, + isEnabled: true, + isHidden: false +} + +describe('accessors.v2', () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + describe('getProvidersAsync', () => { + it('should call dataApiService.get with /providers', async () => { + mockDataApiService.get.mockResolvedValueOnce(mockProviders) + + const result = await getProvidersAsync() + + expect(mockDataApiService.get).toHaveBeenCalledWith('/providers') + expect(result).toEqual(mockProviders) + }) + }) + + describe('getEnabledProvidersAsync', () => { + it('should call dataApiService.get with enabled query', async () => { + mockDataApiService.get.mockResolvedValueOnce(mockProviders) + + const result = await getEnabledProvidersAsync() + + expect(mockDataApiService.get).toHaveBeenCalledWith('/providers', { query: { enabled: true } }) + expect(result).toEqual(mockProviders) + }) + }) + + describe('getProviderByIdAsync', () => { + it('should return the provider when found', async () => { + mockDataApiService.get.mockResolvedValueOnce(mockProviders[0]) + + const result = await getProviderByIdAsync('openai') + + expect(mockDataApiService.get).toHaveBeenCalledWith('/providers/openai') + expect(result).toEqual(mockProviders[0]) + }) + + it('should return undefined on NOT_FOUND error', async () => { + mockDataApiService.get.mockRejectedValueOnce(new DataApiError(ErrorCode.NOT_FOUND, 'Provider not found', 404)) + + const result = await getProviderByIdAsync('nonexistent') + expect(result).toBeUndefined() + }) + + it('should rethrow non-404 errors', async () => { + const serverError = new DataApiError(ErrorCode.INTERNAL_SERVER_ERROR, 'Server error', 500) + mockDataApiService.get.mockRejectedValueOnce(serverError) + + await expect(getProviderByIdAsync('openai')).rejects.toThrow(serverError) + }) + + it('should rethrow non-DataApiError errors', async () => { + const networkError = new Error('Network error') + mockDataApiService.get.mockRejectedValueOnce(networkError) + + await expect(getProviderByIdAsync('openai')).rejects.toThrow(networkError) + }) + }) + + describe('getModelAsync', () => { + it('should return the model when found', async () => { + mockDataApiService.get.mockResolvedValueOnce(mockModel) + + const result = await getModelAsync('openai', 'gpt-4o') + + expect(mockDataApiService.get).toHaveBeenCalledWith('/models/openai/gpt-4o') + expect(result).toEqual(mockModel) + }) + + it('should return undefined on NOT_FOUND error', async () => { + mockDataApiService.get.mockRejectedValueOnce(new DataApiError(ErrorCode.NOT_FOUND, 'Model not found', 404)) + + const result = await getModelAsync('openai', 'nonexistent') + expect(result).toBeUndefined() + }) + + it('should rethrow non-404 errors', async () => { + const serverError = new DataApiError(ErrorCode.INTERNAL_SERVER_ERROR, 'Server error', 500) + mockDataApiService.get.mockRejectedValueOnce(serverError) + + await expect(getModelAsync('openai', 'gpt-4o')).rejects.toThrow(serverError) + }) + }) + + describe('getModelByUniqueIdAsync', () => { + it('should parse UniqueModelId and call getModelAsync', async () => { + mockDataApiService.get.mockResolvedValueOnce(mockModel) + + const result = await getModelByUniqueIdAsync('openai::gpt-4o' as any) + + expect(mockDataApiService.get).toHaveBeenCalledWith('/models/openai/gpt-4o') + expect(result).toEqual(mockModel) + }) + + it('should return undefined when model not found', async () => { + mockDataApiService.get.mockRejectedValueOnce(new DataApiError(ErrorCode.NOT_FOUND, 'Model not found', 404)) + + const result = await getModelByUniqueIdAsync('openai::nonexistent' as any) + expect(result).toBeUndefined() + }) + }) +}) diff --git a/src/renderer/src/data/accessors.v2.ts b/src/renderer/src/data/accessors.v2.ts new file mode 100644 index 00000000000..777358d5e94 --- /dev/null +++ b/src/renderer/src/data/accessors.v2.ts @@ -0,0 +1,40 @@ +import { dataApiService } from '@data/DataApiService' +import { DataApiError, ErrorCode } from '@shared/data/api/apiErrors' +import type { Model } from '@shared/data/types/model' +import { parseUniqueModelId, type UniqueModelId } from '@shared/data/types/model' +import type { Provider } from '@shared/data/types/provider' + +function isNotFoundError(e: unknown): boolean { + return e instanceof DataApiError && e.code === ErrorCode.NOT_FOUND +} + +export async function getProvidersAsync(): Promise { + return dataApiService.get('/providers' as const) +} + +export async function getEnabledProvidersAsync(): Promise { + return dataApiService.get('/providers' as const, { query: { enabled: true } }) +} + +export async function getProviderByIdAsync(id: string): Promise { + try { + return await dataApiService.get(`/providers/${id}` as const) + } catch (e) { + if (isNotFoundError(e)) return undefined + throw e + } +} + +export async function getModelAsync(providerId: string, modelId: string): Promise { + try { + return await dataApiService.get(`/models/${providerId}/${modelId}` as any) + } catch (e) { + if (isNotFoundError(e)) return undefined + throw e + } +} + +export async function getModelByUniqueIdAsync(uniqueModelId: UniqueModelId): Promise { + const { providerId, modelId } = parseUniqueModelId(uniqueModelId) + return getModelAsync(providerId, modelId) +} diff --git a/src/renderer/src/data/hooks/__tests__/useDefaultModel.v2.test.ts b/src/renderer/src/data/hooks/__tests__/useDefaultModel.v2.test.ts new file mode 100644 index 00000000000..6ee066c9418 --- /dev/null +++ b/src/renderer/src/data/hooks/__tests__/useDefaultModel.v2.test.ts @@ -0,0 +1,240 @@ +import { mockUseQuery } from '@test-mocks/renderer/useDataApi' +import { mockUsePreference } from '@test-mocks/renderer/usePreference' +import { act, renderHook } from '@testing-library/react' +import { beforeEach, describe, expect, it, vi } from 'vitest' + +import { useDefaultModel } from '../useDefaultModel.v2' + +// Mock model data +const mockDefaultModel = { + id: 'openai::gpt-4o', + providerId: 'openai', + name: 'GPT-4o', + capabilities: [], + supportsStreaming: true, + isEnabled: true, + isHidden: false +} +const mockQuickModel = { + id: 'openai::gpt-4o-mini', + providerId: 'openai', + name: 'GPT-4o Mini', + capabilities: [], + supportsStreaming: true, + isEnabled: true, + isHidden: false +} +const mockTranslateModel = { + id: 'anthropic::claude-3-haiku', + providerId: 'anthropic', + name: 'Claude 3 Haiku', + capabilities: [], + supportsStreaming: true, + isEnabled: true, + isHidden: false +} + +describe('useDefaultModel.v2', () => { + beforeEach(() => { + vi.clearAllMocks() + + // Default: usePreference returns empty strings (no model set) + mockUsePreference.mockImplementation((key: string) => { + const setFn = vi.fn().mockResolvedValue(undefined) + switch (key) { + case 'model.default_id': + return ['', setFn] + case 'model.quick_id': + return ['', setFn] + case 'model.translate_id': + return ['', setFn] + default: + return ['', setFn] + } + }) + + // Default: useQuery returns undefined data when enabled is false + mockUseQuery.mockImplementation((_path: string, options?: { enabled?: boolean }) => ({ + data: options?.enabled === false ? undefined : undefined, + isLoading: false, + isRefreshing: false, + error: undefined, + refetch: vi.fn(), + mutate: vi.fn() + })) + }) + + it('should return empty state when no models are configured', () => { + const { result } = renderHook(() => useDefaultModel()) + + expect(result.current.defaultModel).toBeUndefined() + expect(result.current.defaultModelId).toBe('') + expect(result.current.quickModel).toBeUndefined() + expect(result.current.quickModelId).toBe('') + expect(result.current.translateModel).toBeUndefined() + expect(result.current.translateModelId).toBe('') + expect(result.current.isLoading).toBe(false) + }) + + it('should resolve models when preference IDs are set', () => { + mockUsePreference.mockImplementation((key: string) => { + const setFn = vi.fn().mockResolvedValue(undefined) + switch (key) { + case 'model.default_id': + return ['openai::gpt-4o', setFn] + case 'model.quick_id': + return ['openai::gpt-4o-mini', setFn] + case 'model.translate_id': + return ['anthropic::claude-3-haiku', setFn] + default: + return ['', setFn] + } + }) + + mockUseQuery.mockImplementation((path: string, options?: { enabled?: boolean }) => { + if (options?.enabled === false) { + return { + data: undefined, + isLoading: false, + isRefreshing: false, + error: undefined, + refetch: vi.fn(), + mutate: vi.fn() + } + } + if (path.includes('openai') && path.includes('gpt-4o-mini')) { + return { + data: mockQuickModel, + isLoading: false, + isRefreshing: false, + error: undefined, + refetch: vi.fn(), + mutate: vi.fn() + } + } + if (path.includes('openai') && path.includes('gpt-4o')) { + return { + data: mockDefaultModel, + isLoading: false, + isRefreshing: false, + error: undefined, + refetch: vi.fn(), + mutate: vi.fn() + } + } + if (path.includes('anthropic') && path.includes('claude-3-haiku')) { + return { + data: mockTranslateModel, + isLoading: false, + isRefreshing: false, + error: undefined, + refetch: vi.fn(), + mutate: vi.fn() + } + } + return { + data: undefined, + isLoading: false, + isRefreshing: false, + error: undefined, + refetch: vi.fn(), + mutate: vi.fn() + } + }) + + const { result } = renderHook(() => useDefaultModel()) + + expect(result.current.defaultModel).toEqual(mockDefaultModel) + expect(result.current.defaultModelId).toBe('openai::gpt-4o') + expect(result.current.quickModel).toEqual(mockQuickModel) + expect(result.current.quickModelId).toBe('openai::gpt-4o-mini') + expect(result.current.translateModel).toEqual(mockTranslateModel) + expect(result.current.translateModelId).toBe('anthropic::claude-3-haiku') + }) + + it('should show isLoading when any model is being resolved', () => { + mockUsePreference.mockImplementation((key: string) => { + const setFn = vi.fn().mockResolvedValue(undefined) + if (key === 'model.default_id') return ['openai::gpt-4o', setFn] + return ['', setFn] + }) + + mockUseQuery.mockImplementation((_path: string, options?: { enabled?: boolean }) => { + if (options?.enabled === false) { + return { + data: undefined, + isLoading: false, + isRefreshing: false, + error: undefined, + refetch: vi.fn(), + mutate: vi.fn() + } + } + // Simulate loading + return { + data: undefined, + isLoading: true, + isRefreshing: false, + error: undefined, + refetch: vi.fn(), + mutate: vi.fn() + } + }) + + const { result } = renderHook(() => useDefaultModel()) + expect(result.current.isLoading).toBe(true) + }) + + it('should disable queries when preference is empty string', () => { + renderHook(() => useDefaultModel()) + + // All 3 useQuery calls should have enabled: false (since preference values are '') + const queryCallsWithEnabledFalse = mockUseQuery.mock.calls.filter((call) => call[1]?.enabled === false) + expect(queryCallsWithEnabledFalse.length).toBe(3) + }) + + it('should provide setter functions for each model', async () => { + const mockSetDefault = vi.fn().mockResolvedValue(undefined) + const mockSetQuick = vi.fn().mockResolvedValue(undefined) + const mockSetTranslate = vi.fn().mockResolvedValue(undefined) + + mockUsePreference.mockImplementation((key: string) => { + switch (key) { + case 'model.default_id': + return ['', mockSetDefault] + case 'model.quick_id': + return ['', mockSetQuick] + case 'model.translate_id': + return ['', mockSetTranslate] + default: + return ['', vi.fn()] + } + }) + + const { result } = renderHook(() => useDefaultModel()) + + await act(async () => { + await result.current.setDefaultModel('openai::gpt-4o' as any) + }) + expect(mockSetDefault).toHaveBeenCalledWith('openai::gpt-4o') + + await act(async () => { + await result.current.setQuickModel('openai::gpt-4o-mini' as any) + }) + expect(mockSetQuick).toHaveBeenCalledWith('openai::gpt-4o-mini') + + await act(async () => { + await result.current.setTranslateModel('anthropic::claude-3-haiku' as any) + }) + expect(mockSetTranslate).toHaveBeenCalledWith('anthropic::claude-3-haiku') + }) + + it('should call usePreference with correct keys', () => { + renderHook(() => useDefaultModel()) + + const calledKeys = mockUsePreference.mock.calls.map((call) => call[0]) + expect(calledKeys).toContain('model.default_id') + expect(calledKeys).toContain('model.quick_id') + expect(calledKeys).toContain('model.translate_id') + }) +}) diff --git a/src/renderer/src/data/hooks/__tests__/useModels.test.ts b/src/renderer/src/data/hooks/__tests__/useModels.test.ts new file mode 100644 index 00000000000..82ee67b0bb0 --- /dev/null +++ b/src/renderer/src/data/hooks/__tests__/useModels.test.ts @@ -0,0 +1,184 @@ +import { mockDataApiService } from '@test-mocks/renderer/DataApiService' +import { mockUseInvalidateCache, mockUseMutation, mockUseQuery } from '@test-mocks/renderer/useDataApi' +import { act, renderHook } from '@testing-library/react' +import { beforeEach, describe, expect, it, vi } from 'vitest' + +import { useModelMutations, useModels } from '../useModels' + +// ─── Mock data ──────────────────────────────────────────────────────── +const mockModel1: any = { + id: 'openai::gpt-4o', + providerId: 'openai', + modelId: 'gpt-4o', + name: 'GPT-4o', + isEnabled: true +} + +const mockModel2: any = { + id: 'anthropic::claude-3-opus', + providerId: 'anthropic', + modelId: 'claude-3-opus', + name: 'Claude 3 Opus', + isEnabled: true +} + +const mockModelList = [mockModel1, mockModel2] + +describe('useModels', () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + it('should return models array from useQuery', () => { + mockUseQuery.mockImplementation(() => ({ + data: mockModelList, + isLoading: false, + isRefreshing: false, + error: undefined, + refetch: vi.fn(), + mutate: vi.fn() + })) + + const { result } = renderHook(() => useModels()) + + expect(result.current.models).toEqual(mockModelList) + expect(result.current.isLoading).toBe(false) + }) + + it('should return empty array when data is undefined', () => { + mockUseQuery.mockImplementation(() => ({ + data: undefined, + isLoading: true, + isRefreshing: false, + error: undefined, + refetch: vi.fn(), + mutate: vi.fn() + })) + + const { result } = renderHook(() => useModels()) + + expect(result.current.models).toEqual([]) + expect(result.current.isLoading).toBe(true) + }) + + it('should call useQuery with /models path and no query when no args', () => { + renderHook(() => useModels()) + + expect(mockUseQuery).toHaveBeenCalledWith('/models', {}) + }) + + it('should pass providerId as query parameter', () => { + renderHook(() => useModels({ providerId: 'openai' })) + + expect(mockUseQuery).toHaveBeenCalledWith('/models', { query: { providerId: 'openai' } }) + }) + + it('should pass enabled option separately from query params', () => { + renderHook(() => useModels({ enabled: false })) + + expect(mockUseQuery).toHaveBeenCalledWith('/models', { enabled: false }) + }) + + it('should pass both providerId and enabled', () => { + renderHook(() => useModels({ providerId: 'openai', enabled: true })) + + expect(mockUseQuery).toHaveBeenCalledWith('/models', { + query: { providerId: 'openai' }, + enabled: true + }) + }) + + it('should expose refetch from mutate', () => { + const mockMutate = vi.fn() + mockUseQuery.mockImplementation(() => ({ + data: mockModelList, + isLoading: false, + isRefreshing: false, + error: undefined, + refetch: vi.fn(), + mutate: mockMutate + })) + + const { result } = renderHook(() => useModels()) + + expect(result.current.refetch).toBe(mockMutate) + }) +}) + +describe('useModelMutations', () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + it('should set up POST mutation for /models', () => { + renderHook(() => useModelMutations()) + + expect(mockUseMutation).toHaveBeenCalledWith('POST', '/models', { + refresh: ['/models'] + }) + }) + + it('should call createTrigger when createModel is invoked', async () => { + const mockTrigger = vi.fn().mockResolvedValue({ id: 'new-model' }) + mockUseMutation.mockImplementation(() => ({ + trigger: mockTrigger, + isLoading: false, + error: undefined + })) + + const { result } = renderHook(() => useModelMutations()) + + const dto = { providerId: 'openai', modelId: 'gpt-5' } + await act(async () => { + await result.current.createModel(dto) + }) + + expect(mockTrigger).toHaveBeenCalledWith({ body: dto }) + }) + + it('should delete model via dataApiService and invalidate cache', async () => { + const mockInvalidate = vi.fn().mockResolvedValue(undefined) + mockUseInvalidateCache.mockImplementation(() => mockInvalidate) + mockDataApiService.delete.mockResolvedValue({ deleted: true }) + + const { result } = renderHook(() => useModelMutations()) + + await act(async () => { + await result.current.deleteModel('openai', 'gpt-4o') + }) + + expect(mockDataApiService.delete).toHaveBeenCalledWith('/models/openai/gpt-4o') + expect(mockInvalidate).toHaveBeenCalledWith('/models') + }) + + it('should patch model via dataApiService and invalidate cache', async () => { + const mockInvalidate = vi.fn().mockResolvedValue(undefined) + mockUseInvalidateCache.mockImplementation(() => mockInvalidate) + mockDataApiService.patch.mockResolvedValue({}) + + const { result } = renderHook(() => useModelMutations()) + + await act(async () => { + await result.current.patchModel('openai', 'gpt-4o', { isEnabled: false }) + }) + + expect(mockDataApiService.patch).toHaveBeenCalledWith('/models/openai/gpt-4o', { + body: { isEnabled: false } + }) + expect(mockInvalidate).toHaveBeenCalledWith('/models') + }) + + it('should encode model ID in path correctly', async () => { + const mockInvalidate = vi.fn().mockResolvedValue(undefined) + mockUseInvalidateCache.mockImplementation(() => mockInvalidate) + mockDataApiService.delete.mockResolvedValue({ deleted: true }) + + const { result } = renderHook(() => useModelMutations()) + + await act(async () => { + await result.current.deleteModel('anthropic', 'claude-3-opus') + }) + + expect(mockDataApiService.delete).toHaveBeenCalledWith('/models/anthropic/claude-3-opus') + }) +}) diff --git a/src/renderer/src/data/hooks/__tests__/useProviders.test.ts b/src/renderer/src/data/hooks/__tests__/useProviders.test.ts new file mode 100644 index 00000000000..5b0bc035ca7 --- /dev/null +++ b/src/renderer/src/data/hooks/__tests__/useProviders.test.ts @@ -0,0 +1,388 @@ +import { mockDataApiService } from '@test-mocks/renderer/DataApiService' +import { mockUseInvalidateCache, mockUseMutation, mockUseQuery } from '@test-mocks/renderer/useDataApi' +import { act, renderHook } from '@testing-library/react' +import { beforeEach, describe, expect, it, vi } from 'vitest' + +import { + useProvider, + useProviderApiKeys, + useProviderAuthConfig, + useProviderCatalogModels, + useProviderMutations, + useProviders +} from '../useProviders' + +// ─── Mock data ──────────────────────────────────────────────────────── +const mockProvider1: any = { + id: 'openai', + name: 'OpenAI', + isEnabled: true, + sortOrder: 0 +} + +const mockProvider2: any = { + id: 'anthropic', + name: 'Anthropic', + isEnabled: true, + sortOrder: 1 +} + +const mockProviderList = [mockProvider1, mockProvider2] + +describe('useProviders', () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + it('should return providers array from useQuery', () => { + mockUseQuery.mockImplementation(() => ({ + data: mockProviderList, + isLoading: false, + isRefreshing: false, + error: undefined, + refetch: vi.fn(), + mutate: vi.fn() + })) + + const { result } = renderHook(() => useProviders()) + + expect(result.current.providers).toEqual(mockProviderList) + expect(result.current.isLoading).toBe(false) + }) + + it('should return empty array when data is undefined', () => { + mockUseQuery.mockImplementation(() => ({ + data: undefined, + isLoading: true, + isRefreshing: false, + error: undefined, + refetch: vi.fn(), + mutate: vi.fn() + })) + + const { result } = renderHook(() => useProviders()) + + expect(result.current.providers).toEqual([]) + expect(result.current.isLoading).toBe(true) + }) + + it('should call useQuery with /providers path', () => { + renderHook(() => useProviders()) + + expect(mockUseQuery).toHaveBeenCalledWith('/providers', undefined) + }) + + it('should pass enabled query option when provided', () => { + renderHook(() => useProviders({ enabled: false })) + + expect(mockUseQuery).toHaveBeenCalledWith('/providers', { query: { enabled: false } }) + }) + + it('should call useMutation for POST /providers', () => { + renderHook(() => useProviders()) + + expect(mockUseMutation).toHaveBeenCalledWith('POST', '/providers', { + refresh: ['/providers'] + }) + }) + + it('should call createTrigger when addProvider is invoked', async () => { + const mockTrigger = vi.fn().mockResolvedValue({ id: 'new-provider' }) + mockUseMutation.mockImplementation(() => ({ + trigger: mockTrigger, + isLoading: false, + error: undefined + })) + + const { result } = renderHook(() => useProviders()) + + const dto = { providerId: 'new-provider', name: 'New Provider' } + await act(async () => { + await result.current.addProvider(dto) + }) + + expect(mockTrigger).toHaveBeenCalledWith({ body: dto }) + }) + + it('should expose refetch from mutate', () => { + const mockMutate = vi.fn() + mockUseQuery.mockImplementation(() => ({ + data: mockProviderList, + isLoading: false, + isRefreshing: false, + error: undefined, + refetch: vi.fn(), + mutate: mockMutate + })) + + const { result } = renderHook(() => useProviders()) + + expect(result.current.refetch).toBe(mockMutate) + }) + + it('should perform optimistic reorder and patch each provider', async () => { + const mockMutate = vi.fn().mockResolvedValue(undefined) + mockUseQuery.mockImplementation(() => ({ + data: mockProviderList, + isLoading: false, + isRefreshing: false, + error: undefined, + refetch: vi.fn(), + mutate: mockMutate + })) + mockDataApiService.patch.mockResolvedValue({}) + + const { result } = renderHook(() => useProviders()) + + const reordered = [mockProvider2, mockProvider1] + await act(async () => { + await result.current.reorderProviders(reordered) + }) + + // Optimistic update + expect(mockMutate).toHaveBeenCalledWith(reordered, false) + + // Patch calls with sortOrder + expect(mockDataApiService.patch).toHaveBeenCalledWith('/providers/anthropic', { body: { sortOrder: 0 } }) + expect(mockDataApiService.patch).toHaveBeenCalledWith('/providers/openai', { body: { sortOrder: 1 } }) + + // Revalidate after success + expect(mockMutate).toHaveBeenCalledWith() + }) + + it('should revalidate on reorder failure', async () => { + const mockMutate = vi.fn().mockResolvedValue(undefined) + mockUseQuery.mockImplementation(() => ({ + data: mockProviderList, + isLoading: false, + isRefreshing: false, + error: undefined, + refetch: vi.fn(), + mutate: mockMutate + })) + mockDataApiService.patch.mockRejectedValue(new Error('Network error')) + + const { result } = renderHook(() => useProviders()) + + await act(async () => { + await result.current.reorderProviders([mockProvider2, mockProvider1]) + }) + + // Should still revalidate on error (rollback) + const revalidateCalls = mockMutate.mock.calls.filter((call: any[]) => call.length === 0) + expect(revalidateCalls.length).toBeGreaterThanOrEqual(1) + }) +}) + +describe('useProvider', () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + it('should query single provider by ID', () => { + mockUseQuery.mockImplementation((path: string) => ({ + data: path.includes('openai') ? mockProvider1 : undefined, + isLoading: false, + isRefreshing: false, + error: undefined, + refetch: vi.fn(), + mutate: vi.fn() + })) + + const { result } = renderHook(() => useProvider('openai')) + + expect(result.current.provider).toEqual(mockProvider1) + expect(result.current.isLoading).toBe(false) + expect(mockUseQuery).toHaveBeenCalledWith('/providers/openai') + }) + + it('should include mutation functions', () => { + const { result } = renderHook(() => useProvider('openai')) + + expect(result.current.updateProvider).toBeDefined() + expect(result.current.deleteProvider).toBeDefined() + expect(result.current.updateAuthConfig).toBeDefined() + expect(result.current.addApiKey).toBeDefined() + expect(result.current.deleteApiKey).toBeDefined() + }) +}) + +describe('useProviderMutations', () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + it('should set up PATCH and DELETE mutations with correct paths', () => { + renderHook(() => useProviderMutations('openai')) + + const patchCall = mockUseMutation.mock.calls.find((c: any[]) => c[0] === 'PATCH') + const deleteCall = mockUseMutation.mock.calls.find((c: any[]) => c[0] === 'DELETE') + + expect(patchCall).toBeDefined() + expect(patchCall![1]).toBe('/providers/openai') + expect(patchCall![2]).toEqual({ refresh: ['/providers'] }) + + expect(deleteCall).toBeDefined() + expect(deleteCall![1]).toBe('/providers/openai') + expect(deleteCall![2]).toEqual({ refresh: ['/providers'] }) + }) + + it('should call patchTrigger when updateProvider is invoked', async () => { + const mockTrigger = vi.fn().mockResolvedValue({}) + mockUseMutation.mockImplementation(() => ({ + trigger: mockTrigger, + isLoading: false, + error: undefined + })) + + const { result } = renderHook(() => useProviderMutations('openai')) + + await act(async () => { + await result.current.updateProvider({ isEnabled: false }) + }) + + expect(mockTrigger).toHaveBeenCalledWith({ body: { isEnabled: false } }) + }) + + it('should call deleteTrigger when deleteProvider is invoked', async () => { + const mockTrigger = vi.fn().mockResolvedValue(undefined) + mockUseMutation.mockImplementation(() => ({ + trigger: mockTrigger, + isLoading: false, + error: undefined + })) + + const { result } = renderHook(() => useProviderMutations('openai')) + + await act(async () => { + await result.current.deleteProvider() + }) + + expect(mockTrigger).toHaveBeenCalled() + }) + + it('should patch authConfig and invalidate auth-config cache', async () => { + const mockTrigger = vi.fn().mockResolvedValue({}) + const mockInvalidate = vi.fn().mockResolvedValue(undefined) + mockUseMutation.mockImplementation(() => ({ + trigger: mockTrigger, + isLoading: false, + error: undefined + })) + mockUseInvalidateCache.mockImplementation(() => mockInvalidate) + + const { result } = renderHook(() => useProviderMutations('openai')) + + const authConfig = { authType: 'api-key' } as any + await act(async () => { + await result.current.updateAuthConfig(authConfig) + }) + + expect(mockTrigger).toHaveBeenCalledWith({ body: { authConfig } }) + expect(mockInvalidate).toHaveBeenCalledWith('/providers/openai/auth-config') + }) + + it('should post API key and invalidate related caches', async () => { + const mockInvalidate = vi.fn().mockResolvedValue(undefined) + mockUseInvalidateCache.mockImplementation(() => mockInvalidate) + mockDataApiService.post.mockResolvedValue({}) + + const { result } = renderHook(() => useProviderMutations('openai')) + + await act(async () => { + await result.current.addApiKey('sk-test-key', 'My Key') + }) + + expect(mockDataApiService.post).toHaveBeenCalledWith('/providers/openai/api-keys', { + body: { key: 'sk-test-key', label: 'My Key' } + }) + expect(mockInvalidate).toHaveBeenCalledWith(['/providers/openai', '/providers/openai/api-keys', '/providers']) + }) + + it('should delete API key and invalidate related caches', async () => { + const mockInvalidate = vi.fn().mockResolvedValue(undefined) + mockUseInvalidateCache.mockImplementation(() => mockInvalidate) + mockDataApiService.delete.mockResolvedValue({}) + + const { result } = renderHook(() => useProviderMutations('openai')) + + await act(async () => { + await result.current.deleteApiKey('key-123') + }) + + expect(mockDataApiService.delete).toHaveBeenCalledWith('/providers/openai/api-keys/key-123') + expect(mockInvalidate).toHaveBeenCalledWith(['/providers/openai', '/providers/openai/api-keys', '/providers']) + }) +}) + +describe('useProviderAuthConfig', () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + it('should query auth config for a provider', () => { + const mockAuthConfig = { authType: 'oauth' } as any + mockUseQuery.mockImplementation((path: string) => ({ + data: path.includes('auth-config') ? mockAuthConfig : undefined, + isLoading: false, + isRefreshing: false, + error: undefined, + refetch: vi.fn(), + mutate: vi.fn() + })) + + const { result } = renderHook(() => useProviderAuthConfig('vertexai')) + + expect(result.current.data).toEqual(mockAuthConfig) + expect(result.current.isLoading).toBe(false) + expect(mockUseQuery).toHaveBeenCalledWith('/providers/vertexai/auth-config') + }) +}) + +describe('useProviderApiKeys', () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + it('should query API keys for a provider', () => { + const mockKeys = { keys: [{ id: 'k1', key: 'sk-xxx', isEnabled: true }] } + mockUseQuery.mockImplementation((path: string) => ({ + data: path.includes('api-keys') ? mockKeys : undefined, + isLoading: false, + isRefreshing: false, + error: undefined, + refetch: vi.fn(), + mutate: vi.fn() + })) + + const { result } = renderHook(() => useProviderApiKeys('openai')) + + expect(result.current.data).toEqual(mockKeys) + expect(result.current.isLoading).toBe(false) + expect(mockUseQuery).toHaveBeenCalledWith('/providers/openai/api-keys') + }) +}) + +describe('useProviderCatalogModels', () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + it('should query catalog models for a provider', () => { + const mockModels = [{ id: 'gpt-4o', name: 'GPT-4o', providerId: 'openai' }] + mockUseQuery.mockImplementation((path: string) => ({ + data: path.includes('catalog-models') ? mockModels : undefined, + isLoading: false, + isRefreshing: false, + error: undefined, + refetch: vi.fn(), + mutate: vi.fn() + })) + + const { result } = renderHook(() => useProviderCatalogModels('openai')) + + expect(result.current.data).toEqual(mockModels) + expect(result.current.isLoading).toBe(false) + expect(mockUseQuery).toHaveBeenCalledWith('/providers/openai/catalog-models') + }) +}) diff --git a/src/renderer/src/data/hooks/useDefaultModel.v2.ts b/src/renderer/src/data/hooks/useDefaultModel.v2.ts new file mode 100644 index 00000000000..03ead86eb92 --- /dev/null +++ b/src/renderer/src/data/hooks/useDefaultModel.v2.ts @@ -0,0 +1,60 @@ +import type { Model } from '@shared/data/types/model' +import { parseUniqueModelId, type UniqueModelId } from '@shared/data/types/model' + +import { useQuery } from './useDataApi' +import { usePreference } from './usePreference' + +/** + * Internal helper: resolve a UniqueModelId string into a Model via useQuery. + * When uniqueModelId is undefined, the query is disabled (no request is made). + */ +function useResolveModel(uniqueModelId: string | undefined) { + const parsed = uniqueModelId ? parseUniqueModelId(uniqueModelId as UniqueModelId) : null + const { data, isLoading } = useQuery(`/models/${parsed?.providerId ?? ''}/${parsed?.modelId ?? ''}` as any, { + enabled: !!parsed + }) + return { model: data as Model | undefined, isLoading: !!parsed && isLoading } +} + +export interface UseDefaultModelReturn { + defaultModel?: Model + defaultModelId: string + quickModel?: Model + quickModelId: string + translateModel?: Model + translateModelId: string + isLoading: boolean + setDefaultModel: (id: UniqueModelId) => Promise + setQuickModel: (id: UniqueModelId) => Promise + setTranslateModel: (id: UniqueModelId) => Promise +} + +/** + * v2 replacement for the v1 useDefaultModel hook. + * + * v1 stored full Model objects in Redux state. + * v2 stores UniqueModelId strings in Preferences and resolves them via useQuery. + */ +export function useDefaultModel(): UseDefaultModelReturn { + const [defaultModelId, setDefaultModelId] = usePreference('model.default_id') + const [quickModelId, setQuickModelId] = usePreference('model.quick_id') + const [translateModelId, setTranslateModelId] = usePreference('model.translate_id') + + // Empty string → undefined to disable the query + const { model: defaultModel, isLoading: loadingDefault } = useResolveModel(defaultModelId || undefined) + const { model: quickModel, isLoading: loadingQuick } = useResolveModel(quickModelId || undefined) + const { model: translateModel, isLoading: loadingTranslate } = useResolveModel(translateModelId || undefined) + + return { + defaultModel, + defaultModelId, + quickModel, + quickModelId, + translateModel, + translateModelId, + isLoading: loadingDefault || loadingQuick || loadingTranslate, + setDefaultModel: setDefaultModelId as (id: UniqueModelId) => Promise, + setQuickModel: setQuickModelId as (id: UniqueModelId) => Promise, + setTranslateModel: setTranslateModelId as (id: UniqueModelId) => Promise + } +} diff --git a/src/renderer/src/data/hooks/useModels.ts b/src/renderer/src/data/hooks/useModels.ts new file mode 100644 index 00000000000..0299ef2914b --- /dev/null +++ b/src/renderer/src/data/hooks/useModels.ts @@ -0,0 +1,57 @@ +import { dataApiService } from '@data/DataApiService' +import type { ConcreteApiPaths } from '@shared/data/api/apiTypes' +import type { CreateModelDto, UpdateModelDto } from '@shared/data/api/schemas/models' +import type { Model } from '@shared/data/types/model' +import { useCallback } from 'react' + +import { useInvalidateCache, useMutation, useQuery } from './useDataApi' + +/** Helper to build `/models/:providerId/:modelId` concrete path (tsgo cannot resolve two-segment template literals) */ +function modelPath(providerId: string, modelId: string): ConcreteApiPaths { + return `/models/${providerId}/${modelId}` as ConcreteApiPaths +} + +const REFRESH_MODELS = ['/models'] as const + +// ─── Layer 1: List ──────────────────────────────────────────────────── +export function useModels(query?: { providerId?: string; enabled?: boolean }) { + const { providerId, enabled, ...rest } = query ?? {} + const queryParams = providerId ? { providerId, ...rest } : rest + const hasQuery = Object.keys(queryParams).length > 0 + + const { data, isLoading, mutate } = useQuery('/models', { + ...(hasQuery ? { query: queryParams } : {}), + ...(enabled !== undefined ? { enabled } : {}) + }) as { data: Model[] | undefined; isLoading: boolean; mutate: any } + + return { models: data ?? [], isLoading, refetch: mutate } +} + +// ─── Layer 2: Mutations ─────────────────────────────────────────────── +export function useModelMutations() { + const invalidate = useInvalidateCache() + + const { trigger: createTrigger } = useMutation('POST', '/models', { + refresh: [...REFRESH_MODELS] + }) + + const createModel = useCallback((dto: CreateModelDto) => createTrigger({ body: dto }), [createTrigger]) + + const deleteModel = useCallback( + async (providerId: string, modelId: string) => { + await dataApiService.delete(modelPath(providerId, modelId)) + await invalidate('/models') + }, + [invalidate] + ) + + const patchModel = useCallback( + async (providerId: string, modelId: string, updates: UpdateModelDto) => { + await dataApiService.patch(modelPath(providerId, modelId), { body: updates }) + await invalidate('/models') + }, + [invalidate] + ) + + return { createModel, deleteModel, patchModel } +} diff --git a/src/renderer/src/data/hooks/useProviders.ts b/src/renderer/src/data/hooks/useProviders.ts new file mode 100644 index 00000000000..277e2ab288a --- /dev/null +++ b/src/renderer/src/data/hooks/useProviders.ts @@ -0,0 +1,161 @@ +import { dataApiService } from '@data/DataApiService' +import type { CreateProviderDto, UpdateProviderDto } from '@shared/data/api/schemas/providers' +import type { Model } from '@shared/data/types/model' +import type { ApiKeyEntry, AuthConfig, Provider } from '@shared/data/types/provider' +import { useCallback, useMemo } from 'react' + +import { useInvalidateCache, useMutation, useQuery } from './useDataApi' + +const REFRESH_PROVIDERS = ['/providers'] as const +const EMPTY_PROVIDERS: Provider[] = [] + +// ─── Layer 1: List + Create + Reorder ───────────────────────────────── +export function useProviders(query?: { enabled?: boolean }) { + const { data, isLoading, mutate } = useQuery('/providers', query ? { query } : undefined) + + const { trigger: createTrigger } = useMutation('POST', '/providers', { + refresh: [...REFRESH_PROVIDERS] + }) + + const addProvider = useCallback((dto: CreateProviderDto) => createTrigger({ body: dto }), [createTrigger]) + + const reorderProviders = useCallback( + async (reorderedList: Provider[]) => { + void mutate(reorderedList as any, false) // optimistic + try { + await Promise.all( + reorderedList.map((p, i) => dataApiService.patch(`/providers/${p.id}` as const, { body: { sortOrder: i } })) + ) + void mutate() + } catch { + void mutate() + } + }, + [mutate] + ) + + const providers = useMemo(() => (data as Provider[] | undefined) ?? EMPTY_PROVIDERS, [data]) + + return { + providers, + isLoading, + addProvider, + reorderProviders, + refetch: mutate + } +} + +// ─── Layer 2: Single read + write + delete ──────────────────────────── +export function useProvider(providerId: string) { + const { data, isLoading } = useQuery(`/providers/${providerId}` as const) as { + data: Provider | undefined + isLoading: boolean + [k: string]: any + } + + const mutations = useProviderMutations(providerId) + + return { provider: data, isLoading, ...mutations } +} + +// ─── Layer 3: Pure mutations ────────────────────────────────────────── +export function useProviderMutations(providerId: string) { + const path = `/providers/${providerId}` as const + const invalidate = useInvalidateCache() + + const { trigger: patchTrigger } = useMutation('PATCH', path, { + refresh: [...REFRESH_PROVIDERS] + }) + + const { trigger: deleteTrigger } = useMutation('DELETE', path, { + refresh: [...REFRESH_PROVIDERS] + }) + + const updateProvider = useCallback((updates: UpdateProviderDto) => patchTrigger({ body: updates }), [patchTrigger]) + + const deleteProvider = useCallback(() => deleteTrigger(), [deleteTrigger]) + + const updateAuthConfig = useCallback( + async (authConfig: AuthConfig) => { + await patchTrigger({ body: { authConfig } }) + await invalidate(`/providers/${providerId}/auth-config`) + }, + [patchTrigger, invalidate, providerId] + ) + + const addApiKey = useCallback( + async (key: string, label?: string) => { + await dataApiService.post(`/providers/${providerId}/api-keys` as const, { + body: { key, label } + }) + await invalidate([`/providers/${providerId}`, `/providers/${providerId}/api-keys`, '/providers']) + }, + [providerId, invalidate] + ) + + const deleteApiKey = useCallback( + async (keyId: string) => { + await dataApiService.delete(`/providers/${providerId}/api-keys/${keyId}` as const) + await invalidate([`/providers/${providerId}`, `/providers/${providerId}/api-keys`, '/providers']) + }, + [providerId, invalidate] + ) + + const updateApiKeys = useCallback( + async (apiKeys: ApiKeyEntry[]) => { + await patchTrigger({ body: { apiKeys } }) + await invalidate(`/providers/${providerId}/api-keys`) + }, + [patchTrigger, invalidate, providerId] + ) + + return { updateProvider, deleteProvider, updateAuthConfig, updateApiKeys, addApiKey, deleteApiKey } +} + +// ─── Typed query helpers ───────────────────────────────────────────── +export function useProviderAuthConfig(providerId: string) { + return useQuery(`/providers/${providerId}/auth-config` as const) as { + data: AuthConfig | null | undefined + isLoading: boolean + [k: string]: any + } +} + +export function useProviderApiKeys(providerId: string) { + return useQuery(`/providers/${providerId}/api-keys` as const) as { + data: { keys: ApiKeyEntry[] } | undefined + isLoading: boolean + [k: string]: any + } +} + +export function useProviderCatalogModels(providerId: string) { + return useQuery(`/providers/${providerId}/catalog-models` as const) as { + data: Model[] | undefined + isLoading: boolean + [k: string]: any + } +} + +// ─── Dynamic ID operations (for context menus, URL schema handlers) ── +export function useProviderActions() { + const invalidate = useInvalidateCache() + + const patchProviderById = useCallback( + async (providerId: string, updates: UpdateProviderDto) => { + await dataApiService.patch(`/providers/${providerId}` as const, { body: updates }) + await invalidate('/providers') + }, + [invalidate] + ) + + const deleteProviderById = useCallback( + async (providerId: string) => { + await dataApiService.delete(`/providers/${providerId}` as const) + await invalidate('/providers') + }, + [invalidate] + ) + + return { patchProviderById, deleteProviderById } +} diff --git a/src/renderer/src/utils/__tests__/provider.v2.test.ts b/src/renderer/src/utils/__tests__/provider.v2.test.ts new file mode 100644 index 00000000000..7dfb4484788 --- /dev/null +++ b/src/renderer/src/utils/__tests__/provider.v2.test.ts @@ -0,0 +1,300 @@ +import { EndpointType } from '@shared/data/types/model' +import type { Provider } from '@shared/data/types/provider' +import { describe, expect, it } from 'vitest' + +import { + getFancyProviderName, + hasApiKeys, + isAnthropicProvider, + isAnthropicSupportedProvider, + isAwsBedrockProvider, + isAzureOpenAIProvider, + isCherryAIProvider, + isGeminiProvider, + isNewApiProvider, + isOllamaProvider, + isOpenAIChatProvider, + isOpenAICompatibleProvider, + isOpenAIResponsesProvider, + isPerplexityProvider, + isSupportArrayContentProvider, + isSupportDeveloperRoleProvider, + isSupportEnableThinkingProvider, + isSupportServiceTierProvider, + isSupportStreamOptionsProvider, + isSupportVerbosityProvider, + isSystemProvider, + isVertexProvider, + matchKeywordsInProvider, + replaceBaseUrlDomain +} from '../provider.v2' + +/** Helper to create a minimal v2 Provider for testing */ +function makeProvider(overrides: Partial = {}): Provider { + return { + id: 'test-provider', + name: 'Test Provider', + apiKeys: [], + authType: 'api-key', + apiFeatures: { + arrayContent: true, + streamOptions: true, + developerRole: false, + serviceTier: false, + verbosity: false, + enableThinking: true + }, + settings: {}, + isEnabled: true, + ...overrides + } +} + +describe('provider.v2 - Protocol-level identity checks', () => { + it('isAnthropicProvider: true when defaultChatEndpoint is ANTHROPIC_MESSAGES', () => { + const p = makeProvider({ defaultChatEndpoint: EndpointType.ANTHROPIC_MESSAGES }) + expect(isAnthropicProvider(p)).toBe(true) + }) + + it('isAnthropicProvider: false for other endpoints', () => { + expect(isAnthropicProvider(makeProvider({ defaultChatEndpoint: EndpointType.OPENAI_CHAT_COMPLETIONS }))).toBe(false) + expect(isAnthropicProvider(makeProvider())).toBe(false) // undefined endpoint + }) + + it('isGeminiProvider: true when defaultChatEndpoint is GOOGLE_GENERATE_CONTENT', () => { + const p = makeProvider({ defaultChatEndpoint: EndpointType.GOOGLE_GENERATE_CONTENT }) + expect(isGeminiProvider(p)).toBe(true) + }) + + it('isGeminiProvider: false for other endpoints', () => { + expect(isGeminiProvider(makeProvider())).toBe(false) + }) + + it('isOllamaProvider: true when defaultChatEndpoint is OLLAMA_CHAT', () => { + const p = makeProvider({ defaultChatEndpoint: EndpointType.OLLAMA_CHAT }) + expect(isOllamaProvider(p)).toBe(true) + }) + + it('isOpenAIResponsesProvider: true when defaultChatEndpoint is OPENAI_RESPONSES', () => { + const p = makeProvider({ defaultChatEndpoint: EndpointType.OPENAI_RESPONSES }) + expect(isOpenAIResponsesProvider(p)).toBe(true) + }) + + it('isOpenAIChatProvider: true when defaultChatEndpoint is OPENAI_CHAT_COMPLETIONS', () => { + const p = makeProvider({ defaultChatEndpoint: EndpointType.OPENAI_CHAT_COMPLETIONS }) + expect(isOpenAIChatProvider(p)).toBe(true) + }) +}) + +describe('provider.v2 - Vendor-level identity checks (authType)', () => { + it('isAzureOpenAIProvider: true when authType is iam-azure', () => { + const p = makeProvider({ authType: 'iam-azure' }) + expect(isAzureOpenAIProvider(p)).toBe(true) + }) + + it('isAzureOpenAIProvider: false for other authTypes', () => { + expect(isAzureOpenAIProvider(makeProvider({ authType: 'api-key' }))).toBe(false) + }) + + it('isVertexProvider: true when authType is iam-gcp', () => { + const p = makeProvider({ authType: 'iam-gcp' }) + expect(isVertexProvider(p)).toBe(true) + }) + + it('isAwsBedrockProvider: true when authType is iam-aws', () => { + const p = makeProvider({ authType: 'iam-aws' }) + expect(isAwsBedrockProvider(p)).toBe(true) + }) +}) + +describe('provider.v2 - ID-level identity checks', () => { + it('isCherryAIProvider: true when id is cherryai', () => { + expect(isCherryAIProvider(makeProvider({ id: 'cherryai' }))).toBe(true) + expect(isCherryAIProvider(makeProvider({ id: 'other' }))).toBe(false) + }) + + it('isPerplexityProvider: true when id is perplexity', () => { + expect(isPerplexityProvider(makeProvider({ id: 'perplexity' }))).toBe(true) + expect(isPerplexityProvider(makeProvider({ id: 'other' }))).toBe(false) + }) + + it('isNewApiProvider: true for new-api or cherryin id, or presetProviderId new-api', () => { + expect(isNewApiProvider(makeProvider({ id: 'new-api' }))).toBe(true) + expect(isNewApiProvider(makeProvider({ id: 'cherryin' }))).toBe(true) + expect(isNewApiProvider(makeProvider({ id: 'custom', presetProviderId: 'new-api' }))).toBe(true) + expect(isNewApiProvider(makeProvider({ id: 'openai' }))).toBe(false) + }) + + it('isSystemProvider: true when presetProviderId is defined', () => { + expect(isSystemProvider(makeProvider({ presetProviderId: 'openai' }))).toBe(true) + expect(isSystemProvider(makeProvider())).toBe(false) // no presetProviderId + }) +}) + +describe('provider.v2 - Composite identity checks', () => { + it('isOpenAICompatibleProvider: true for OPENAI_CHAT_COMPLETIONS or OPENAI_RESPONSES', () => { + expect( + isOpenAICompatibleProvider(makeProvider({ defaultChatEndpoint: EndpointType.OPENAI_CHAT_COMPLETIONS })) + ).toBe(true) + expect(isOpenAICompatibleProvider(makeProvider({ defaultChatEndpoint: EndpointType.OPENAI_RESPONSES }))).toBe(true) + expect(isOpenAICompatibleProvider(makeProvider({ defaultChatEndpoint: EndpointType.ANTHROPIC_MESSAGES }))).toBe( + false + ) + }) + + it('isAnthropicSupportedProvider: true for ANTHROPIC_MESSAGES endpoint', () => { + const p = makeProvider({ defaultChatEndpoint: EndpointType.ANTHROPIC_MESSAGES }) + expect(isAnthropicSupportedProvider(p)).toBe(true) + }) + + it('isAnthropicSupportedProvider: true when baseUrls has ANTHROPIC_MESSAGES key', () => { + const p = makeProvider({ + defaultChatEndpoint: EndpointType.OPENAI_CHAT_COMPLETIONS, + baseUrls: { [EndpointType.ANTHROPIC_MESSAGES]: 'https://api.example.com' } + }) + expect(isAnthropicSupportedProvider(p)).toBe(true) + }) + + it('isAnthropicSupportedProvider: false when neither condition is met', () => { + const p = makeProvider({ defaultChatEndpoint: EndpointType.OPENAI_CHAT_COMPLETIONS }) + expect(isAnthropicSupportedProvider(p)).toBe(false) + }) +}) + +describe('provider.v2 - Capability checks (apiFeatures)', () => { + it('isSupportArrayContentProvider reads apiFeatures.arrayContent', () => { + expect(isSupportArrayContentProvider(makeProvider({ apiFeatures: features({ arrayContent: true }) }))).toBe(true) + expect(isSupportArrayContentProvider(makeProvider({ apiFeatures: features({ arrayContent: false }) }))).toBe(false) + }) + + it('isSupportDeveloperRoleProvider reads apiFeatures.developerRole', () => { + expect(isSupportDeveloperRoleProvider(makeProvider({ apiFeatures: features({ developerRole: true }) }))).toBe(true) + expect(isSupportDeveloperRoleProvider(makeProvider({ apiFeatures: features({ developerRole: false }) }))).toBe( + false + ) + }) + + it('isSupportStreamOptionsProvider reads apiFeatures.streamOptions', () => { + expect(isSupportStreamOptionsProvider(makeProvider({ apiFeatures: features({ streamOptions: true }) }))).toBe(true) + expect(isSupportStreamOptionsProvider(makeProvider({ apiFeatures: features({ streamOptions: false }) }))).toBe( + false + ) + }) + + it('isSupportServiceTierProvider reads apiFeatures.serviceTier', () => { + expect(isSupportServiceTierProvider(makeProvider({ apiFeatures: features({ serviceTier: true }) }))).toBe(true) + expect(isSupportServiceTierProvider(makeProvider({ apiFeatures: features({ serviceTier: false }) }))).toBe(false) + }) + + it('isSupportVerbosityProvider reads apiFeatures.verbosity', () => { + expect(isSupportVerbosityProvider(makeProvider({ apiFeatures: features({ verbosity: true }) }))).toBe(true) + expect(isSupportVerbosityProvider(makeProvider({ apiFeatures: features({ verbosity: false }) }))).toBe(false) + }) + + it('isSupportEnableThinkingProvider reads apiFeatures.enableThinking', () => { + expect(isSupportEnableThinkingProvider(makeProvider({ apiFeatures: features({ enableThinking: true }) }))).toBe( + true + ) + expect(isSupportEnableThinkingProvider(makeProvider({ apiFeatures: features({ enableThinking: false }) }))).toBe( + false + ) + }) +}) + +describe('provider.v2 - Display helpers', () => { + it('getFancyProviderName: uses provider label for system providers', () => { + const p = makeProvider({ id: 'openai', presetProviderId: 'openai', name: 'My Custom Name' }) + // System provider → should use i18n label (mocked as id-based fallback) + const name = getFancyProviderName(p) + expect(typeof name).toBe('string') + expect(name.length).toBeGreaterThan(0) + }) + + it('getFancyProviderName: uses provider.name for custom providers', () => { + const p = makeProvider({ name: 'My Custom Provider' }) + expect(getFancyProviderName(p)).toBe('My Custom Provider') + }) + + it('matchKeywordsInProvider: matches by name for custom providers', () => { + const p = makeProvider({ name: 'My Custom Provider' }) + expect(matchKeywordsInProvider(['custom'], p)).toBe(true) + expect(matchKeywordsInProvider(['nonexistent'], p)).toBe(false) + }) + + it('matchKeywordsInProvider: matches by id for system providers', () => { + const p = makeProvider({ id: 'openai', presetProviderId: 'openai' }) + expect(matchKeywordsInProvider(['openai'], p)).toBe(true) + }) + + it('matchKeywordsInProvider: returns true for empty keywords', () => { + const p = makeProvider() + expect(matchKeywordsInProvider([], p)).toBe(true) + }) +}) + +describe('provider.v2 - API Key helpers', () => { + it('hasApiKeys: returns false for empty apiKeys array', () => { + expect(hasApiKeys(makeProvider({ apiKeys: [] }))).toBe(false) + }) + + it('hasApiKeys: returns false when all keys are disabled', () => { + expect( + hasApiKeys( + makeProvider({ + apiKeys: [ + { id: '1', isEnabled: false }, + { id: '2', isEnabled: false } + ] + }) + ) + ).toBe(false) + }) + + it('hasApiKeys: returns true when at least one key is enabled', () => { + expect( + hasApiKeys( + makeProvider({ + apiKeys: [ + { id: '1', isEnabled: false }, + { id: '2', isEnabled: true } + ] + }) + ) + ).toBe(true) + }) +}) + +describe('provider.v2 - Base URL helpers', () => { + it('replaceBaseUrlDomain: replaces domain in all URLs while preserving paths', () => { + const result = replaceBaseUrlDomain({ 1: 'https://old.com/v1', 3: 'https://old.com/anthropic' }, 'new.com') + expect(result[1]).toBe('https://new.com/v1') + expect(result[3]).toBe('https://new.com/anthropic') + }) + + it('replaceBaseUrlDomain: returns empty object for undefined input', () => { + expect(replaceBaseUrlDomain(undefined, 'new.com')).toEqual({}) + }) + + it('replaceBaseUrlDomain: preserves invalid URLs unchanged', () => { + const result = replaceBaseUrlDomain({ 1: 'not-a-url' }, 'new.com') + expect(result[1]).toBe('not-a-url') + }) + + it('replaceBaseUrlDomain: handles URLs with ports and paths', () => { + const result = replaceBaseUrlDomain({ 6: 'http://localhost:11434/api' }, '192.168.1.100') + expect(result[6]).toBe('http://192.168.1.100:11434/api') + }) +}) + +/** Helper to create RuntimeApiFeatures with defaults */ +function features(overrides: Partial = {}): Provider['apiFeatures'] { + return { + arrayContent: true, + streamOptions: true, + developerRole: false, + serviceTier: false, + verbosity: false, + enableThinking: true, + ...overrides + } +} diff --git a/src/renderer/src/utils/provider.v2.ts b/src/renderer/src/utils/provider.v2.ts new file mode 100644 index 00000000000..be29efbdcd9 --- /dev/null +++ b/src/renderer/src/utils/provider.v2.ts @@ -0,0 +1,152 @@ +import { getProviderLabel } from '@renderer/i18n/label' +import { EndpointType } from '@shared/data/types/model' +import type { Provider } from '@shared/data/types/provider' + +// ─── Protocol-level: check defaultChatEndpoint ─────────────────────────────── + +export function isAnthropicProvider(provider: Provider): boolean { + return provider.defaultChatEndpoint === EndpointType.ANTHROPIC_MESSAGES +} + +export function isGeminiProvider(provider: Provider): boolean { + return provider.defaultChatEndpoint === EndpointType.GOOGLE_GENERATE_CONTENT +} + +export function isOllamaProvider(provider: Provider): boolean { + return provider.defaultChatEndpoint === EndpointType.OLLAMA_CHAT +} + +export function isOpenAIResponsesProvider(provider: Provider): boolean { + return provider.defaultChatEndpoint === EndpointType.OPENAI_RESPONSES +} + +export function isOpenAIChatProvider(provider: Provider): boolean { + return provider.defaultChatEndpoint === EndpointType.OPENAI_CHAT_COMPLETIONS +} + +// ─── Vendor-level: check authType ──────────────────────────────────────────── +// Azure/Vertex/Bedrock reuse other vendors' endpoint protocols, +// so authType is the only reliable discriminator. + +export function isAzureOpenAIProvider(provider: Provider): boolean { + return provider.authType === 'iam-azure' +} + +export function isVertexProvider(provider: Provider): boolean { + return provider.authType === 'iam-gcp' +} + +export function isAwsBedrockProvider(provider: Provider): boolean { + return provider.authType === 'iam-aws' +} + +// ─── ID-level: direct comparison ───────────────────────────────────────────── + +export function isCherryAIProvider(provider: Provider): boolean { + return provider.id === 'cherryai' +} + +export function isPerplexityProvider(provider: Provider): boolean { + return provider.id === 'perplexity' +} + +export function isNewApiProvider(provider: Provider): boolean { + return ['new-api', 'cherryin'].includes(provider.id) || provider.presetProviderId === 'new-api' +} + +export function isSystemProvider(provider: Provider): boolean { + return provider.presetProviderId != null +} + +// ─── Composite ─────────────────────────────────────────────────────────────── + +export function isOpenAICompatibleProvider(provider: Provider): boolean { + return ( + provider.defaultChatEndpoint === EndpointType.OPENAI_CHAT_COMPLETIONS || + provider.defaultChatEndpoint === EndpointType.OPENAI_RESPONSES + ) +} + +export function isAnthropicSupportedProvider(provider: Provider): boolean { + return ( + provider.defaultChatEndpoint === EndpointType.ANTHROPIC_MESSAGES || + provider.baseUrls?.[EndpointType.ANTHROPIC_MESSAGES] != null + ) +} + +// ─── Capability checks (apiFeatures booleans) ──────────────────────────────── + +export function isSupportArrayContentProvider(provider: Provider): boolean { + return provider.apiFeatures.arrayContent +} + +export function isSupportDeveloperRoleProvider(provider: Provider): boolean { + return provider.apiFeatures.developerRole +} + +export function isSupportStreamOptionsProvider(provider: Provider): boolean { + return provider.apiFeatures.streamOptions +} + +export function isSupportServiceTierProvider(provider: Provider): boolean { + return provider.apiFeatures.serviceTier +} + +export function isSupportVerbosityProvider(provider: Provider): boolean { + return provider.apiFeatures.verbosity +} + +export function isSupportEnableThinkingProvider(provider: Provider): boolean { + return provider.apiFeatures.enableThinking +} + +// ─── Display helpers ───────────────────────────────────────────────────────── + +export function getFancyProviderName(provider: Provider): string { + return isSystemProvider(provider) ? getProviderLabel(provider.id) : provider.name +} + +export function getProviderSearchString(provider: Provider): string { + return isSystemProvider(provider) ? `${getProviderLabel(provider.id)} ${provider.id}` : provider.name +} + +export function matchKeywordsInProvider(keywords: string[], provider: Provider): boolean { + if (keywords.length === 0) return true + const searchStr = getProviderSearchString(provider).toLowerCase() + return keywords.every((kw) => searchStr.includes(kw)) +} + +// ─── API Key helpers ──────────────────────────────────────────────────────── + +/** + * Check if provider has at least one enabled API key. + * Replaces v1 `!isEmpty(provider.apiKey)`. + */ +export function hasApiKeys(provider: Provider): boolean { + return provider.apiKeys.length > 0 && provider.apiKeys.some((k) => k.isEnabled) +} + +// ─── Base URL helpers ─────────────────────────────────────────────────────── + +/** + * Replace the domain (host) in all baseUrls while preserving URL paths. + * Used by CherryIN/DMXAPI domain switching. + */ +export function replaceBaseUrlDomain( + baseUrls: Partial> | undefined, + newDomain: string +): Partial> { + if (!baseUrls) return {} + const result: Partial> = {} + for (const [key, url] of Object.entries(baseUrls)) { + if (!url) continue + try { + const parsed = new URL(url) + parsed.hostname = newDomain + result[Number(key)] = parsed.toString().replace(/\/$/, '') + } catch { + result[Number(key)] = url + } + } + return result +} diff --git a/src/renderer/src/utils/v1ProviderShim.ts b/src/renderer/src/utils/v1ProviderShim.ts new file mode 100644 index 00000000000..7e82b371d4d --- /dev/null +++ b/src/renderer/src/utils/v1ProviderShim.ts @@ -0,0 +1,109 @@ +// TODO(v2-cleanup): Phase 5 迁移完成后删除此文件 +// 将 v2 DataApi Provider 桥接为 v1 Provider 形状,供尚未迁移的下游使用。 + +import type { Model as V1Model, Provider as V1Provider, ProviderType } from '@renderer/types' +import { EndpointType, type Model as V2Model } from '@shared/data/types/model' +import type { Provider as V2Provider } from '@shared/data/types/provider' + +export interface V1ShimOptions { + /** 来自 useModels()(v2 Model 与 v1 在运行时可互操作) */ + models?: V2Model[] + /** 来自 useProviderApiKeys() keys join(',') 或表单本地 key */ + apiKey?: string + /** 覆盖 baseUrls 推导,例如表单中的 apiHost */ + apiHost?: string +} + +function defaultChatBaseUrl(v2: V2Provider): string { + const ep = v2.defaultChatEndpoint ?? EndpointType.OPENAI_CHAT_COMPLETIONS + return v2.baseUrls?.[ep] ?? '' +} + +function v1ProviderTypeFromV2(v2: V2Provider): ProviderType { + if (v2.authType === 'iam-azure') { + return 'azure-openai' + } + if (v2.authType === 'iam-gcp') { + return 'vertexai' + } + if (v2.authType === 'iam-aws') { + return 'aws-bedrock' + } + + if (v2.id === 'new-api' || v2.presetProviderId === 'new-api') { + return 'new-api' + } + if (v2.id === 'gateway') { + return 'gateway' + } + if (v2.id === 'mistral' || v2.presetProviderId === 'mistral') { + return 'mistral' + } + + const ep = v2.defaultChatEndpoint ?? EndpointType.OPENAI_CHAT_COMPLETIONS + + switch (ep) { + case EndpointType.OPENAI_RESPONSES: + return 'openai-response' + case EndpointType.ANTHROPIC_MESSAGES: + return 'anthropic' + case EndpointType.GOOGLE_GENERATE_CONTENT: + return 'gemini' + case EndpointType.OLLAMA_CHAT: + case EndpointType.OLLAMA_GENERATE: + return 'ollama' + case EndpointType.OPENAI_CHAT_COMPLETIONS: + case EndpointType.OPENAI_TEXT_COMPLETIONS: + default: + return 'openai' + } +} + +function apiFeaturesToApiOptions(v2: V2Provider): V1Provider['apiOptions'] { + const f = v2.apiFeatures + return { + isNotSupportArrayContent: !f.arrayContent, + isNotSupportStreamOptions: !f.streamOptions, + isSupportDeveloperRole: f.developerRole, + isNotSupportDeveloperRole: !f.developerRole, + isSupportServiceTier: f.serviceTier, + isNotSupportServiceTier: !f.serviceTier, + isNotSupportVerbosity: !f.verbosity, + isNotSupportEnableThinking: !f.enableThinking + } +} + +/** + * 将 v2 Provider 桥接为 v1 Provider 形状(临时过渡层)。 + */ +export function toV1ProviderShim(v2Provider: V2Provider, options: V1ShimOptions = {}): V1Provider { + const cache = v2Provider.settings?.cacheControl + const anthropicCacheControl = + cache != null + ? { + tokenThreshold: cache.tokenThreshold ?? 0, + cacheSystemMessage: cache.cacheSystemMessage ?? false, + cacheLastNMessages: cache.cacheLastNMessages ?? 0 + } + : undefined + + return { + id: v2Provider.id, + name: v2Provider.name, + type: v1ProviderTypeFromV2(v2Provider), + apiKey: options.apiKey ?? '', + apiHost: options.apiHost ?? defaultChatBaseUrl(v2Provider), + anthropicApiHost: v2Provider.baseUrls?.[EndpointType.ANTHROPIC_MESSAGES], + models: (options.models ?? []) as unknown as V1Model[], + enabled: v2Provider.isEnabled, + isSystem: v2Provider.presetProviderId != null, + rateLimit: v2Provider.settings?.rateLimit, + apiVersion: v2Provider.settings?.apiVersion, + serviceTier: v2Provider.settings?.serviceTier as V1Provider['serviceTier'], + verbosity: v2Provider.settings?.verbosity as V1Provider['verbosity'], + apiOptions: apiFeaturesToApiOptions(v2Provider), + anthropicCacheControl, + notes: v2Provider.settings?.notes, + extra_headers: v2Provider.settings?.extraHeaders + } as V1Provider +} From 5cff60788ef9337b0620805637a5463aad02f16d Mon Sep 17 00:00:00 2001 From: jidan745le <420511176@qq.com> Date: Sun, 5 Apr 2026 22:16:39 -0800 Subject: [PATCH 10/29] feat(renderer): migrate LMStudio/GPUStack/ModelNotes to v2 DataApi Signed-off-by: jidan745le <420511176@qq.com> --- .../ProviderSettings/GPUStackSettings.tsx | 21 ++++++++++++++----- .../ProviderSettings/LMStudioSettings.tsx | 21 ++++++++++++++----- .../ProviderSettings/ModelNotesPopup.tsx | 18 +++++++--------- 3 files changed, 39 insertions(+), 21 deletions(-) diff --git a/src/renderer/src/pages/settings/ProviderSettings/GPUStackSettings.tsx b/src/renderer/src/pages/settings/ProviderSettings/GPUStackSettings.tsx index 5928537e062..e50c705076d 100644 --- a/src/renderer/src/pages/settings/ProviderSettings/GPUStackSettings.tsx +++ b/src/renderer/src/pages/settings/ProviderSettings/GPUStackSettings.tsx @@ -1,4 +1,4 @@ -import { useGPUStackSettings } from '@renderer/hooks/useGPUStack' +import { useProvider } from '@renderer/data/hooks/useProviders' import { InputNumber } from 'antd' import type { FC } from 'react' import { useState } from 'react' @@ -7,11 +7,22 @@ import styled from 'styled-components' import { SettingHelpText, SettingHelpTextRow, SettingSubtitle } from '..' -const GPUStackSettings: FC = () => { - const { keepAliveTime, setKeepAliveTime } = useGPUStackSettings() - const [keepAliveMinutes, setKeepAliveMinutes] = useState(keepAliveTime) +interface Props { + providerId: string +} + +const GPUStackSettings: FC = ({ providerId }) => { + const { provider, updateProvider } = useProvider(providerId) const { t } = useTranslation() + const keepAliveTime = provider?.settings?.keepAliveTime ?? 0 + const [keepAliveMinutes, setKeepAliveMinutes] = useState(keepAliveTime) + + const handleBlur = async () => { + if (keepAliveMinutes === keepAliveTime) return + await updateProvider({ providerSettings: { ...provider?.settings, keepAliveTime: keepAliveMinutes } }) + } + return ( {t('gpustack.keep_alive_time.title')} @@ -19,7 +30,7 @@ const GPUStackSettings: FC = () => { style={{ width: '100%' }} value={keepAliveMinutes} onChange={(e) => setKeepAliveMinutes(Number(e))} - onBlur={() => setKeepAliveTime(keepAliveMinutes)} + onBlur={handleBlur} suffix={t('gpustack.keep_alive_time.placeholder')} step={5} /> diff --git a/src/renderer/src/pages/settings/ProviderSettings/LMStudioSettings.tsx b/src/renderer/src/pages/settings/ProviderSettings/LMStudioSettings.tsx index 821afa6158a..b3bf27d1789 100644 --- a/src/renderer/src/pages/settings/ProviderSettings/LMStudioSettings.tsx +++ b/src/renderer/src/pages/settings/ProviderSettings/LMStudioSettings.tsx @@ -1,4 +1,4 @@ -import { useLMStudioSettings } from '@renderer/hooks/useLMStudio' +import { useProvider } from '@renderer/data/hooks/useProviders' import { InputNumber } from 'antd' import type { FC } from 'react' import { useState } from 'react' @@ -7,11 +7,22 @@ import styled from 'styled-components' import { SettingHelpText, SettingHelpTextRow, SettingSubtitle } from '..' -const LMStudioSettings: FC = () => { - const { keepAliveTime, setKeepAliveTime } = useLMStudioSettings() - const [keepAliveMinutes, setKeepAliveMinutes] = useState(keepAliveTime) +interface Props { + providerId: string +} + +const LMStudioSettings: FC = ({ providerId }) => { + const { provider, updateProvider } = useProvider(providerId) const { t } = useTranslation() + const keepAliveTime = provider?.settings?.keepAliveTime ?? 0 + const [keepAliveMinutes, setKeepAliveMinutes] = useState(keepAliveTime) + + const handleBlur = async () => { + if (keepAliveMinutes === keepAliveTime) return + await updateProvider({ providerSettings: { ...provider?.settings, keepAliveTime: keepAliveMinutes } }) + } + return ( {t('lmstudio.keep_alive_time.title')} @@ -20,7 +31,7 @@ const LMStudioSettings: FC = () => { value={keepAliveMinutes} min={0} onChange={(e) => setKeepAliveMinutes(Math.floor(Number(e)))} - onBlur={() => setKeepAliveTime(keepAliveMinutes)} + onBlur={handleBlur} suffix={t('lmstudio.keep_alive_time.placeholder')} step={5} /> diff --git a/src/renderer/src/pages/settings/ProviderSettings/ModelNotesPopup.tsx b/src/renderer/src/pages/settings/ProviderSettings/ModelNotesPopup.tsx index dd0417612f1..d2e1e9aaa1d 100644 --- a/src/renderer/src/pages/settings/ProviderSettings/ModelNotesPopup.tsx +++ b/src/renderer/src/pages/settings/ProviderSettings/ModelNotesPopup.tsx @@ -1,7 +1,6 @@ import MarkdownEditor from '@renderer/components/MarkdownEditor' import { TopView } from '@renderer/components/TopView' -import { useProvider } from '@renderer/hooks/useProvider' -import type { Provider } from '@renderer/types' +import { useProvider } from '@renderer/data/hooks/useProviders' import { Modal } from 'antd' import type { FC } from 'react' import { useState } from 'react' @@ -9,24 +8,21 @@ import { useTranslation } from 'react-i18next' import styled from 'styled-components' interface ShowParams { - provider: Provider + providerId: string } interface Props extends ShowParams { resolve: (data: any) => void } -const PopupContainer: FC = ({ provider: _provider, resolve }) => { +const PopupContainer: FC = ({ providerId, resolve }) => { const { t } = useTranslation() const [open, setOpen] = useState(true) - const { provider, updateProvider } = useProvider(_provider.id) - const [notes, setNotes] = useState(provider.notes || '') + const { provider, updateProvider } = useProvider(providerId) + const [notes, setNotes] = useState(provider?.settings?.notes || '') - const handleSave = () => { - updateProvider({ - ...provider, - notes - }) + const handleSave = async () => { + await updateProvider({ providerSettings: { ...provider?.settings, notes } }) setOpen(false) } From 08346160237974a402ddabd0d49f225df84f0c6b Mon Sep 17 00:00:00 2001 From: jidan745le <420511176@qq.com> Date: Sun, 5 Apr 2026 22:16:52 -0800 Subject: [PATCH 11/29] feat(renderer): migrate ApiOptions/CustomHeader/CherryIN/DMXAPI to v2 DataApi Signed-off-by: jidan745le <420511176@qq.com> --- .../ApiOptionsSettings/ApiOptionsSettings.tsx | 71 ++++++++----------- .../ProviderSettings/CherryINSettings.tsx | 46 +++++++----- .../ProviderSettings/CustomHeaderPopup.tsx | 27 +++---- .../ProviderSettings/DMXAPISettings.tsx | 51 +++++++------ 4 files changed, 98 insertions(+), 97 deletions(-) diff --git a/src/renderer/src/pages/settings/ProviderSettings/ApiOptionsSettings/ApiOptionsSettings.tsx b/src/renderer/src/pages/settings/ProviderSettings/ApiOptionsSettings/ApiOptionsSettings.tsx index 9fb02ec65c4..5759a9c8bf8 100644 --- a/src/renderer/src/pages/settings/ProviderSettings/ApiOptionsSettings/ApiOptionsSettings.tsx +++ b/src/renderer/src/pages/settings/ProviderSettings/ApiOptionsSettings/ApiOptionsSettings.tsx @@ -1,8 +1,7 @@ import { ColFlex, RowFlex, Switch } from '@cherrystudio/ui' import { InfoTooltip } from '@cherrystudio/ui' -import { useProvider } from '@renderer/hooks/useProvider' -import { type AnthropicCacheControlSettings, type Provider } from '@renderer/types' -import { isSupportAnthropicPromptCacheProvider } from '@renderer/utils/provider' +import { useProvider } from '@renderer/data/hooks/useProviders' +import { isAnthropicProvider, isAzureOpenAIProvider, isOpenAICompatibleProvider } from '@renderer/utils/provider.v2' import { Divider, InputNumber } from 'antd' import { startTransition, useCallback, useMemo } from 'react' import { useTranslation } from 'react-i18next' @@ -23,8 +22,8 @@ const ApiOptionsSettings = ({ providerId }: Props) => { const { t } = useTranslation() const { provider, updateProvider } = useProvider(providerId) - const updateProviderTransition = useCallback( - (updates: Partial) => { + const patchProvider = useCallback( + (updates: Record) => { startTransition(() => { updateProvider(updates) }) @@ -39,58 +38,48 @@ const ApiOptionsSettings = ({ providerId }: Props) => { label: t('settings.provider.api.options.developer_role.label'), tip: t('settings.provider.api.options.developer_role.help'), onChange: (checked: boolean) => { - updateProviderTransition({ - apiOptions: { ...provider.apiOptions, isSupportDeveloperRole: checked } - }) + patchProvider({ apiFeatures: { ...provider?.apiFeatures, developerRole: checked } }) }, - checked: !!provider.apiOptions?.isSupportDeveloperRole + checked: provider?.apiFeatures.developerRole ?? false }, { key: 'openai_stream_options', label: t('settings.provider.api.options.stream_options.label'), tip: t('settings.provider.api.options.stream_options.help'), onChange: (checked: boolean) => { - updateProviderTransition({ - apiOptions: { ...provider.apiOptions, isNotSupportStreamOptions: !checked } - }) + patchProvider({ apiFeatures: { ...provider?.apiFeatures, streamOptions: checked } }) }, - checked: !provider.apiOptions?.isNotSupportStreamOptions + checked: provider?.apiFeatures.streamOptions ?? true }, { key: 'openai_service_tier', label: t('settings.provider.api.options.service_tier.label'), tip: t('settings.provider.api.options.service_tier.help'), onChange: (checked: boolean) => { - updateProviderTransition({ - apiOptions: { ...provider.apiOptions, isSupportServiceTier: checked } - }) + patchProvider({ apiFeatures: { ...provider?.apiFeatures, serviceTier: checked } }) }, - checked: !!provider.apiOptions?.isSupportServiceTier + checked: provider?.apiFeatures.serviceTier ?? false }, { key: 'openai_enable_thinking', label: t('settings.provider.api.options.enable_thinking.label'), tip: t('settings.provider.api.options.enable_thinking.help'), onChange: (checked: boolean) => { - updateProviderTransition({ - apiOptions: { ...provider.apiOptions, isNotSupportEnableThinking: !checked } - }) + patchProvider({ apiFeatures: { ...provider?.apiFeatures, enableThinking: checked } }) }, - checked: !provider.apiOptions?.isNotSupportEnableThinking + checked: provider?.apiFeatures.enableThinking ?? true }, { key: 'openai_verbosity', label: t('settings.provider.api.options.verbosity.label'), tip: t('settings.provider.api.options.verbosity.help'), onChange: (checked: boolean) => { - updateProviderTransition({ - apiOptions: { ...provider.apiOptions, isNotSupportVerbosity: !checked } - }) + patchProvider({ apiFeatures: { ...provider?.apiFeatures, verbosity: checked } }) }, - checked: !provider.apiOptions?.isNotSupportVerbosity + checked: provider?.apiFeatures.verbosity ?? false } ], - [t, provider, updateProviderTransition] + [t, provider, patchProvider] ) const options = useMemo(() => { @@ -100,40 +89,42 @@ const ApiOptionsSettings = ({ providerId }: Props) => { label: t('settings.provider.api.options.array_content.label'), tip: t('settings.provider.api.options.array_content.help'), onChange: (checked: boolean) => { - updateProviderTransition({ - apiOptions: { ...provider.apiOptions, isNotSupportArrayContent: !checked } - }) + patchProvider({ apiFeatures: { ...provider?.apiFeatures, arrayContent: checked } }) }, - checked: !provider.apiOptions?.isNotSupportArrayContent + checked: provider?.apiFeatures.arrayContent ?? true } ] - if (provider.type === 'openai' || provider.type === 'openai-response' || provider.type === 'azure-openai') { + if (provider && (isOpenAICompatibleProvider(provider) || isAzureOpenAIProvider(provider))) { items.push(...openAIOptions) } return items - }, [openAIOptions, provider.apiOptions, provider.type, t, updateProviderTransition]) + }, [openAIOptions, provider, t, patchProvider]) - const isSupportAnthropicPromptCache = isSupportAnthropicPromptCacheProvider(provider) + const isSupportAnthropicPromptCache = provider ? isAnthropicProvider(provider) : false const cacheSettings = useMemo( () => - provider.anthropicCacheControl ?? { + provider?.settings?.cacheControl ?? { + enabled: false, tokenThreshold: 0, cacheSystemMessage: true, cacheLastNMessages: 0 }, - [provider.anthropicCacheControl] + [provider?.settings?.cacheControl] ) const updateCacheSettings = useCallback( - (updates: Partial) => { - updateProviderTransition({ - anthropicCacheControl: { ...cacheSettings, ...updates } + (updates: Partial) => { + patchProvider({ + providerSettings: { + ...provider?.settings, + cacheControl: { ...cacheSettings, enabled: true, ...updates } + } }) }, - [cacheSettings, updateProviderTransition] + [cacheSettings, provider?.settings, patchProvider] ) return ( @@ -166,7 +157,7 @@ const ApiOptionsSettings = ({ providerId }: Props) => { style={{ width: 100 }} /> - {cacheSettings.tokenThreshold > 0 && ( + {(cacheSettings.tokenThreshold ?? 0) > 0 && ( <> diff --git a/src/renderer/src/pages/settings/ProviderSettings/CherryINSettings.tsx b/src/renderer/src/pages/settings/ProviderSettings/CherryINSettings.tsx index fe438c41205..e4d51895ff9 100644 --- a/src/renderer/src/pages/settings/ProviderSettings/CherryINSettings.tsx +++ b/src/renderer/src/pages/settings/ProviderSettings/CherryINSettings.tsx @@ -1,46 +1,58 @@ -import { useProvider } from '@renderer/hooks/useProvider' +import { useProvider } from '@renderer/data/hooks/useProviders' +import { replaceBaseUrlDomain } from '@renderer/utils/provider.v2' import { Select } from 'antd' import type { FC } from 'react' import { useCallback, useMemo } from 'react' +import { useTranslation } from 'react-i18next' interface CherryINSettingsProps { providerId: string - apiHost: string - setApiHost: (host: string) => void } const API_HOST_OPTIONS = [ { - value: 'https://open.cherryin.cc', + value: 'open.cherryin.cc', labelKey: '加速域名', description: 'open.cherryin.cc' }, { - value: 'https://open.cherryin.net', + value: 'open.cherryin.net', labelKey: '国际域名', description: 'open.cherryin.net' }, { - value: 'https://open.cherryin.ai', + value: 'open.cherryin.ai', labelKey: '备用域名', description: 'open.cherryin.ai' } ] -const CherryINSettings: FC = ({ providerId, apiHost, setApiHost }) => { - const { updateProvider } = useProvider(providerId) +const CherryINSettings: FC = ({ providerId }) => { + const { provider, updateProvider } = useProvider(providerId) + const { t } = useTranslation() + + const currentDomain = useMemo(() => { + if (!provider?.baseUrls) return API_HOST_OPTIONS[0].value + const firstUrl = Object.values(provider.baseUrls)[0] + if (!firstUrl) return API_HOST_OPTIONS[0].value + try { + return new URL(firstUrl).hostname + } catch { + return API_HOST_OPTIONS[0].value + } + }, [provider?.baseUrls]) const getCurrentHost = useMemo(() => { - const matchedOption = API_HOST_OPTIONS.find((option) => apiHost?.includes(option.value.replace('https://', ''))) - return matchedOption?.value ?? API_HOST_OPTIONS[0].value - }, [apiHost]) + const matched = API_HOST_OPTIONS.find((option) => currentDomain.includes(option.value)) + return matched?.value ?? API_HOST_OPTIONS[0].value + }, [currentDomain]) const handleHostChange = useCallback( - (value: string) => { - setApiHost(value) - updateProvider({ apiHost: value, anthropicApiHost: value }) + async (value: string) => { + const newBaseUrls = replaceBaseUrlDomain(provider?.baseUrls, value) + await updateProvider({ baseUrls: newBaseUrls }) }, - [setApiHost, updateProvider] + [provider?.baseUrls, updateProvider] ) const options = useMemo( @@ -50,11 +62,11 @@ const CherryINSettings: FC = ({ providerId, apiHost, setA label: (
{option.labelKey} - {option.description} + {t(option.description)}
) })), - [] + [t] ) return ( diff --git a/src/renderer/src/pages/settings/ProviderSettings/CustomHeaderPopup.tsx b/src/renderer/src/pages/settings/ProviderSettings/CustomHeaderPopup.tsx index 8fe01ea7760..bbb9accc660 100644 --- a/src/renderer/src/pages/settings/ProviderSettings/CustomHeaderPopup.tsx +++ b/src/renderer/src/pages/settings/ProviderSettings/CustomHeaderPopup.tsx @@ -2,9 +2,8 @@ import { CodeEditor } from '@cherrystudio/ui' import { usePreference } from '@data/hooks/usePreference' import { TopView } from '@renderer/components/TopView' import { useCodeStyle } from '@renderer/context/CodeStyleProvider' +import { useProvider } from '@renderer/data/hooks/useProviders' import { useCopilot } from '@renderer/hooks/useCopilot' -import { useProvider } from '@renderer/hooks/useProvider' -import type { Provider } from '@renderer/types' import { Modal, Space } from 'antd' import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' @@ -12,43 +11,45 @@ import { useTranslation } from 'react-i18next' import { SettingHelpText } from '..' interface ShowParams { - provider: Provider + providerId: string } interface Props extends ShowParams { resolve: (data: any) => void } -const PopupContainer: React.FC = ({ provider, resolve }) => { +const PopupContainer: React.FC = ({ providerId, resolve }) => { const [open, setOpen] = useState(true) const { t } = useTranslation() - const { updateProvider } = useProvider(provider.id) + const { provider, updateProvider } = useProvider(providerId) const { defaultHeaders, updateDefaultHeaders } = useCopilot() const [fontSize] = usePreference('chat.message.font_size') const { activeCmTheme } = useCodeStyle() const headers = - provider.id === 'copilot' + providerId === 'copilot' ? JSON.stringify(defaultHeaders || {}, null, 2) - : JSON.stringify(provider.extra_headers || {}, null, 2) + : JSON.stringify(provider?.settings?.extraHeaders || {}, null, 2) const [headerText, setHeaderText] = useState(headers) const onUpdateHeaders = useCallback(() => { try { - const headers = headerText.trim() ? JSON.parse(headerText) : {} + const parsedHeaders = headerText.trim() ? JSON.parse(headerText) : {} - if (provider.id === 'copilot') { - updateDefaultHeaders(headers) - } else { - updateProvider({ ...provider, extra_headers: headers }) + // Copilot: dual-write to v2 providerSettings AND copilot Redux + // (aiCore still reads copilot Redux until Phase 5B) + if (providerId === 'copilot') { + updateDefaultHeaders(parsedHeaders) } + updateProvider({ providerSettings: { ...provider?.settings, extraHeaders: parsedHeaders } }) + window.toast.success(t('message.save.success.title')) } catch (error) { window.toast.error(t('settings.provider.copilot.invalid_json')) } - }, [headerText, provider, t, updateDefaultHeaders, updateProvider]) + }, [headerText, providerId, provider?.settings, t, updateDefaultHeaders, updateProvider]) const onOk = () => { onUpdateHeaders() diff --git a/src/renderer/src/pages/settings/ProviderSettings/DMXAPISettings.tsx b/src/renderer/src/pages/settings/ProviderSettings/DMXAPISettings.tsx index e3064b93781..7928d248d89 100644 --- a/src/renderer/src/pages/settings/ProviderSettings/DMXAPISettings.tsx +++ b/src/renderer/src/pages/settings/ProviderSettings/DMXAPISettings.tsx @@ -1,5 +1,6 @@ import { Dmxapi } from '@cherrystudio/ui/icons' -import { useProvider } from '@renderer/hooks/useProvider' +import { useProvider } from '@renderer/data/hooks/useProviders' +import { replaceBaseUrlDomain } from '@renderer/utils/provider.v2' import type { RadioChangeEvent } from 'antd' import { Radio, Space } from 'antd' import type { FC } from 'react' @@ -13,58 +14,55 @@ interface DMXAPISettingsProps { providerId: string } -// DMXAPI平台选项 -enum PlatformType { - OFFICIAL = 'https://www.DMXAPI.cn', - INTERNATIONAL = 'https://www.DMXAPI.com', - OVERSEA = 'https://ssvip.DMXAPI.com' +// DMXAPI platform domain options +enum PlatformDomain { + OFFICIAL = 'www.DMXAPI.cn', + INTERNATIONAL = 'www.DMXAPI.com', + OVERSEA = 'ssvip.DMXAPI.com' } const DMXAPISettings: FC = ({ providerId }) => { const { provider, updateProvider } = useProvider(providerId) - const { t } = useTranslation() const PlatformOptions = [ { label: t('settings.provider.dmxapi.platform_official'), - value: PlatformType.OFFICIAL, + value: PlatformDomain.OFFICIAL, apiKeyWebsite: 'https://www.dmxapi.cn/register?aff=bwwY' }, { label: t('settings.provider.dmxapi.platform_international'), - value: PlatformType.INTERNATIONAL, + value: PlatformDomain.INTERNATIONAL, apiKeyWebsite: 'https://www.dmxapi.com/register' }, { label: t('settings.provider.dmxapi.platform_enterprise'), - value: PlatformType.OVERSEA, + value: PlatformDomain.OVERSEA, apiKeyWebsite: 'https://ssvip.dmxapi.com/register' } ] - // 获取当前选中的平台,如果没有设置则默认为官方平台 - const getCurrentPlatform = (): PlatformType => { - if (!provider.apiHost) return PlatformType.OFFICIAL - - if (provider.apiHost.includes('DMXAPI.com')) { - return provider.apiHost.includes('ssvip') ? PlatformType.OVERSEA : PlatformType.INTERNATIONAL + const getCurrentPlatform = (): PlatformDomain => { + if (!provider?.baseUrls) return PlatformDomain.OFFICIAL + const firstUrl = Object.values(provider.baseUrls)[0] + if (!firstUrl) return PlatformDomain.OFFICIAL + if (firstUrl.includes('DMXAPI.com') || firstUrl.includes('dmxapi.com')) { + return firstUrl.includes('ssvip') ? PlatformDomain.OVERSEA : PlatformDomain.INTERNATIONAL } - - return PlatformType.OFFICIAL + return PlatformDomain.OFFICIAL } - // 状态管理 - const [selectedPlatform, setSelectedPlatform] = useState(getCurrentPlatform()) + const [selectedPlatform, setSelectedPlatform] = useState(getCurrentPlatform()) - // 处理平台选择变更 const handlePlatformChange = useCallback( - (e: RadioChangeEvent) => { - const platform = e.target.value as PlatformType - setSelectedPlatform(platform) - updateProvider({ ...provider, apiHost: platform }) + async (e: RadioChangeEvent) => { + const domain = e.target.value as PlatformDomain + setSelectedPlatform(domain) + const newBaseUrls = replaceBaseUrlDomain(provider?.baseUrls, domain) + await updateProvider({ baseUrls: newBaseUrls }) }, - [provider, updateProvider] + [provider?.baseUrls, updateProvider] ) return ( @@ -99,7 +97,6 @@ const DMXAPISettings: FC = ({ providerId }) => { ) } -// 样式组件 const Container = styled.div` margin-top: 16px; margin-bottom: 30px; From 006979cb9c6b31682cbf7be49ee4d4b28e24b373 Mon Sep 17 00:00:00 2001 From: jidan745le <420511176@qq.com> Date: Sun, 5 Apr 2026 22:17:05 -0800 Subject: [PATCH 12/29] feat(renderer): migrate OAuth/VertexAI/Bedrock/Copilot to v2 DataApi Signed-off-by: jidan745le <420511176@qq.com> --- .../ProviderSettings/AwsBedrockSettings.tsx | 95 +++++++++------- .../ProviderSettings/CherryINOAuth.tsx | 31 +++--- .../GithubCopilotSettings.tsx | 86 +++++++++------ .../ProviderSettings/ProviderOAuth.tsx | 17 +-- .../ProviderSettings/VertexAISettings.tsx | 103 ++++++++---------- 5 files changed, 184 insertions(+), 148 deletions(-) diff --git a/src/renderer/src/pages/settings/ProviderSettings/AwsBedrockSettings.tsx b/src/renderer/src/pages/settings/ProviderSettings/AwsBedrockSettings.tsx index 218979e6dcb..c4216696681 100644 --- a/src/renderer/src/pages/settings/ProviderSettings/AwsBedrockSettings.tsx +++ b/src/renderer/src/pages/settings/ProviderSettings/AwsBedrockSettings.tsx @@ -1,35 +1,62 @@ import { RowFlex } from '@cherrystudio/ui' import { PROVIDER_URLS } from '@renderer/config/providers' -import { useAwsBedrockSettings } from '@renderer/hooks/useAwsBedrock' +import { useProvider, useProviderAuthConfig } from '@renderer/data/hooks/useProviders' import { Alert, Input, Radio } from 'antd' import type { FC } from 'react' -import { useState } from 'react' +import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import { SettingHelpLink, SettingHelpText, SettingHelpTextRow, SettingSubtitle } from '..' -const AwsBedrockSettings: FC = () => { +interface Props { + providerId: string +} + +const AwsBedrockSettings: FC = ({ providerId }) => { const { t } = useTranslation() - const { - authType, - accessKeyId, - secretAccessKey, - apiKey, - region, - setAuthType, - setAccessKeyId, - setSecretAccessKey, - setApiKey, - setRegion - } = useAwsBedrockSettings() + const { provider, updateAuthConfig } = useProvider(providerId) + const { data: authConfig } = useProviderAuthConfig(providerId) + + const isIamMode = provider?.authType === 'iam-aws' + const awsConfig = authConfig?.type === 'iam-aws' ? authConfig : null const providerConfig = PROVIDER_URLS['aws-bedrock'] const apiKeyWebsite = providerConfig?.websites?.apiKey - const [localAccessKeyId, setLocalAccessKeyId] = useState(accessKeyId) - const [localSecretAccessKey, setLocalSecretAccessKey] = useState(secretAccessKey) - const [localApiKey, setLocalApiKey] = useState(apiKey) - const [localRegion, setLocalRegion] = useState(region) + const [localAccessKeyId, setLocalAccessKeyId] = useState(awsConfig?.accessKeyId ?? '') + const [localSecretAccessKey, setLocalSecretAccessKey] = useState(awsConfig?.secretAccessKey ?? '') + const [localRegion, setLocalRegion] = useState(awsConfig?.region ?? '') + + useEffect(() => { + if (awsConfig) { + setLocalAccessKeyId(awsConfig.accessKeyId ?? '') + setLocalSecretAccessKey(awsConfig.secretAccessKey ?? '') + setLocalRegion(awsConfig.region ?? '') + } + }, [authConfig]) + + const handleAuthTypeChange = async (value: string) => { + if (value === 'iam') { + await updateAuthConfig({ type: 'iam-aws', region: localRegion || 'us-east-1' }) + } else { + await updateAuthConfig({ type: 'api-key' }) + } + } + + const saveIamConfig = async () => { + await updateAuthConfig({ + type: 'iam-aws' as const, + region: localRegion, + accessKeyId: localAccessKeyId, + secretAccessKey: localSecretAccessKey + }) + } + + const saveRegion = async () => { + if (isIamMode) { + await saveIamConfig() + } + } return ( <> @@ -38,7 +65,10 @@ const AwsBedrockSettings: FC = () => { {/* Authentication Type Selector */} {t('settings.provider.aws-bedrock.auth_type')} - setAuthType(e.target.value)} style={{ marginTop: 5 }}> + handleAuthTypeChange(e.target.value)} + style={{ marginTop: 5 }}> {t('settings.provider.aws-bedrock.auth_type_iam')} {t('settings.provider.aws-bedrock.auth_type_api_key')} @@ -47,7 +77,7 @@ const AwsBedrockSettings: FC = () => { {/* IAM Credentials Fields */} - {authType === 'iam' && ( + {isIamMode && ( <> {t('settings.provider.aws-bedrock.access_key_id')} @@ -56,7 +86,7 @@ const AwsBedrockSettings: FC = () => { value={localAccessKeyId} placeholder={t('settings.provider.aws-bedrock.access_key_id')} onChange={(e) => setLocalAccessKeyId(e.target.value)} - onBlur={() => setAccessKeyId(localAccessKeyId)} + onBlur={saveIamConfig} style={{ marginTop: 5 }} /> @@ -70,7 +100,7 @@ const AwsBedrockSettings: FC = () => { value={localSecretAccessKey} placeholder={t('settings.provider.aws-bedrock.secret_access_key')} onChange={(e) => setLocalSecretAccessKey(e.target.value)} - onBlur={() => setSecretAccessKey(localSecretAccessKey)} + onBlur={saveIamConfig} style={{ marginTop: 5 }} spellCheck={false} /> @@ -87,29 +117,12 @@ const AwsBedrockSettings: FC = () => { )} - {authType === 'apiKey' && ( - <> - {t('settings.provider.aws-bedrock.api_key')} - setLocalApiKey(e.target.value)} - onBlur={() => setApiKey(localApiKey)} - style={{ marginTop: 5 }} - spellCheck={false} - /> - - {t('settings.provider.aws-bedrock.api_key_help')} - - - )} - {t('settings.provider.aws-bedrock.region')} setLocalRegion(e.target.value)} - onBlur={() => setRegion(localRegion)} + onBlur={saveRegion} style={{ marginTop: 5 }} /> diff --git a/src/renderer/src/pages/settings/ProviderSettings/CherryINOAuth.tsx b/src/renderer/src/pages/settings/ProviderSettings/CherryINOAuth.tsx index ceab42aa107..d3c4a8ba965 100644 --- a/src/renderer/src/pages/settings/ProviderSettings/CherryINOAuth.tsx +++ b/src/renderer/src/pages/settings/ProviderSettings/CherryINOAuth.tsx @@ -1,9 +1,9 @@ import { Cherryin } from '@cherrystudio/ui/icons' import { loggerService } from '@logger' -import { useProvider } from '@renderer/hooks/useProvider' +import { useProvider } from '@renderer/data/hooks/useProviders' import { oauthWithCherryIn } from '@renderer/utils/oauth' +import { hasApiKeys } from '@renderer/utils/provider.v2' import { Button, Skeleton } from 'antd' -import { isEmpty } from 'lodash' import { CreditCard, LogIn, LogOut, RefreshCw } from 'lucide-react' import type { FC } from 'react' import { useCallback, useEffect, useState } from 'react' @@ -34,7 +34,7 @@ interface CherryINOAuthProps { } const CherryINOAuth: FC = ({ providerId }) => { - const { updateProvider, provider } = useProvider(providerId) + const { provider, updateProvider, addApiKey, deleteApiKey } = useProvider(providerId) const { t } = useTranslation() const [isLoggingOut, setIsLoggingOut] = useState(false) @@ -42,9 +42,9 @@ const CherryINOAuth: FC = ({ providerId }) => { const [balanceInfo, setBalanceInfo] = useState(null) const [hasOAuthToken, setHasOAuthToken] = useState(null) - const hasApiKey = !isEmpty(provider.apiKey) + const hasKeys = provider ? hasApiKeys(provider) : false // User is considered logged in via OAuth only if they have both API key and OAuth token - const isOAuthLoggedIn = hasApiKey && hasOAuthToken === true + const isOAuthLoggedIn = hasKeys && hasOAuthToken === true const fetchData = useCallback(async () => { setIsLoadingData(true) @@ -74,7 +74,7 @@ const CherryINOAuth: FC = ({ providerId }) => { useEffect(() => { // Only fetch balance if logged in via OAuth if (isOAuthLoggedIn) { - void fetchData() + fetchData() } else { setBalanceInfo(null) } @@ -83,8 +83,10 @@ const CherryINOAuth: FC = ({ providerId }) => { const handleOAuthLogin = useCallback(async () => { try { await oauthWithCherryIn( - (apiKeys: string) => { - updateProvider({ apiKey: apiKeys, enabled: true }) + async (apiKeys: string) => { + // POST each key to /api-keys endpoint + await addApiKey(apiKeys, 'OAuth') + await updateProvider({ isEnabled: true }) setHasOAuthToken(true) window.toast.success(t('auth.get_key_success')) }, @@ -96,7 +98,7 @@ const CherryINOAuth: FC = ({ providerId }) => { logger.error('OAuth Error:', error as Error) window.toast.error(t('settings.provider.oauth.error')) } - }, [updateProvider, t]) + }, [addApiKey, updateProvider, t]) const handleLogout = useCallback(() => { window.modal.confirm({ @@ -108,14 +110,17 @@ const CherryINOAuth: FC = ({ providerId }) => { try { await window.api.cherryin.logout(CHERRYIN_OAUTH_SERVER) - updateProvider({ apiKey: '' }) + // Delete OAuth key by label + const oauthKey = provider?.apiKeys.find((k) => k.label === 'OAuth') + if (oauthKey) { + await deleteApiKey(oauthKey.id) + } setHasOAuthToken(false) setBalanceInfo(null) window.toast.success(t('settings.provider.oauth.logout_success')) } catch (error) { logger.error('Logout error:', error as Error) // Still clear local state even if server revocation failed - updateProvider({ apiKey: '' }) setHasOAuthToken(false) setBalanceInfo(null) window.toast.warning(t('settings.provider.oauth.logout_warning')) @@ -124,7 +129,7 @@ const CherryINOAuth: FC = ({ providerId }) => { } } }) - }, [updateProvider, t]) + }, [provider?.apiKeys, deleteApiKey, t]) const handleTopup = useCallback(() => { window.open(CHERRYIN_TOPUP_URL, '_blank') @@ -135,7 +140,7 @@ const CherryINOAuth: FC = ({ providerId }) => { // 2. Has API key + OAuth token → Show logged-in UI // 3. Has API key + No OAuth token (legacy manual key) → Show connect button to upgrade to OAuth const renderContent = () => { - if (!hasApiKey) { + if (!hasKeys) { // Case 1: No API key - show login button return ( - + {selectedProvider && }
) } @@ -467,6 +450,7 @@ const ProviderListContainer = styled.div` display: flex; flex-direction: column; min-width: calc(var(--settings-width) + 10px); + height: calc(100vh - var(--navbar-height)); padding-bottom: 5px; border-right: 0.5px solid var(--color-border); ` diff --git a/src/renderer/src/pages/settings/ProviderSettings/ProviderSetting.tsx b/src/renderer/src/pages/settings/ProviderSettings/ProviderSetting.tsx index f788d4be957..7ef40d9125f 100644 --- a/src/renderer/src/pages/settings/ProviderSettings/ProviderSetting.tsx +++ b/src/renderer/src/pages/settings/ProviderSettings/ProviderSetting.tsx @@ -1,15 +1,16 @@ import { Button, Flex, RowFlex, Switch, Tooltip, WarnTooltip } from '@cherrystudio/ui' import { HelpTooltip } from '@cherrystudio/ui' +import { useModels } from '@data/hooks/useModels' +import { useProvider, useProviderApiKeys, useProviderMutations } from '@data/hooks/useProviders' import { adaptProvider } from '@renderer/aiCore/provider/providerConfig' import OpenAIAlert from '@renderer/components/Alert/OpenAIAlert' -import { showErrorDetailPopup } from '@renderer/components/ErrorDetailModal' +import { ErrorDetailModal } from '@renderer/components/ErrorDetailModal' import { LoadingIcon } from '@renderer/components/Icons' import { ApiKeyListPopup } from '@renderer/components/Popups/ApiKeyListPopup' import Selector from '@renderer/components/Selector' import { isRerankModel } from '@renderer/config/models' import { PROVIDER_URLS } from '@renderer/config/providers' import { useTheme } from '@renderer/context/ThemeProvider' -import { useAllProviders, useProvider, useProviders } from '@renderer/hooks/useProvider' import { useTimer } from '@renderer/hooks/useTimer' import AnthropicSettings from '@renderer/pages/settings/ProviderSettings/AnthropicSettings' import { ModelList } from '@renderer/pages/settings/ProviderSettings/ModelList' @@ -17,30 +18,34 @@ import { checkApi } from '@renderer/services/ApiService' import { isProviderSupportAuth } from '@renderer/services/ProviderService' import { useAppDispatch } from '@renderer/store' import { updateWebSearchProvider } from '@renderer/store/websearch' -import type { SystemProviderId } from '@renderer/types' -import { isSystemProvider, isSystemProviderId, SystemProviderIds } from '@renderer/types' +import type { Model as V1Model, SystemProviderId } from '@renderer/types' +import { isSystemProviderId, SystemProviderIds } from '@renderer/types' import type { ApiKeyConnectivity } from '@renderer/types/healthCheck' import { HealthStatus } from '@renderer/types/healthCheck' -import { formatApiHost, formatApiKeys, getFancyProviderName, validateApiHost } from '@renderer/utils' +import { formatApiHost, formatApiKeys, validateApiHost } from '@renderer/utils' import { serializeHealthCheckError } from '@renderer/utils/error' import { - isAIGatewayProvider, + getFancyProviderName, isAnthropicProvider, + isAnthropicSupportedProvider, isAzureOpenAIProvider, isGeminiProvider, isNewApiProvider, isOllamaProvider, isOpenAICompatibleProvider, - isOpenAIProvider, - isSupportAnthropicPromptCacheProvider, + isOpenAIResponsesProvider, + isSystemProvider, isVertexProvider -} from '@renderer/utils/provider' +} from '@renderer/utils/provider.v2' +import { toV1ProviderShim } from '@renderer/utils/v1ProviderShim' +import { EndpointType } from '@shared/data/types/model' +import type { Provider } from '@shared/data/types/provider' import { Divider, Input, Select, Space } from 'antd' import Link from 'antd/es/typography/Link' import { debounce, isEmpty } from 'lodash' import { Bolt, Check, Settings2, SquareArrowOutUpRight } from 'lucide-react' import type { FC } from 'react' -import { useCallback, useEffect, useMemo, useRef, useState } from 'react' +import { useCallback, useEffect, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import styled from 'styled-components' @@ -68,8 +73,6 @@ import VertexAISettings from './VertexAISettings' interface Props { providerId: string - /** Whether in onboarding mode for new users */ - isOnboarding?: boolean } const ANTHROPIC_COMPATIBLE_PROVIDER_IDS = [ @@ -88,8 +91,7 @@ const ANTHROPIC_COMPATIBLE_PROVIDER_IDS = [ SystemProviderIds.dmxapi, SystemProviderIds.mimo, SystemProviderIds.openrouter, - SystemProviderIds.tokenflux, - SystemProviderIds.ollama + SystemProviderIds.tokenflux ] as const type AnthropicCompatibleProviderId = (typeof ANTHROPIC_COMPATIBLE_PROVIDER_IDS)[number] @@ -100,13 +102,38 @@ const isAnthropicCompatibleProviderId = (id: string): id is AnthropicCompatibleP type HostField = 'apiHost' | 'anthropicApiHost' -const ProviderSetting: FC = ({ providerId, isOnboarding = false }) => { - const { provider, updateProvider, models } = useProvider(providerId) - const allProviders = useAllProviders() - const { updateProviders } = useProviders() - const [apiHost, setApiHost] = useState(provider.apiHost) - const [anthropicApiHost, setAnthropicHost] = useState(provider.anthropicApiHost) - const [apiVersion, setApiVersion] = useState(provider.apiVersion) +const ProviderSetting: FC = ({ providerId }) => { + const { provider } = useProvider(providerId) + if (!provider) return null + return +} + +interface ContentProps { + provider: Provider + providerId: string +} + +const ProviderSettingContent: FC = ({ provider, providerId }) => { + const { updateProvider, updateApiKeys } = useProviderMutations(providerId) + const { models } = useModels({ providerId }) + const { data: apiKeysData } = useProviderApiKeys(providerId) + const patchProvider = useCallback( + async (updates: Record) => { + await updateProvider(updates) + }, + [updateProvider] + ) + + // Derive v1-like fields from v2 Provider + const primaryEndpoint = provider.defaultChatEndpoint ?? EndpointType.OPENAI_CHAT_COMPLETIONS + const providerApiHost = provider.baseUrls?.[primaryEndpoint] ?? '' + const providerAnthropicHost = provider.baseUrls?.[EndpointType.ANTHROPIC_MESSAGES] + const providerApiVersion = provider.settings?.apiVersion ?? '' + const providerApiKey = apiKeysData?.keys?.map((k) => k.key).join(',') ?? '' + + const [apiHost, setApiHost] = useState(providerApiHost) + const [anthropicApiHost, setAnthropicHost] = useState(providerAnthropicHost) + const [apiVersion, setApiVersion] = useState(providerApiVersion) const [activeHostField, setActiveHostField] = useState('apiHost') const { t, i18n } = useTranslation() const { theme } = useTheme() @@ -122,18 +149,20 @@ const ProviderSetting: FC = ({ providerId, isOnboarding = false }) => { const noAPIKeyInputProviders = ['copilot', 'vertexai'] as const satisfies SystemProviderId[] const hideApiKeyInput = noAPIKeyInputProviders.some((id) => id === provider.id) - const providerConfig = PROVIDER_URLS[provider.id] - const officialWebsite = providerConfig?.websites?.official - const apiKeyWebsite = providerConfig?.websites?.apiKey + // Use v2 provider.websites first, fallback to static config + const providerConfig = PROVIDER_URLS[provider.id as keyof typeof PROVIDER_URLS] + const officialWebsite = provider.websites?.official ?? providerConfig?.websites?.official + const apiKeyWebsite = provider.websites?.apiKey ?? providerConfig?.websites?.apiKey const configuredApiHost = providerConfig?.api?.url const fancyProviderName = getFancyProviderName(provider) - const [localApiKey, setLocalApiKey] = useState(provider.apiKey) + const [localApiKey, setLocalApiKey] = useState(providerApiKey) const [apiKeyConnectivity, setApiKeyConnectivity] = useState({ status: HealthStatus.NOT_CHECKED, checking: false }) + const [showErrorModal, setShowErrorModal] = useState(false) const updateWebSearchProviderKey = useCallback( ({ apiKey }: { apiKey: string }) => { @@ -142,92 +171,53 @@ const ProviderSetting: FC = ({ providerId, isOnboarding = false }) => { [dispatch, provider.id] ) - // Store callbacks in ref to avoid recreating debounce function when dependencies change - const callbacks = { updateProvider, updateWebSearchProviderKey, isOnboarding, providerEnabled: provider.enabled } - const callbacksRef = useRef(callbacks) - callbacksRef.current = callbacks - const debouncedUpdateApiKey = useMemo( () => - debounce((value: string) => { - const { updateProvider, updateWebSearchProviderKey, isOnboarding, providerEnabled } = callbacksRef.current - const formattedKey = formatApiKeys(value) - updateProvider({ apiKey: formattedKey }) - updateWebSearchProviderKey({ apiKey: formattedKey }) - // Auto-enable provider when apiKey is updated in onboarding mode - if (isOnboarding && formattedKey && !providerEnabled) { - updateProvider({ enabled: true }) - } + debounce(async (value: string) => { + const formatted = formatApiKeys(value) + const keys = formatted.split(',').filter(Boolean) + const apiKeys = keys.map((key) => ({ id: crypto.randomUUID(), key, isEnabled: true })) + await updateApiKeys(apiKeys) + updateWebSearchProviderKey({ apiKey: formatted }) }, 150), - [] + [updateApiKeys, updateWebSearchProviderKey] ) - // Track whether update comes from external source to avoid loops - const isExternalUpdateRef = useRef(false) - - // Sync provider.apiKey to localApiKey and reset connectivity status + // 同步 provider apiKey 到 localApiKey + // 重置连通性检查状态 useEffect(() => { - // Cancel any pending debounce calls to prevent old values from overwriting new ones - debouncedUpdateApiKey.cancel() - isExternalUpdateRef.current = true - setLocalApiKey(provider.apiKey) + setLocalApiKey(providerApiKey) setApiKeyConnectivity({ status: HealthStatus.NOT_CHECKED }) - }, [provider.apiKey, debouncedUpdateApiKey]) + }, [providerApiKey]) - // Sync localApiKey to provider.apiKey (debounced) - // Only trigger on user input, not on external updates + // 同步 localApiKey 到 provider(防抖) useEffect(() => { - if (isExternalUpdateRef.current) { - isExternalUpdateRef.current = false - return - } - if (localApiKey !== provider.apiKey) { + if (localApiKey !== providerApiKey) { debouncedUpdateApiKey(localApiKey) } - }, [localApiKey, provider.apiKey, debouncedUpdateApiKey]) - // Flush pending updates on unmount to prevent data loss - useEffect(() => { - return () => { - debouncedUpdateApiKey.flush() - } - }, [debouncedUpdateApiKey]) + // 卸载时取消任何待执行的更新 + return () => debouncedUpdateApiKey.cancel() + }, [localApiKey, providerApiKey, debouncedUpdateApiKey]) const isApiKeyConnectable = useMemo(() => { return apiKeyConnectivity.status === 'success' }, [apiKeyConnectivity]) - const moveProviderToTop = useCallback( - (providerId: string) => { - const reorderedProviders = [...allProviders] - const index = reorderedProviders.findIndex((p) => p.id === providerId) - - if (index !== -1) { - const updatedProvider = { ...reorderedProviders[index], enabled: true } - reorderedProviders.splice(index, 1) - reorderedProviders.unshift(updatedProvider) - updateProviders(reorderedProviders) - } - }, - [allProviders, updateProviders] - ) + const moveProviderToTop = useCallback(async () => { + await updateProvider({ sortOrder: 0, isEnabled: true }) + }, [updateProvider]) const onUpdateApiHost = () => { if (!validateApiHost(apiHost)) { - setApiHost(provider.apiHost) + setApiHost(providerApiHost) window.toast.error(t('settings.provider.api_host_no_valid')) return } if (isVertexProvider(provider) || apiHost.trim()) { - // For new-api provider, keep apiHost and anthropicApiHost in sync - if (isNewApiProvider(provider)) { - updateProvider({ apiHost, anthropicApiHost: apiHost }) - setAnthropicHost(apiHost) - } else { - updateProvider({ apiHost }) - } + patchProvider({ baseUrls: { ...provider.baseUrls, [primaryEndpoint]: apiHost } }) } else { - setApiHost(provider.apiHost) + setApiHost(providerApiHost) } } @@ -235,19 +225,24 @@ const ProviderSetting: FC = ({ providerId, isOnboarding = false }) => { const trimmedHost = anthropicApiHost?.trim() if (trimmedHost) { - updateProvider({ anthropicApiHost: trimmedHost }) + patchProvider({ + baseUrls: { ...provider.baseUrls, [EndpointType.ANTHROPIC_MESSAGES]: trimmedHost } + }) setAnthropicHost(trimmedHost) } else { - updateProvider({ anthropicApiHost: undefined }) + const { [EndpointType.ANTHROPIC_MESSAGES]: _, ...restUrls } = provider.baseUrls ?? {} + patchProvider({ baseUrls: restUrls }) setAnthropicHost(undefined) } } - const onUpdateApiVersion = () => updateProvider({ apiVersion }) + const onUpdateApiVersion = () => patchProvider({ providerSettings: { ...provider.settings, apiVersion } }) const openApiKeyList = async () => { - if (localApiKey !== provider.apiKey) { - updateProvider({ apiKey: formatApiKeys(localApiKey) }) - await new Promise((resolve) => setTimeout(resolve, 0)) + if (localApiKey !== providerApiKey) { + const formatted = formatApiKeys(localApiKey) + const keys = formatted.split(',').filter(Boolean) + const apiKeys = keys.map((key) => ({ id: crypto.randomUUID(), key, isEnabled: true })) + await updateApiKeys(apiKeys) } await ApiKeyListPopup.show({ @@ -259,14 +254,13 @@ const ProviderSetting: FC = ({ providerId, isOnboarding = false }) => { const onCheckApi = async () => { const formattedLocalKey = formatApiKeys(localApiKey) - // 如果存在多个密钥,直接打开管理窗口 if (formattedLocalKey.includes(',')) { await openApiKeyList() return } - const modelsToCheck = models.filter((model) => !isRerankModel(model)) + const modelsToCheck = models.filter((model) => !isRerankModel(model as any)) if (isEmpty(modelsToCheck)) { window.toast.error({ @@ -276,7 +270,13 @@ const ProviderSetting: FC = ({ providerId, isOnboarding = false }) => { return } - const model = await SelectProviderModelPopup.show({ provider }) + // TODO(v2-cleanup): Remove v1 shim after SelectProviderModelPopup migrates to v2 + const v1ProviderForPopup = toV1ProviderShim(provider, { + models, + apiKey: formattedLocalKey, + apiHost + }) + const model = await SelectProviderModelPopup.show({ provider: v1ProviderForPopup }) if (!model) { window.toast.error(i18n.t('message.error.enter.model')) @@ -285,7 +285,13 @@ const ProviderSetting: FC = ({ providerId, isOnboarding = false }) => { try { setApiKeyConnectivity((prev) => ({ ...prev, checking: true, status: HealthStatus.NOT_CHECKED })) - await checkApi({ ...provider, apiHost, apiKey: formattedLocalKey }, model) + // TODO(v2-cleanup): Remove v1 shim after checkApi migrates to v2 + const v1ProviderForCheck = toV1ProviderShim(provider, { + models, + apiKey: formattedLocalKey, + apiHost + }) + await checkApi(v1ProviderForCheck, model as V1Model) window.toast.success({ timeout: 2000, @@ -293,12 +299,6 @@ const ProviderSetting: FC = ({ providerId, isOnboarding = false }) => { }) setApiKeyConnectivity((prev) => ({ ...prev, status: HealthStatus.SUCCESS })) - - // Auto-enable provider when API check succeeds in onboarding mode - if (isOnboarding && !provider.enabled) { - updateProvider({ enabled: true }) - } - setTimeoutTimer( 'onCheckApi', () => { @@ -322,15 +322,17 @@ const ProviderSetting: FC = ({ providerId, isOnboarding = false }) => { const onReset = useCallback(() => { setApiHost(configuredApiHost) - updateProvider({ apiHost: configuredApiHost }) - }, [configuredApiHost, updateProvider]) + patchProvider({ baseUrls: { ...provider?.baseUrls, [primaryEndpoint]: configuredApiHost } }) + }, [configuredApiHost, patchProvider, provider?.baseUrls, primaryEndpoint]) const isApiHostResettable = useMemo(() => { return !isEmpty(configuredApiHost) && apiHost !== configuredApiHost }, [configuredApiHost, apiHost]) const hostPreview = () => { - const formattedApiHost = adaptProvider({ provider: { ...provider, apiHost } }).apiHost + // TODO(v2-cleanup): Remove v1 shim after adaptProvider migrates to v2 + const v1ProviderForAdapt = toV1ProviderShim(provider, { apiHost }) + const formattedApiHost = adaptProvider({ provider: v1ProviderForAdapt }).apiHost if (isOllamaProvider(provider)) { return formattedApiHost + '/chat' @@ -341,8 +343,8 @@ const ProviderSetting: FC = ({ providerId, isOnboarding = false }) => { } if (isAzureOpenAIProvider(provider)) { - const apiVersion = provider.apiVersion || '' - const path = !['preview', 'v1'].includes(apiVersion) + const ver = provider.settings?.apiVersion || '' + const path = !['preview', 'v1'].includes(ver) ? `/v1/chat/completions?apiVersion=v1` : `/v1/responses?apiVersion=v1` return formattedApiHost + path @@ -355,15 +357,12 @@ const ProviderSetting: FC = ({ providerId, isOnboarding = false }) => { if (isGeminiProvider(provider)) { return formattedApiHost + '/models' } - if (isOpenAIProvider(provider)) { + if (isOpenAIResponsesProvider(provider)) { return formattedApiHost + '/responses' } if (isVertexProvider(provider)) { return formattedApiHost + '/publishers/google' } - if (isAIGatewayProvider(provider)) { - return formattedApiHost + '/language-model' - } return formattedApiHost } @@ -380,7 +379,12 @@ const ProviderSetting: FC = ({ providerId, isOnboarding = false }) => { {apiKeyConnectivity.error?.message || t('settings.models.check.failed')} } iconProps={{ size: 16, color: 'var(--color-status-warning)' }} - onClick={() => showErrorDetailPopup({ error: apiKeyConnectivity.error })} + onClick={() => setShowErrorModal(true)} + /> + setShowErrorModal(false)} + error={apiKeyConnectivity.error} /> ) @@ -390,12 +394,12 @@ const ProviderSetting: FC = ({ providerId, isOnboarding = false }) => { if (provider.id === 'copilot') { return } - setApiHost(provider.apiHost) - }, [provider.apiHost, provider.id]) + setApiHost(providerApiHost) + }, [providerApiHost, provider.id]) useEffect(() => { - setAnthropicHost(provider.anthropicApiHost) - }, [provider.anthropicApiHost]) + setAnthropicHost(providerAnthropicHost) + }, [providerAnthropicHost]) const canConfigureAnthropicHost = useMemo(() => { if (isCherryIN) { @@ -405,17 +409,16 @@ const ProviderSetting: FC = ({ providerId, isOnboarding = false }) => { return true } return ( - provider.type !== 'anthropic' && isSystemProviderId(provider.id) && isAnthropicCompatibleProviderId(provider.id) + !isAnthropicProvider(provider) && isSystemProviderId(provider.id) && isAnthropicCompatibleProviderId(provider.id) ) }, [isCherryIN, provider]) const anthropicHostPreview = useMemo(() => { - const rawHost = anthropicApiHost ?? provider.anthropicApiHost - // AI SDK uses the baseURL with /v1, then appends /messages + const rawHost = anthropicApiHost ?? providerAnthropicHost const normalizedHost = formatApiHost(rawHost) return `${normalizedHost}/messages` - }, [anthropicApiHost, provider.anthropicApiHost]) + }, [anthropicApiHost, providerAnthropicHost]) const hostSelectorOptions = useMemo(() => { const options: { value: HostField; label: string }[] = [ @@ -448,13 +451,13 @@ const ProviderSetting: FC = ({ providerId, isOnboarding = false }) => { {fancyProviderName} {officialWebsite && ( - + )} - {(!isSystemProvider(provider) || isSupportAnthropicPromptCacheProvider(provider)) && ( + {(!isSystemProvider(provider) || isAnthropicSupportedProvider(provider)) && ( - ) - }, [list, t, loadingModels, provider, onRemoveAll, onAddAll, loadModels]) + }, [list, t, loadingModels, provider, existingModelIds, onRemoveAll, onAddAll, loadModels]) return ( = ({ providerId, resolve }) => { /> ) : ( )} diff --git a/src/renderer/src/pages/settings/ProviderSettings/ModelList/ModelList.tsx b/src/renderer/src/pages/settings/ProviderSettings/ModelList/ModelList.tsx index 7365986934b..214573f87b6 100644 --- a/src/renderer/src/pages/settings/ProviderSettings/ModelList/ModelList.tsx +++ b/src/renderer/src/pages/settings/ProviderSettings/ModelList/ModelList.tsx @@ -1,9 +1,10 @@ import { Button, ColFlex, Flex, RowFlex, Tooltip } from '@cherrystudio/ui' +import { useModelMutations, useModels } from '@data/hooks/useModels' +import { useProvider, useProviderApiKeys } from '@data/hooks/useProviders' import CollapsibleSearchBar from '@renderer/components/CollapsibleSearchBar' import { LoadingIcon, StreamlineGoodHealthAndWellBeing } from '@renderer/components/Icons' import CustomTag from '@renderer/components/Tags/CustomTag' import { PROVIDER_URLS } from '@renderer/config/providers' -import { useProvider } from '@renderer/hooks/useProvider' import { getProviderLabel } from '@renderer/i18n/label' import { SettingHelpLink, SettingHelpText, SettingHelpTextRow, SettingSubtitle } from '@renderer/pages/settings' import EditModelPopup from '@renderer/pages/settings/ProviderSettings/EditModelPopup/EditModelPopup' @@ -11,13 +12,15 @@ import AddModelPopup from '@renderer/pages/settings/ProviderSettings/ModelList/A import DownloadOVMSModelPopup from '@renderer/pages/settings/ProviderSettings/ModelList/DownloadOVMSModelPopup' import ManageModelsPopup from '@renderer/pages/settings/ProviderSettings/ModelList/ManageModelsPopup' import NewApiAddModelPopup from '@renderer/pages/settings/ProviderSettings/ModelList/NewApiAddModelPopup' -import type { Model } from '@renderer/types' +import type { Model as V1Model, Provider as V1Provider } from '@renderer/types' import { filterModelsByKeywords } from '@renderer/utils' -import { getDuplicateModelNames } from '@renderer/utils/model' -import { isNewApiProvider } from '@renderer/utils/provider' -import { Space, Spin } from 'antd' +import { isNewApiProvider } from '@renderer/utils/provider.v2' +import { toV1ProviderShim } from '@renderer/utils/v1ProviderShim' +import type { Model } from '@shared/data/types/model' +import { parseUniqueModelId } from '@shared/data/types/model' +import { Spin } from 'antd' import { groupBy, isEmpty, sortBy, toPairs } from 'lodash' -import { Plus, RefreshCw } from 'lucide-react' +import { ListCheck, Plus } from 'lucide-react' import React, { memo, startTransition, useCallback, useEffect, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' @@ -35,7 +38,7 @@ const MODEL_COUNT_THRESHOLD = 10 * 根据搜索文本筛选模型、分组并排序 */ const calculateModelGroups = (models: Model[], searchText: string): ModelGroups => { - const filteredModels = searchText ? filterModelsByKeywords(searchText, models) : models + const filteredModels = searchText ? filterModelsByKeywords(searchText, models as any) : models const grouped = groupBy(filteredModels, 'group') return sortBy(toPairs(grouped), [0]).reduce((acc, [key, value]) => { acc[key] = value @@ -48,14 +51,29 @@ const calculateModelGroups = (models: Model[], searchText: string): ModelGroups */ const ModelList: React.FC = ({ providerId }) => { const { t } = useTranslation() - const { provider, models, removeModel } = useProvider(providerId) + const { provider } = useProvider(providerId) + const { models } = useModels({ providerId }) + const { data: apiKeysData } = useProviderApiKeys(providerId) + const joinedApiKey = apiKeysData?.keys?.map((k) => k.key).join(',') ?? '' + const { deleteModel } = useModelMutations() + + const removeModel = useCallback( + async (model: Model) => { + const { modelId } = parseUniqueModelId(model.id) + await deleteModel(model.providerId, modelId) + }, + [deleteModel] + ) // 稳定的编辑模型回调,避免内联函数导致子组件 memo 失效 - const handleEditModel = useCallback((model: Model) => EditModelPopup.show({ provider, model }), [provider]) + const handleEditModel = useCallback( + (model: Model) => provider && EditModelPopup.show({ provider: provider as any, model: model as any }), + [provider] + ) - const providerConfig = PROVIDER_URLS[provider.id] - const docsWebsite = providerConfig?.websites?.docs - const modelsWebsite = providerConfig?.websites?.models + const providerConfig = provider ? PROVIDER_URLS[provider.id as keyof typeof PROVIDER_URLS] : undefined + const docsWebsite = provider?.websites?.docs ?? providerConfig?.websites?.docs + const modelsWebsite = provider?.websites?.models ?? providerConfig?.websites?.models const [searchText, _setSearchText] = useState('') const [displayedModelGroups, setDisplayedModelGroups] = useState(() => { @@ -65,8 +83,25 @@ const ModelList: React.FC = ({ providerId }) => { return calculateModelGroups(models, '') }) - const { isChecking: isHealthChecking, modelStatuses, runHealthCheck } = useHealthCheck(provider, models) - const duplicateModelNames = useMemo(() => getDuplicateModelNames(models), [models]) + const v1ProviderForHealth = useMemo((): V1Provider => { + if (provider) { + return toV1ProviderShim(provider, { models, apiKey: joinedApiKey }) + } + return { + id: '', + name: '', + type: 'openai', + apiKey: '', + apiHost: '', + models: [] + } as V1Provider + }, [provider, models, joinedApiKey]) + // TODO(v2-cleanup): Remove v1 shim after useHealthCheck migrates to v2 + const { + isChecking: isHealthChecking, + modelStatuses, + runHealthCheck + } = useHealthCheck(v1ProviderForHealth, models as unknown as V1Model[]) // 将 modelStatuses 数组转换为 Map,实现 O(1) 查找 const modelStatusMap = useMemo(() => { @@ -94,75 +129,51 @@ const ModelList: React.FC = ({ providerId }) => { }, [displayedModelGroups]) const onManageModel = useCallback(() => { - void ManageModelsPopup.show({ providerId: provider.id }) - }, [provider.id]) + if (provider) ManageModelsPopup.show({ providerId: provider.id }) + }, [provider]) const onAddModel = useCallback(() => { + if (!provider) return if (isNewApiProvider(provider)) { - void NewApiAddModelPopup.show({ title: t('settings.models.add.add_model'), provider }) + NewApiAddModelPopup.show({ title: t('settings.models.add.add_model'), provider: provider as any }) } else { - void AddModelPopup.show({ title: t('settings.models.add.add_model'), provider }) + AddModelPopup.show({ title: t('settings.models.add.add_model'), provider: provider as any }) } }, [provider, t]) const onDownloadModel = useCallback( - () => DownloadOVMSModelPopup.show({ title: t('ovms.download.title'), provider }), + () => provider && DownloadOVMSModelPopup.show({ title: t('ovms.download.title'), provider: provider as any }), [provider, t] ) const isLoading = useMemo(() => displayedModelGroups === null, [displayedModelGroups]) - const hasNoModels = useMemo(() => models.length === 0, [models.length]) - - const actionButtons = ( - - - {provider.id !== 'ovms' ? ( - - - - ) : ( - - - - )} - - ) return ( <> - - - - {t('common.models')} - - {modelCount} - - {!hasNoModels && ( - <> - - - - - + + + + {t('common.models')} + {modelCount > 0 && ( + + {modelCount} + )} + + + + + + - {!hasNoModels && actionButtons} - {hasNoModels &&
{actionButtons}
} }> {displayedModelGroups && !isEmpty(displayedModelGroups) && ( @@ -170,12 +181,11 @@ const ModelList: React.FC = ({ providerId }) => { displayedModelGroups[group].forEach((model) => removeModel(model))} /> ))} @@ -188,7 +198,7 @@ const ModelList: React.FC = ({ providerId }) => { {t('settings.provider.docs_check')} {docsWebsite && ( - {getProviderLabel(provider.id) + ' '} + {getProviderLabel(provider?.id ?? '') + ' '} {t('common.docs')} )} @@ -204,6 +214,23 @@ const ModelList: React.FC = ({ providerId }) => {
)} + + + {provider?.id !== 'ovms' ? ( + + ) : ( + + )} + ) } diff --git a/src/renderer/src/pages/settings/ProviderSettings/ModelList/NewApiAddModelPopup.tsx b/src/renderer/src/pages/settings/ProviderSettings/ModelList/NewApiAddModelPopup.tsx index e577c4e8ed1..e4da0e881c3 100644 --- a/src/renderer/src/pages/settings/ProviderSettings/ModelList/NewApiAddModelPopup.tsx +++ b/src/renderer/src/pages/settings/ProviderSettings/ModelList/NewApiAddModelPopup.tsx @@ -1,24 +1,23 @@ import { Flex } from '@cherrystudio/ui' import { Button } from '@cherrystudio/ui' +import { useModelMutations, useModels } from '@data/hooks/useModels' import { TopView } from '@renderer/components/TopView' import { endpointTypeOptions } from '@renderer/config/endpointTypes' -import { isNotSupportTextDeltaModel } from '@renderer/config/models' import { useDynamicLabelWidth } from '@renderer/hooks/useDynamicLabelWidth' -import { useProvider } from '@renderer/hooks/useProvider' -import type { EndpointType, Model, Provider } from '@renderer/types' import { getDefaultGroupName } from '@renderer/utils' -import { isNewApiProvider } from '@renderer/utils/provider' +import { isNewApiProvider } from '@renderer/utils/provider.v2' +import type { Model } from '@shared/data/types/model' +import type { Provider } from '@shared/data/types/provider' import type { FormProps } from 'antd' import { Form, Input, Modal, Select } from 'antd' -import { find } from 'lodash' import { useState } from 'react' import { useTranslation } from 'react-i18next' interface ShowParams { title: string - provider: Provider - model?: Model - endpointType?: EndpointType + provider: Provider | any + model?: Model | any + endpointType?: number | string } interface Props extends ShowParams { @@ -30,13 +29,14 @@ type FieldType = { id: string name?: string group?: string - endpointType?: EndpointType + endpointType?: number | string } const PopupContainer: React.FC = ({ title, provider, resolve, model, endpointType }) => { const [open, setOpen] = useState(true) const [form] = Form.useForm() - const { addModel, models } = useProvider(provider.id) + const { models } = useModels({ providerId: provider.id }) + const { createModel } = useModelMutations() const { t } = useTranslation() const onOk = () => { @@ -51,38 +51,38 @@ const PopupContainer: React.FC = ({ title, provider, resolve, model, endp resolve({}) } - const onAddModel = (values: FieldType) => { - const id = values.id.trim() + const onAddModel = async (values: FieldType) => { + const modelId = values.id.trim() - if (find(models, { id })) { + if (models.some((m) => m.id.endsWith(`::${modelId}`))) { window.toast.error(t('error.model.exists')) return } - const model: Model = { - id, - provider: provider.id, - name: values.name ? values.name : id.toUpperCase(), - group: values.group ?? getDefaultGroupName(id), - endpoint_type: isNewApiProvider(provider) ? values.endpointType : undefined - } - - addModel({ ...model, supported_text_delta: !isNotSupportTextDeltaModel(model) }) + await createModel({ + providerId: provider.id, + modelId, + name: values.name ? values.name : modelId.toUpperCase(), + group: values.group ?? getDefaultGroupName(modelId), + endpointTypes: isNewApiProvider(provider) && values.endpointType ? [values.endpointType as number] : undefined + }) return true } - const onFinish: FormProps['onFinish'] = (values) => { + const onFinish: FormProps['onFinish'] = async (values) => { const id = values.id.trim().replaceAll(',', ',') if (id.includes(',')) { const ids = id.split(',') - ids.forEach((id) => onAddModel({ id, name: id } as FieldType)) + for (const singleId of ids) { + await onAddModel({ id: singleId, name: singleId } as FieldType) + } resolve({}) return } - if (onAddModel(values)) { + if (await onAddModel(values)) { resolve({}) } } diff --git a/src/renderer/src/pages/settings/ProviderSettings/ModelList/NewApiBatchAddModelPopup.tsx b/src/renderer/src/pages/settings/ProviderSettings/ModelList/NewApiBatchAddModelPopup.tsx index 5cb19c03bf3..6c864e67426 100644 --- a/src/renderer/src/pages/settings/ProviderSettings/ModelList/NewApiBatchAddModelPopup.tsx +++ b/src/renderer/src/pages/settings/ProviderSettings/ModelList/NewApiBatchAddModelPopup.tsx @@ -1,11 +1,10 @@ import { Flex } from '@cherrystudio/ui' import { Button } from '@cherrystudio/ui' +import { useModelMutations } from '@data/hooks/useModels' import { TopView } from '@renderer/components/TopView' import { endpointTypeOptions } from '@renderer/config/endpointTypes' -import { isNotSupportTextDeltaModel } from '@renderer/config/models' import { useDynamicLabelWidth } from '@renderer/hooks/useDynamicLabelWidth' -import { useProvider } from '@renderer/hooks/useProvider' -import type { EndpointType, Model, Provider } from '@renderer/types' +import { parseUniqueModelId } from '@shared/data/types/model' import type { FormProps } from 'antd' import { Form, Modal, Select } from 'antd' import { useState } from 'react' @@ -13,8 +12,8 @@ import { useTranslation } from 'react-i18next' interface ShowParams { title: string - provider: Provider - batchModels: Model[] + provider: any + batchModels: any[] } interface Props extends ShowParams { @@ -24,13 +23,13 @@ interface Props extends ShowParams { type FieldType = { provider: string group?: string - endpointType?: EndpointType + endpointType?: number | string } const PopupContainer: React.FC = ({ title, provider, resolve, batchModels }) => { const [open, setOpen] = useState(true) const [form] = Form.useForm() - const { addModel } = useProvider(provider.id) + const { createModel } = useModelMutations() const { t } = useTranslation() const onOk = () => { @@ -45,19 +44,22 @@ const PopupContainer: React.FC = ({ title, provider, resolve, batchModels resolve({}) } - const onAddModel = (values: FieldType) => { - batchModels.forEach((model) => { - addModel({ - ...model, - endpoint_type: values.endpointType, - supported_text_delta: !isNotSupportTextDeltaModel(model) + const onAddModel = async (values: FieldType) => { + for (const model of batchModels) { + const modelId = model.apiModelId ?? parseUniqueModelId(model.id).modelId + await createModel({ + providerId: provider.id, + modelId, + name: model.name, + group: model.group, + endpointTypes: values.endpointType ? [values.endpointType as number] : undefined }) - }) + } return true } - const onFinish: FormProps['onFinish'] = (values) => { - if (onAddModel(values)) { + const onFinish: FormProps['onFinish'] = async (values) => { + if (await onAddModel(values)) { resolve({}) } } diff --git a/src/renderer/src/pages/settings/ProviderSettings/ModelList/utils.ts b/src/renderer/src/pages/settings/ProviderSettings/ModelList/utils.ts index 0b484fdedce..eab48225d44 100644 --- a/src/renderer/src/pages/settings/ProviderSettings/ModelList/utils.ts +++ b/src/renderer/src/pages/settings/ProviderSettings/ModelList/utils.ts @@ -1,9 +1,4 @@ -import type { Model, Provider } from '@renderer/types' - -// Check if the model exists in the provider's model list -export const isModelInProvider = (provider: Provider, modelId: string): boolean => { - return provider.models.some((m) => m.id === modelId) -} +import type { Model } from '@renderer/types' export const isValidNewApiModel = (model: Model): boolean => { return !!(model.supported_endpoint_types && model.supported_endpoint_types.length > 0) From cbde3dd4465f778dca37024ddbfa26cd625b0dc3 Mon Sep 17 00:00:00 2001 From: jidan745le <420511176@qq.com> Date: Sun, 5 Apr 2026 22:17:46 -0800 Subject: [PATCH 15/29] feat(renderer): migrate LlmApiKeyList to v2 via shim Signed-off-by: jidan745le <420511176@qq.com> --- .../Popups/ApiKeyListPopup/list.tsx | 38 +++++++++++++++++-- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/src/renderer/src/components/Popups/ApiKeyListPopup/list.tsx b/src/renderer/src/components/Popups/ApiKeyListPopup/list.tsx index cb126813906..3a7bc3c23fc 100644 --- a/src/renderer/src/components/Popups/ApiKeyListPopup/list.tsx +++ b/src/renderer/src/components/Popups/ApiKeyListPopup/list.tsx @@ -1,19 +1,21 @@ import { Button, Flex, Tooltip } from '@cherrystudio/ui' +import { useModels } from '@data/hooks/useModels' +import { useProvider, useProviderApiKeys, useProviderMutations } from '@data/hooks/useProviders' import { DeleteIcon } from '@renderer/components/Icons' import { StreamlineGoodHealthAndWellBeing } from '@renderer/components/Icons/SVGIcon' import Scrollbar from '@renderer/components/Scrollbar' import { usePreprocessProvider } from '@renderer/hooks/usePreprocess' -import { useProvider } from '@renderer/hooks/useProvider' import { useWebSearchProvider } from '@renderer/hooks/useWebSearchProviders' import { SettingHelpText } from '@renderer/pages/settings' import { isProviderSupportAuth } from '@renderer/services/ProviderService' import type { PreprocessProviderId, WebSearchProviderId } from '@renderer/types' import type { ApiKeyWithStatus } from '@renderer/types/healthCheck' import { HealthStatus } from '@renderer/types/healthCheck' +import { toV1ProviderShim } from '@renderer/utils/v1ProviderShim' import { Card, List, Popconfirm, Space, Typography } from 'antd' import { Plus } from 'lucide-react' import type { FC } from 'react' -import { useState } from 'react' +import { useCallback, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import styled from 'styled-components' @@ -191,9 +193,37 @@ type DocPreprocessApiKeyListProps = SpecificApiKeyListProps & { } export const LlmApiKeyList: FC = ({ providerId, showHealthCheck = true }) => { - const { provider, updateProvider } = useProvider(providerId) + // TODO(v2-cleanup): Remove v1 shim after useApiKeys/checkApi migrate to v2 + const { provider } = useProvider(providerId) + const { data: apiKeysData } = useProviderApiKeys(providerId) + const { models } = useModels({ providerId }) + const { updateApiKeys } = useProviderMutations(providerId) + + const joinedApiKey = useMemo(() => apiKeysData?.keys?.map((k) => k.key).join(',') ?? '', [apiKeysData]) + + const v1Provider = useMemo(() => { + if (!provider) return null + return toV1ProviderShim(provider, { models, apiKey: joinedApiKey }) + }, [provider, models, joinedApiKey]) + + const shimUpdateProvider = useCallback( + (updates: Partial<{ apiKey: string }>) => { + if (updates.apiKey === undefined) return + const keys = updates.apiKey + .split(',') + .map((k) => k.trim()) + .filter(Boolean) + const apiKeys = keys.map((key) => ({ id: crypto.randomUUID(), key, isEnabled: true })) + void updateApiKeys(apiKeys) + }, + [updateApiKeys] + ) - return + if (!v1Provider) return null + + return ( + + ) } export const WebSearchApiKeyList: FC = ({ providerId, showHealthCheck = true }) => { From d952df3d99f7322b5a55e8505361c3bd1b83bcb4 Mon Sep 17 00:00:00 2001 From: jidan745le <420511176@qq.com> Date: Sun, 5 Apr 2026 22:24:40 -0800 Subject: [PATCH 16/29] fix(renderer): adapt migrated files to target branch API surface Signed-off-by: jidan745le <420511176@qq.com> --- .../__tests__/useDefaultModel.v2.test.ts | 240 ------------------ .../src/data/hooks/useDefaultModel.v2.ts | 60 ----- .../ProviderSettings/AddProviderPopup.tsx | 4 +- .../ProviderSettings/ModelList/ModelList.tsx | 3 + .../ProviderSettings/ProviderList.tsx | 11 +- .../ProviderSettings/ProviderSetting.tsx | 41 +-- 6 files changed, 40 insertions(+), 319 deletions(-) delete mode 100644 src/renderer/src/data/hooks/__tests__/useDefaultModel.v2.test.ts delete mode 100644 src/renderer/src/data/hooks/useDefaultModel.v2.ts diff --git a/src/renderer/src/data/hooks/__tests__/useDefaultModel.v2.test.ts b/src/renderer/src/data/hooks/__tests__/useDefaultModel.v2.test.ts deleted file mode 100644 index 6ee066c9418..00000000000 --- a/src/renderer/src/data/hooks/__tests__/useDefaultModel.v2.test.ts +++ /dev/null @@ -1,240 +0,0 @@ -import { mockUseQuery } from '@test-mocks/renderer/useDataApi' -import { mockUsePreference } from '@test-mocks/renderer/usePreference' -import { act, renderHook } from '@testing-library/react' -import { beforeEach, describe, expect, it, vi } from 'vitest' - -import { useDefaultModel } from '../useDefaultModel.v2' - -// Mock model data -const mockDefaultModel = { - id: 'openai::gpt-4o', - providerId: 'openai', - name: 'GPT-4o', - capabilities: [], - supportsStreaming: true, - isEnabled: true, - isHidden: false -} -const mockQuickModel = { - id: 'openai::gpt-4o-mini', - providerId: 'openai', - name: 'GPT-4o Mini', - capabilities: [], - supportsStreaming: true, - isEnabled: true, - isHidden: false -} -const mockTranslateModel = { - id: 'anthropic::claude-3-haiku', - providerId: 'anthropic', - name: 'Claude 3 Haiku', - capabilities: [], - supportsStreaming: true, - isEnabled: true, - isHidden: false -} - -describe('useDefaultModel.v2', () => { - beforeEach(() => { - vi.clearAllMocks() - - // Default: usePreference returns empty strings (no model set) - mockUsePreference.mockImplementation((key: string) => { - const setFn = vi.fn().mockResolvedValue(undefined) - switch (key) { - case 'model.default_id': - return ['', setFn] - case 'model.quick_id': - return ['', setFn] - case 'model.translate_id': - return ['', setFn] - default: - return ['', setFn] - } - }) - - // Default: useQuery returns undefined data when enabled is false - mockUseQuery.mockImplementation((_path: string, options?: { enabled?: boolean }) => ({ - data: options?.enabled === false ? undefined : undefined, - isLoading: false, - isRefreshing: false, - error: undefined, - refetch: vi.fn(), - mutate: vi.fn() - })) - }) - - it('should return empty state when no models are configured', () => { - const { result } = renderHook(() => useDefaultModel()) - - expect(result.current.defaultModel).toBeUndefined() - expect(result.current.defaultModelId).toBe('') - expect(result.current.quickModel).toBeUndefined() - expect(result.current.quickModelId).toBe('') - expect(result.current.translateModel).toBeUndefined() - expect(result.current.translateModelId).toBe('') - expect(result.current.isLoading).toBe(false) - }) - - it('should resolve models when preference IDs are set', () => { - mockUsePreference.mockImplementation((key: string) => { - const setFn = vi.fn().mockResolvedValue(undefined) - switch (key) { - case 'model.default_id': - return ['openai::gpt-4o', setFn] - case 'model.quick_id': - return ['openai::gpt-4o-mini', setFn] - case 'model.translate_id': - return ['anthropic::claude-3-haiku', setFn] - default: - return ['', setFn] - } - }) - - mockUseQuery.mockImplementation((path: string, options?: { enabled?: boolean }) => { - if (options?.enabled === false) { - return { - data: undefined, - isLoading: false, - isRefreshing: false, - error: undefined, - refetch: vi.fn(), - mutate: vi.fn() - } - } - if (path.includes('openai') && path.includes('gpt-4o-mini')) { - return { - data: mockQuickModel, - isLoading: false, - isRefreshing: false, - error: undefined, - refetch: vi.fn(), - mutate: vi.fn() - } - } - if (path.includes('openai') && path.includes('gpt-4o')) { - return { - data: mockDefaultModel, - isLoading: false, - isRefreshing: false, - error: undefined, - refetch: vi.fn(), - mutate: vi.fn() - } - } - if (path.includes('anthropic') && path.includes('claude-3-haiku')) { - return { - data: mockTranslateModel, - isLoading: false, - isRefreshing: false, - error: undefined, - refetch: vi.fn(), - mutate: vi.fn() - } - } - return { - data: undefined, - isLoading: false, - isRefreshing: false, - error: undefined, - refetch: vi.fn(), - mutate: vi.fn() - } - }) - - const { result } = renderHook(() => useDefaultModel()) - - expect(result.current.defaultModel).toEqual(mockDefaultModel) - expect(result.current.defaultModelId).toBe('openai::gpt-4o') - expect(result.current.quickModel).toEqual(mockQuickModel) - expect(result.current.quickModelId).toBe('openai::gpt-4o-mini') - expect(result.current.translateModel).toEqual(mockTranslateModel) - expect(result.current.translateModelId).toBe('anthropic::claude-3-haiku') - }) - - it('should show isLoading when any model is being resolved', () => { - mockUsePreference.mockImplementation((key: string) => { - const setFn = vi.fn().mockResolvedValue(undefined) - if (key === 'model.default_id') return ['openai::gpt-4o', setFn] - return ['', setFn] - }) - - mockUseQuery.mockImplementation((_path: string, options?: { enabled?: boolean }) => { - if (options?.enabled === false) { - return { - data: undefined, - isLoading: false, - isRefreshing: false, - error: undefined, - refetch: vi.fn(), - mutate: vi.fn() - } - } - // Simulate loading - return { - data: undefined, - isLoading: true, - isRefreshing: false, - error: undefined, - refetch: vi.fn(), - mutate: vi.fn() - } - }) - - const { result } = renderHook(() => useDefaultModel()) - expect(result.current.isLoading).toBe(true) - }) - - it('should disable queries when preference is empty string', () => { - renderHook(() => useDefaultModel()) - - // All 3 useQuery calls should have enabled: false (since preference values are '') - const queryCallsWithEnabledFalse = mockUseQuery.mock.calls.filter((call) => call[1]?.enabled === false) - expect(queryCallsWithEnabledFalse.length).toBe(3) - }) - - it('should provide setter functions for each model', async () => { - const mockSetDefault = vi.fn().mockResolvedValue(undefined) - const mockSetQuick = vi.fn().mockResolvedValue(undefined) - const mockSetTranslate = vi.fn().mockResolvedValue(undefined) - - mockUsePreference.mockImplementation((key: string) => { - switch (key) { - case 'model.default_id': - return ['', mockSetDefault] - case 'model.quick_id': - return ['', mockSetQuick] - case 'model.translate_id': - return ['', mockSetTranslate] - default: - return ['', vi.fn()] - } - }) - - const { result } = renderHook(() => useDefaultModel()) - - await act(async () => { - await result.current.setDefaultModel('openai::gpt-4o' as any) - }) - expect(mockSetDefault).toHaveBeenCalledWith('openai::gpt-4o') - - await act(async () => { - await result.current.setQuickModel('openai::gpt-4o-mini' as any) - }) - expect(mockSetQuick).toHaveBeenCalledWith('openai::gpt-4o-mini') - - await act(async () => { - await result.current.setTranslateModel('anthropic::claude-3-haiku' as any) - }) - expect(mockSetTranslate).toHaveBeenCalledWith('anthropic::claude-3-haiku') - }) - - it('should call usePreference with correct keys', () => { - renderHook(() => useDefaultModel()) - - const calledKeys = mockUsePreference.mock.calls.map((call) => call[0]) - expect(calledKeys).toContain('model.default_id') - expect(calledKeys).toContain('model.quick_id') - expect(calledKeys).toContain('model.translate_id') - }) -}) diff --git a/src/renderer/src/data/hooks/useDefaultModel.v2.ts b/src/renderer/src/data/hooks/useDefaultModel.v2.ts deleted file mode 100644 index 03ead86eb92..00000000000 --- a/src/renderer/src/data/hooks/useDefaultModel.v2.ts +++ /dev/null @@ -1,60 +0,0 @@ -import type { Model } from '@shared/data/types/model' -import { parseUniqueModelId, type UniqueModelId } from '@shared/data/types/model' - -import { useQuery } from './useDataApi' -import { usePreference } from './usePreference' - -/** - * Internal helper: resolve a UniqueModelId string into a Model via useQuery. - * When uniqueModelId is undefined, the query is disabled (no request is made). - */ -function useResolveModel(uniqueModelId: string | undefined) { - const parsed = uniqueModelId ? parseUniqueModelId(uniqueModelId as UniqueModelId) : null - const { data, isLoading } = useQuery(`/models/${parsed?.providerId ?? ''}/${parsed?.modelId ?? ''}` as any, { - enabled: !!parsed - }) - return { model: data as Model | undefined, isLoading: !!parsed && isLoading } -} - -export interface UseDefaultModelReturn { - defaultModel?: Model - defaultModelId: string - quickModel?: Model - quickModelId: string - translateModel?: Model - translateModelId: string - isLoading: boolean - setDefaultModel: (id: UniqueModelId) => Promise - setQuickModel: (id: UniqueModelId) => Promise - setTranslateModel: (id: UniqueModelId) => Promise -} - -/** - * v2 replacement for the v1 useDefaultModel hook. - * - * v1 stored full Model objects in Redux state. - * v2 stores UniqueModelId strings in Preferences and resolves them via useQuery. - */ -export function useDefaultModel(): UseDefaultModelReturn { - const [defaultModelId, setDefaultModelId] = usePreference('model.default_id') - const [quickModelId, setQuickModelId] = usePreference('model.quick_id') - const [translateModelId, setTranslateModelId] = usePreference('model.translate_id') - - // Empty string → undefined to disable the query - const { model: defaultModel, isLoading: loadingDefault } = useResolveModel(defaultModelId || undefined) - const { model: quickModel, isLoading: loadingQuick } = useResolveModel(quickModelId || undefined) - const { model: translateModel, isLoading: loadingTranslate } = useResolveModel(translateModelId || undefined) - - return { - defaultModel, - defaultModelId, - quickModel, - quickModelId, - translateModel, - translateModelId, - isLoading: loadingDefault || loadingQuick || loadingTranslate, - setDefaultModel: setDefaultModelId as (id: UniqueModelId) => Promise, - setQuickModel: setQuickModelId as (id: UniqueModelId) => Promise, - setTranslateModel: setTranslateModelId as (id: UniqueModelId) => Promise - } -} diff --git a/src/renderer/src/pages/settings/ProviderSettings/AddProviderPopup.tsx b/src/renderer/src/pages/settings/ProviderSettings/AddProviderPopup.tsx index 5b019a92764..5ff9d5d55d2 100644 --- a/src/renderer/src/pages/settings/ProviderSettings/AddProviderPopup.tsx +++ b/src/renderer/src/pages/settings/ProviderSettings/AddProviderPopup.tsx @@ -1,9 +1,9 @@ import { Center, ColFlex } from '@cherrystudio/ui' +import { resolveProviderIcon } from '@cherrystudio/ui/icons' import { loggerService } from '@logger' import { ProviderAvatarPrimitive } from '@renderer/components/ProviderAvatar' import ProviderLogoPicker from '@renderer/components/ProviderLogoPicker' import { TopView } from '@renderer/components/TopView' -import { getProviderLogo } from '@renderer/config/providers' import ImageStorage from '@renderer/services/ImageStorage' import type { Provider, ProviderType } from '@renderer/types' import { compressImage, generateColorFromChar, getForegroundColor } from '@renderer/utils' @@ -73,7 +73,7 @@ const PopupContainer: React.FC = ({ provider, resolve }) => { // 处理内置头像的点击事件 const handleProviderLogoClick = async (providerId: string) => { try { - const icon = getProviderLogo(providerId) + const icon = resolveProviderIcon(providerId) if (!icon) return // Store the provider icon ID as a reference (prefixed with 'icon:') diff --git a/src/renderer/src/pages/settings/ProviderSettings/ModelList/ModelList.tsx b/src/renderer/src/pages/settings/ProviderSettings/ModelList/ModelList.tsx index 214573f87b6..79df4dafd79 100644 --- a/src/renderer/src/pages/settings/ProviderSettings/ModelList/ModelList.tsx +++ b/src/renderer/src/pages/settings/ProviderSettings/ModelList/ModelList.tsx @@ -14,6 +14,7 @@ import ManageModelsPopup from '@renderer/pages/settings/ProviderSettings/ModelLi import NewApiAddModelPopup from '@renderer/pages/settings/ProviderSettings/ModelList/NewApiAddModelPopup' import type { Model as V1Model, Provider as V1Provider } from '@renderer/types' import { filterModelsByKeywords } from '@renderer/utils' +import { getDuplicateModelNames } from '@renderer/utils/model' import { isNewApiProvider } from '@renderer/utils/provider.v2' import { toV1ProviderShim } from '@renderer/utils/v1ProviderShim' import type { Model } from '@shared/data/types/model' @@ -56,6 +57,7 @@ const ModelList: React.FC = ({ providerId }) => { const { data: apiKeysData } = useProviderApiKeys(providerId) const joinedApiKey = apiKeysData?.keys?.map((k) => k.key).join(',') ?? '' const { deleteModel } = useModelMutations() + const duplicateModelNames = useMemo(() => getDuplicateModelNames(models as any), [models]) const removeModel = useCallback( async (model: Model) => { @@ -183,6 +185,7 @@ const ModelList: React.FC = ({ providerId }) => { groupName={group} models={displayedModelGroups[group] as any} modelStatusMap={modelStatusMap as any} + duplicateModelNames={duplicateModelNames} defaultOpen={i <= 5} onEditModel={handleEditModel as any} onRemoveModel={removeModel as any} diff --git a/src/renderer/src/pages/settings/ProviderSettings/ProviderList.tsx b/src/renderer/src/pages/settings/ProviderSettings/ProviderList.tsx index 7302fba3720..c291f6443a4 100644 --- a/src/renderer/src/pages/settings/ProviderSettings/ProviderList.tsx +++ b/src/renderer/src/pages/settings/ProviderSettings/ProviderList.tsx @@ -49,7 +49,12 @@ const getIsOvmsSupported = async (): Promise => { } } -const ProviderList: FC = () => { +interface ProviderListProps { + /** Whether in onboarding mode for new users */ + isOnboarding?: boolean +} + +const ProviderList: FC = ({ isOnboarding = false }) => { // TODO: Define validateSearch in routes/settings/provider.tsx and replace with Route.useSearch() // for type-safe search params. Currently using untyped useSearch as a stopgap after removing react-router-dom. const search = useSearch({ strict: false }) as Record @@ -434,7 +439,9 @@ const ProviderList: FC = () => { - {selectedProvider && } + {selectedProvider && ( + + )} ) } diff --git a/src/renderer/src/pages/settings/ProviderSettings/ProviderSetting.tsx b/src/renderer/src/pages/settings/ProviderSettings/ProviderSetting.tsx index 7ef40d9125f..f9142ac9e07 100644 --- a/src/renderer/src/pages/settings/ProviderSettings/ProviderSetting.tsx +++ b/src/renderer/src/pages/settings/ProviderSettings/ProviderSetting.tsx @@ -4,7 +4,7 @@ import { useModels } from '@data/hooks/useModels' import { useProvider, useProviderApiKeys, useProviderMutations } from '@data/hooks/useProviders' import { adaptProvider } from '@renderer/aiCore/provider/providerConfig' import OpenAIAlert from '@renderer/components/Alert/OpenAIAlert' -import { ErrorDetailModal } from '@renderer/components/ErrorDetailModal' +import { showErrorDetailPopup } from '@renderer/components/ErrorDetailModal' import { LoadingIcon } from '@renderer/components/Icons' import { ApiKeyListPopup } from '@renderer/components/Popups/ApiKeyListPopup' import Selector from '@renderer/components/Selector' @@ -45,7 +45,7 @@ import Link from 'antd/es/typography/Link' import { debounce, isEmpty } from 'lodash' import { Bolt, Check, Settings2, SquareArrowOutUpRight } from 'lucide-react' import type { FC } from 'react' -import { useCallback, useEffect, useMemo, useState } from 'react' +import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import styled from 'styled-components' @@ -73,6 +73,7 @@ import VertexAISettings from './VertexAISettings' interface Props { providerId: string + isOnboarding?: boolean } const ANTHROPIC_COMPATIBLE_PROVIDER_IDS = [ @@ -102,18 +103,19 @@ const isAnthropicCompatibleProviderId = (id: string): id is AnthropicCompatibleP type HostField = 'apiHost' | 'anthropicApiHost' -const ProviderSetting: FC = ({ providerId }) => { +const ProviderSetting: FC = ({ providerId, isOnboarding = false }) => { const { provider } = useProvider(providerId) if (!provider) return null - return + return } interface ContentProps { provider: Provider providerId: string + isOnboarding?: boolean } -const ProviderSettingContent: FC = ({ provider, providerId }) => { +const ProviderSettingContent: FC = ({ provider, providerId, isOnboarding = false }) => { const { updateProvider, updateApiKeys } = useProviderMutations(providerId) const { models } = useModels({ providerId }) const { data: apiKeysData } = useProviderApiKeys(providerId) @@ -162,7 +164,6 @@ const ProviderSettingContent: FC = ({ provider, providerId }) => { status: HealthStatus.NOT_CHECKED, checking: false }) - const [showErrorModal, setShowErrorModal] = useState(false) const updateWebSearchProviderKey = useCallback( ({ apiKey }: { apiKey: string }) => { @@ -171,16 +172,31 @@ const ProviderSettingContent: FC = ({ provider, providerId }) => { [dispatch, provider.id] ) + const callbacks = { updateApiKeys, updateWebSearchProviderKey, isOnboarding, providerEnabled: provider.isEnabled } + const callbacksRef = useRef(callbacks) + callbacksRef.current = callbacks + const debouncedUpdateApiKey = useMemo( () => debounce(async (value: string) => { + const { + updateApiKeys: _updateApiKeys, + updateWebSearchProviderKey: _updateWS, + isOnboarding: _onb, + providerEnabled + } = callbacksRef.current const formatted = formatApiKeys(value) const keys = formatted.split(',').filter(Boolean) const apiKeys = keys.map((key) => ({ id: crypto.randomUUID(), key, isEnabled: true })) - await updateApiKeys(apiKeys) - updateWebSearchProviderKey({ apiKey: formatted }) + await _updateApiKeys(apiKeys) + _updateWS({ apiKey: formatted }) + // Auto-enable provider when apiKey is updated in onboarding mode + if (_onb && formatted && !providerEnabled) { + await patchProvider({ isEnabled: true }) + } }, 150), - [updateApiKeys, updateWebSearchProviderKey] + + [patchProvider] ) // 同步 provider apiKey 到 localApiKey @@ -379,12 +395,7 @@ const ProviderSettingContent: FC = ({ provider, providerId }) => { {apiKeyConnectivity.error?.message || t('settings.models.check.failed')} } iconProps={{ size: 16, color: 'var(--color-status-warning)' }} - onClick={() => setShowErrorModal(true)} - /> - setShowErrorModal(false)} - error={apiKeyConnectivity.error} + onClick={() => showErrorDetailPopup({ error: apiKeyConnectivity.error })} /> ) From b852bb3fc4f28fb968c29b6063b9b9a5a172d5a6 Mon Sep 17 00:00:00 2001 From: suyao Date: Mon, 6 Apr 2026 21:22:13 +0800 Subject: [PATCH 17/29] refactor: unify baseUrls, modelsApiUrls, reasoningFormatTypes into endpointConfigs Merge three separate provider endpoint fields into a single `endpointConfigs` map keyed by EndpointType. Each entry holds baseUrl, modelsApiUrls, and reasoningFormatType in one place. Also removes unused `createdAt` from ApiKeyEntry. Co-Authored-By: Claude Opus 4.6 Signed-off-by: suyao --- packages/provider-catalog/proto/buf.gen.yaml | 7 + packages/provider-catalog/proto/buf.yaml | 12 + .../provider-catalog/proto/v1/common.proto | 111 +++++++++ .../provider-catalog/proto/v1/model.proto | 110 +++++++++ .../provider-catalog/proto/v1/provider.proto | 223 ++++++++++++++++++ .../proto/v1/provider_models.proto | 63 +++++ .../src/gen/v1/provider_pb.ts | 58 +++-- .../provider-catalog/src/schemas/provider.ts | 47 ++-- packages/shared/data/api/schemas/providers.ts | 12 +- packages/shared/data/types/provider.ts | 42 ++-- packages/shared/data/utils/modelMerger.ts | 129 +++++++--- src/main/data/db/schemas/userProvider.ts | 20 +- .../mappings/ProviderModelMappings.ts | 36 ++- .../data/services/ProviderCatalogService.ts | 104 ++++---- src/main/data/services/ProviderService.ts | 19 +- 15 files changed, 791 insertions(+), 202 deletions(-) create mode 100644 packages/provider-catalog/proto/buf.gen.yaml create mode 100644 packages/provider-catalog/proto/buf.yaml create mode 100644 packages/provider-catalog/proto/v1/common.proto create mode 100644 packages/provider-catalog/proto/v1/model.proto create mode 100644 packages/provider-catalog/proto/v1/provider.proto create mode 100644 packages/provider-catalog/proto/v1/provider_models.proto diff --git a/packages/provider-catalog/proto/buf.gen.yaml b/packages/provider-catalog/proto/buf.gen.yaml new file mode 100644 index 00000000000..2dbb4bfe635 --- /dev/null +++ b/packages/provider-catalog/proto/buf.gen.yaml @@ -0,0 +1,7 @@ +version: v2 +inputs: + - directory: . +plugins: + - local: protoc-gen-es + opt: target=ts + out: ../src/gen diff --git a/packages/provider-catalog/proto/buf.yaml b/packages/provider-catalog/proto/buf.yaml new file mode 100644 index 00000000000..bc6d53f35a2 --- /dev/null +++ b/packages/provider-catalog/proto/buf.yaml @@ -0,0 +1,12 @@ +version: v2 +modules: + - path: . + name: buf.build/cherrystudio/catalog +lint: + use: + - STANDARD + except: + - PACKAGE_DIRECTORY_MATCH +breaking: + use: + - FILE diff --git a/packages/provider-catalog/proto/v1/common.proto b/packages/provider-catalog/proto/v1/common.proto new file mode 100644 index 00000000000..b3042471f0f --- /dev/null +++ b/packages/provider-catalog/proto/v1/common.proto @@ -0,0 +1,111 @@ +syntax = "proto3"; +package catalog.v1; + +// ═══════════════════════════════════════════════════════════════════════════════ +// Enums +// ═══════════════════════════════════════════════════════════════════════════════ + +enum EndpointType { + ENDPOINT_TYPE_UNSPECIFIED = 0; + ENDPOINT_TYPE_OPENAI_CHAT_COMPLETIONS = 1; + ENDPOINT_TYPE_OPENAI_TEXT_COMPLETIONS = 2; + ENDPOINT_TYPE_ANTHROPIC_MESSAGES = 3; + ENDPOINT_TYPE_OPENAI_RESPONSES = 4; + ENDPOINT_TYPE_GOOGLE_GENERATE_CONTENT = 5; + ENDPOINT_TYPE_OLLAMA_CHAT = 6; + ENDPOINT_TYPE_OLLAMA_GENERATE = 7; + ENDPOINT_TYPE_OPENAI_EMBEDDINGS = 8; + ENDPOINT_TYPE_JINA_RERANK = 9; + ENDPOINT_TYPE_OPENAI_IMAGE_GENERATION = 10; + ENDPOINT_TYPE_OPENAI_IMAGE_EDIT = 11; + ENDPOINT_TYPE_OPENAI_AUDIO_TRANSCRIPTION = 12; + ENDPOINT_TYPE_OPENAI_AUDIO_TRANSLATION = 13; + ENDPOINT_TYPE_OPENAI_TEXT_TO_SPEECH = 14; + ENDPOINT_TYPE_OPENAI_VIDEO_GENERATION = 15; +} + +enum ModelCapability { + MODEL_CAPABILITY_UNSPECIFIED = 0; + MODEL_CAPABILITY_FUNCTION_CALL = 1; + MODEL_CAPABILITY_REASONING = 2; + MODEL_CAPABILITY_IMAGE_RECOGNITION = 3; + MODEL_CAPABILITY_IMAGE_GENERATION = 4; + MODEL_CAPABILITY_AUDIO_RECOGNITION = 5; + MODEL_CAPABILITY_AUDIO_GENERATION = 6; + MODEL_CAPABILITY_EMBEDDING = 7; + MODEL_CAPABILITY_RERANK = 8; + MODEL_CAPABILITY_AUDIO_TRANSCRIPT = 9; + MODEL_CAPABILITY_VIDEO_RECOGNITION = 10; + MODEL_CAPABILITY_VIDEO_GENERATION = 11; + MODEL_CAPABILITY_STRUCTURED_OUTPUT = 12; + MODEL_CAPABILITY_FILE_INPUT = 13; + MODEL_CAPABILITY_WEB_SEARCH = 14; + MODEL_CAPABILITY_CODE_EXECUTION = 15; + MODEL_CAPABILITY_FILE_SEARCH = 16; + MODEL_CAPABILITY_COMPUTER_USE = 17; +} + +enum Modality { + MODALITY_UNSPECIFIED = 0; + MODALITY_TEXT = 1; + MODALITY_IMAGE = 2; + MODALITY_AUDIO = 3; + MODALITY_VIDEO = 4; + MODALITY_VECTOR = 5; +} + +enum Currency { + CURRENCY_UNSPECIFIED = 0; // defaults to USD + CURRENCY_USD = 1; + CURRENCY_CNY = 2; +} + +// Shared reasoning effort levels — superset of all providers' actual API values. +// Used in ReasoningCommon.supported_efforts to describe catalog-level capabilities. +// Per-provider params use their own specific effort enums below. +enum ReasoningEffort { + REASONING_EFFORT_UNSPECIFIED = 0; + REASONING_EFFORT_NONE = 1; + REASONING_EFFORT_MINIMAL = 2; + REASONING_EFFORT_LOW = 3; + REASONING_EFFORT_MEDIUM = 4; + REASONING_EFFORT_HIGH = 5; + REASONING_EFFORT_MAX = 6; + REASONING_EFFORT_AUTO = 7; +} + +// Per-provider reasoning effort enums — match actual API values + +enum OpenAIReasoningEffort { + OPENAI_REASONING_EFFORT_UNSPECIFIED = 0; + OPENAI_REASONING_EFFORT_LOW = 1; + OPENAI_REASONING_EFFORT_MEDIUM = 2; + OPENAI_REASONING_EFFORT_HIGH = 3; +} + +enum AnthropicReasoningEffort { + ANTHROPIC_REASONING_EFFORT_UNSPECIFIED = 0; + ANTHROPIC_REASONING_EFFORT_LOW = 1; + ANTHROPIC_REASONING_EFFORT_MEDIUM = 2; + ANTHROPIC_REASONING_EFFORT_HIGH = 3; + ANTHROPIC_REASONING_EFFORT_MAX = 4; +} + +// ═══════════════════════════════════════════════════════════════════════════════ +// Shared Messages +// ═══════════════════════════════════════════════════════════════════════════════ + +message NumericRange { + double min = 1; + double max = 2; +} + +message PricePerToken { + optional double per_million_tokens = 1; // nullable — absent means unknown + Currency currency = 2; +} + +// Generic key-value metadata (replaces Record) +message Metadata { + map entries = 1; +} diff --git a/packages/provider-catalog/proto/v1/model.proto b/packages/provider-catalog/proto/v1/model.proto new file mode 100644 index 00000000000..7d453b16818 --- /dev/null +++ b/packages/provider-catalog/proto/v1/model.proto @@ -0,0 +1,110 @@ +syntax = "proto3"; +package catalog.v1; + +import "v1/common.proto"; + +// ═══════════════════════════════════════════════════════════════════════════════ +// Reasoning Support (model-level capabilities only) +// +// Describes WHAT reasoning capabilities a model supports. +// HOW to invoke reasoning is defined by the provider's reasoning_format +// (see provider.proto ProviderReasoningFormat). +// ═══════════════════════════════════════════════════════════════════════════════ + +message ThinkingTokenLimits { + optional uint32 min = 1; + optional uint32 max = 2; + optional uint32 default = 3; +} + +// Model-level reasoning capabilities +message ReasoningSupport { + optional ThinkingTokenLimits thinking_token_limits = 1; + repeated ReasoningEffort supported_efforts = 2; + optional bool interleaved = 3; +} + +// ═══════════════════════════════════════════════════════════════════════════════ +// Parameter Support +// ═══════════════════════════════════════════════════════════════════════════════ + +message RangedParameterSupport { + bool supported = 1; + optional NumericRange range = 2; +} + +message ParameterSupport { + optional RangedParameterSupport temperature = 1; + optional RangedParameterSupport top_p = 2; + optional RangedParameterSupport top_k = 3; + optional bool frequency_penalty = 4; + optional bool presence_penalty = 5; + optional bool max_tokens = 6; + optional bool stop_sequences = 7; + optional bool system_message = 8; +} + +// ═══════════════════════════════════════════════════════════════════════════════ +// Pricing +// ═══════════════════════════════════════════════════════════════════════════════ + +enum ImagePriceUnit { + IMAGE_PRICE_UNIT_UNSPECIFIED = 0; + IMAGE_PRICE_UNIT_IMAGE = 1; + IMAGE_PRICE_UNIT_PIXEL = 2; +} + +message ImagePrice { + double price = 1; + Currency currency = 2; + ImagePriceUnit unit = 3; +} + +message MinutePrice { + double price = 1; + Currency currency = 2; +} + +message ModelPricing { + PricePerToken input = 1; + PricePerToken output = 2; + optional PricePerToken cache_read = 3; + optional PricePerToken cache_write = 4; + optional ImagePrice per_image = 5; + optional MinutePrice per_minute = 6; +} + +// ═══════════════════════════════════════════════════════════════════════════════ +// Model Config +// ═══════════════════════════════════════════════════════════════════════════════ + +message ModelConfig { + string id = 1; + optional string name = 2; + optional string description = 3; + + repeated ModelCapability capabilities = 4; + repeated Modality input_modalities = 5; + repeated Modality output_modalities = 6; + + optional uint32 context_window = 7; + optional uint32 max_output_tokens = 8; + optional uint32 max_input_tokens = 9; + + optional ModelPricing pricing = 10; + optional ReasoningSupport reasoning = 11; + optional ParameterSupport parameter_support = 12; + + optional string family = 13; + optional string owned_by = 14; + optional bool open_weights = 15; + + repeated string alias = 16; + optional Metadata metadata = 17; +} + +// Top-level container +message ModelCatalog { + string version = 1; + repeated ModelConfig models = 2; +} diff --git a/packages/provider-catalog/proto/v1/provider.proto b/packages/provider-catalog/proto/v1/provider.proto new file mode 100644 index 00000000000..08548ce8164 --- /dev/null +++ b/packages/provider-catalog/proto/v1/provider.proto @@ -0,0 +1,223 @@ +syntax = "proto3"; +package catalog.v1; + +import "v1/common.proto"; + +// ═══════════════════════════════════════════════════════════════════════════════ +// API Features +// +// Flags describing which API features a provider supports. +// These control request construction at the SDK level. +// ═══════════════════════════════════════════════════════════════════════════════ + +message ApiFeatures { + // --- Request format flags --- + + // Whether the provider supports array-formatted content in messages + optional bool array_content = 1; + // Whether the provider supports stream_options for usage data + optional bool stream_options = 2; + + // --- Provider-specific parameter flags --- + + // Whether the provider supports the 'developer' role (OpenAI-specific) + optional bool developer_role = 3; + // Whether the provider supports service tier selection (OpenAI/Groq-specific) + optional bool service_tier = 4; + // Whether the provider supports verbosity settings (Gemini-specific) + optional bool verbosity = 5; + // Whether the provider supports enable_thinking parameter format + optional bool enable_thinking = 6; +} + +// ═══════════════════════════════════════════════════════════════════════════════ +// Provider Reasoning Format +// +// Describes HOW a provider's API expects reasoning parameters to be formatted. +// This is a provider-level concern — every model on a given provider uses the +// same parameter format. Model-level reasoning capabilities (effort levels, +// token limits) are in model.proto ReasoningSupport. +// +// The type names describe API parameter shapes, not providers or model families: +// openai_chat — { reasoningEffort: 'low'|'medium'|'high' } +// openai_responses — { reasoningEffort, reasoningSummary } +// anthropic — { thinking: { type, budgetTokens }, effort } +// gemini — { thinkingConfig: { thinkingBudget, thinkingLevel } } +// openrouter — { reasoning: { effort, enabled, exclude } } +// enable_thinking — { enable_thinking: bool, thinking_budget?: number } +// thinking_type — { thinking: { type: 'enabled'|'disabled'|'auto' } } +// dashscope — { enable_thinking: bool, incremental_output?: bool } +// self_hosted — { chat_template_kwargs: { thinking, enable_thinking } } +// ═══════════════════════════════════════════════════════════════════════════════ + +// --- Per-provider reasoning param definitions --- + +message OpenAIChatReasoningFormat { + optional OpenAIReasoningEffort reasoning_effort = 1; +} + +enum ResponsesSummaryMode { + RESPONSES_SUMMARY_MODE_UNSPECIFIED = 0; + RESPONSES_SUMMARY_MODE_AUTO = 1; + RESPONSES_SUMMARY_MODE_CONCISE = 2; + RESPONSES_SUMMARY_MODE_DETAILED = 3; +} + +message OpenAIResponsesReasoningFormat { + optional OpenAIReasoningEffort effort = 1; + optional ResponsesSummaryMode summary = 2; +} + +enum AnthropicThinkingType { + ANTHROPIC_THINKING_TYPE_UNSPECIFIED = 0; + ANTHROPIC_THINKING_TYPE_ENABLED = 1; + ANTHROPIC_THINKING_TYPE_DISABLED = 2; + ANTHROPIC_THINKING_TYPE_ADAPTIVE = 3; +} + +message AnthropicReasoningFormat { + optional AnthropicThinkingType type = 1; + optional uint32 budget_tokens = 2; + optional AnthropicReasoningEffort effort = 3; +} + +message GeminiThinkingConfig { + optional bool include_thoughts = 1; + optional uint32 thinking_budget = 2; +} + +enum GeminiThinkingLevel { + GEMINI_THINKING_LEVEL_UNSPECIFIED = 0; + GEMINI_THINKING_LEVEL_MINIMAL = 1; + GEMINI_THINKING_LEVEL_LOW = 2; + GEMINI_THINKING_LEVEL_MEDIUM = 3; + GEMINI_THINKING_LEVEL_HIGH = 4; +} + +message GeminiReasoningFormat { + oneof config { + GeminiThinkingConfig thinking_config = 1; + GeminiThinkingLevel thinking_level = 2; + } +} + +message OpenRouterReasoningFormat { + optional OpenAIReasoningEffort effort = 1; + optional uint32 max_tokens = 2; + optional bool exclude = 3; +} + +// enable_thinking + thinking_budget format (Qwen, Hunyuan, Silicon, etc.) +message EnableThinkingReasoningFormat { + optional bool enable_thinking = 1; + optional uint32 thinking_budget = 2; +} + +enum ThinkingType { + THINKING_TYPE_UNSPECIFIED = 0; + THINKING_TYPE_ENABLED = 1; + THINKING_TYPE_DISABLED = 2; + THINKING_TYPE_AUTO = 3; +} + +// thinking: { type } format (Doubao, Zhipu, DeepSeek, many aggregators) +message ThinkingTypeReasoningFormat { + optional ThinkingType thinking_type = 1; +} + +// DashScope-specific: enable_thinking + incremental_output +message DashscopeReasoningFormat { + optional bool enable_thinking = 1; + optional bool incremental_output = 2; +} + +// Self-hosted/Nvidia: chat_template_kwargs +message SelfHostedReasoningFormat { + optional bool enable_thinking = 1; + optional bool thinking = 2; +} + +// Discriminated union: which API parameter shape this provider uses for reasoning +message ProviderReasoningFormat { + oneof format { + OpenAIChatReasoningFormat openai_chat = 1; + OpenAIResponsesReasoningFormat openai_responses = 2; + AnthropicReasoningFormat anthropic = 3; + GeminiReasoningFormat gemini = 4; + OpenRouterReasoningFormat openrouter = 5; + EnableThinkingReasoningFormat enable_thinking = 6; + ThinkingTypeReasoningFormat thinking_type = 7; + DashscopeReasoningFormat dashscope = 8; + SelfHostedReasoningFormat self_hosted = 9; + } +} + +// ═══════════════════════════════════════════════════════════════════════════════ +// Provider Website +// ═══════════════════════════════════════════════════════════════════════════════ + +message ProviderWebsite { + optional string official = 1; + optional string docs = 2; + optional string api_key = 3; + optional string models = 4; +} + +// ═══════════════════════════════════════════════════════════════════════════════ +// Models API URLs +// ═══════════════════════════════════════════════════════════════════════════════ + +message ModelsApiUrls { + optional string default = 1; + optional string embedding = 2; + optional string reranker = 3; +} + +// ═══════════════════════════════════════════════════════════════════════════════ +// Provider Metadata (wraps generic Metadata + provider-specific fields) +// ═══════════════════════════════════════════════════════════════════════════════ + +message ProviderMetadata { + optional ProviderWebsite website = 1; +} + +// ═══════════════════════════════════════════════════════════════════════════════ +// Per-endpoint-type configuration +// ═══════════════════════════════════════════════════════════════════════════════ + +message EndpointConfig { + // Base URL for this endpoint type's API + optional string base_url = 1; + // URLs for fetching available models via this endpoint type + optional ModelsApiUrls models_api_urls = 2; + // How this endpoint type expects reasoning parameters to be formatted + optional ProviderReasoningFormat reasoning_format = 3; +} + +// ═══════════════════════════════════════════════════════════════════════════════ +// Provider Config +// ═══════════════════════════════════════════════════════════════════════════════ + +message ProviderConfig { + string id = 1; + string name = 2; + optional string description = 3; + + // Per-endpoint-type configuration (key = EndpointType enum value) + map endpoint_configs = 10; + + optional EndpointType default_chat_endpoint = 5; + optional ApiFeatures api_features = 6; + + optional ProviderMetadata metadata = 8; + + // Reserved field numbers from removed fields + reserved 4, 7, 9; + reserved "base_urls", "models_api_urls", "reasoning_format"; +} + +// Top-level container +message ProviderCatalog { + string version = 1; + repeated ProviderConfig providers = 2; +} diff --git a/packages/provider-catalog/proto/v1/provider_models.proto b/packages/provider-catalog/proto/v1/provider_models.proto new file mode 100644 index 00000000000..a3309352d8f --- /dev/null +++ b/packages/provider-catalog/proto/v1/provider_models.proto @@ -0,0 +1,63 @@ +syntax = "proto3"; +package catalog.v1; + +import "v1/common.proto"; +import "v1/model.proto"; + +// ═══════════════════════════════════════════════════════════════════════════════ +// Capability Override +// ═══════════════════════════════════════════════════════════════════════════════ + +message CapabilityOverride { + repeated ModelCapability add = 1; + repeated ModelCapability remove = 2; + repeated ModelCapability force = 3; +} + +// ═══════════════════════════════════════════════════════════════════════════════ +// Model Limits Override +// ═══════════════════════════════════════════════════════════════════════════════ + +message ModelLimits { + optional uint32 context_window = 1; + optional uint32 max_output_tokens = 2; + optional uint32 max_input_tokens = 3; + optional uint32 rate_limit = 4; +} + +// ═══════════════════════════════════════════════════════════════════════════════ +// Provider Model Override +// ═══════════════════════════════════════════════════════════════════════════════ + +message ProviderModelOverride { + // Identification + string provider_id = 1; + string model_id = 2; + optional string api_model_id = 3; + optional string model_variant = 4; + + // Overrides + optional CapabilityOverride capabilities = 5; + optional ModelLimits limits = 6; + optional ModelPricing pricing = 7; + optional ReasoningSupport reasoning = 8; + optional ParameterSupport parameter_support = 9; + + repeated EndpointType endpoint_types = 10; + repeated Modality input_modalities = 11; + repeated Modality output_modalities = 12; + + // Status + optional bool disabled = 13; + optional string replace_with = 14; + + // Metadata + optional string reason = 15; + uint32 priority = 16; // 0 = auto, 100+ = manual +} + +// Top-level container +message ProviderModelCatalog { + string version = 1; + repeated ProviderModelOverride overrides = 2; +} diff --git a/packages/provider-catalog/src/gen/v1/provider_pb.ts b/packages/provider-catalog/src/gen/v1/provider_pb.ts index a4bf80d264d..20537e8ba5f 100644 --- a/packages/provider-catalog/src/gen/v1/provider_pb.ts +++ b/packages/provider-catalog/src/gen/v1/provider_pb.ts @@ -14,7 +14,7 @@ import type { Message } from '@bufbuild/protobuf' export const file_v1_provider: GenFile = /*@__PURE__*/ fileDesc( - 'ChF2MS9wcm92aWRlci5wcm90bxIKY2F0YWxvZy52MSKfAgoLQXBpRmVhdHVyZXMSGgoNYXJyYXlfY29udGVudBgBIAEoCEgAiAEBEhsKDnN0cmVhbV9vcHRpb25zGAIgASgISAGIAQESGwoOZGV2ZWxvcGVyX3JvbGUYAyABKAhIAogBARIZCgxzZXJ2aWNlX3RpZXIYBCABKAhIA4gBARIWCgl2ZXJib3NpdHkYBSABKAhIBIgBARIcCg9lbmFibGVfdGhpbmtpbmcYBiABKAhIBYgBAUIQCg5fYXJyYXlfY29udGVudEIRCg9fc3RyZWFtX29wdGlvbnNCEQoPX2RldmVsb3Blcl9yb2xlQg8KDV9zZXJ2aWNlX3RpZXJCDAoKX3ZlcmJvc2l0eUISChBfZW5hYmxlX3RoaW5raW5nInIKGU9wZW5BSUNoYXRSZWFzb25pbmdGb3JtYXQSQAoQcmVhc29uaW5nX2VmZm9ydBgBIAEoDjIhLmNhdGFsb2cudjEuT3BlbkFJUmVhc29uaW5nRWZmb3J0SACIAQFCEwoRX3JlYXNvbmluZ19lZmZvcnQipwEKHk9wZW5BSVJlc3BvbnNlc1JlYXNvbmluZ0Zvcm1hdBI2CgZlZmZvcnQYASABKA4yIS5jYXRhbG9nLnYxLk9wZW5BSVJlYXNvbmluZ0VmZm9ydEgAiAEBEjYKB3N1bW1hcnkYAiABKA4yIC5jYXRhbG9nLnYxLlJlc3BvbnNlc1N1bW1hcnlNb2RlSAGIAQFCCQoHX2VmZm9ydEIKCghfc3VtbWFyeSLNAQoYQW50aHJvcGljUmVhc29uaW5nRm9ybWF0EjQKBHR5cGUYASABKA4yIS5jYXRhbG9nLnYxLkFudGhyb3BpY1RoaW5raW5nVHlwZUgAiAEBEhoKDWJ1ZGdldF90b2tlbnMYAiABKA1IAYgBARI5CgZlZmZvcnQYAyABKA4yJC5jYXRhbG9nLnYxLkFudGhyb3BpY1JlYXNvbmluZ0VmZm9ydEgCiAEBQgcKBV90eXBlQhAKDl9idWRnZXRfdG9rZW5zQgkKB19lZmZvcnQifAoUR2VtaW5pVGhpbmtpbmdDb25maWcSHQoQaW5jbHVkZV90aG91Z2h0cxgBIAEoCEgAiAEBEhwKD3RoaW5raW5nX2J1ZGdldBgCIAEoDUgBiAEBQhMKEV9pbmNsdWRlX3Rob3VnaHRzQhIKEF90aGlua2luZ19idWRnZXQimQEKFUdlbWluaVJlYXNvbmluZ0Zvcm1hdBI7Cg90aGlua2luZ19jb25maWcYASABKAsyIC5jYXRhbG9nLnYxLkdlbWluaVRoaW5raW5nQ29uZmlnSAASOQoOdGhpbmtpbmdfbGV2ZWwYAiABKA4yHy5jYXRhbG9nLnYxLkdlbWluaVRoaW5raW5nTGV2ZWxIAEIICgZjb25maWciqAEKGU9wZW5Sb3V0ZXJSZWFzb25pbmdGb3JtYXQSNgoGZWZmb3J0GAEgASgOMiEuY2F0YWxvZy52MS5PcGVuQUlSZWFzb25pbmdFZmZvcnRIAIgBARIXCgptYXhfdG9rZW5zGAIgASgNSAGIAQESFAoHZXhjbHVkZRgDIAEoCEgCiAEBQgkKB19lZmZvcnRCDQoLX21heF90b2tlbnNCCgoIX2V4Y2x1ZGUigwEKHUVuYWJsZVRoaW5raW5nUmVhc29uaW5nRm9ybWF0EhwKD2VuYWJsZV90aGlua2luZxgBIAEoCEgAiAEBEhwKD3RoaW5raW5nX2J1ZGdldBgCIAEoDUgBiAEBQhIKEF9lbmFibGVfdGhpbmtpbmdCEgoQX3RoaW5raW5nX2J1ZGdldCJlChtUaGlua2luZ1R5cGVSZWFzb25pbmdGb3JtYXQSNAoNdGhpbmtpbmdfdHlwZRgBIAEoDjIYLmNhdGFsb2cudjEuVGhpbmtpbmdUeXBlSACIAQFCEAoOX3RoaW5raW5nX3R5cGUihAEKGERhc2hzY29wZVJlYXNvbmluZ0Zvcm1hdBIcCg9lbmFibGVfdGhpbmtpbmcYASABKAhIAIgBARIfChJpbmNyZW1lbnRhbF9vdXRwdXQYAiABKAhIAYgBAUISChBfZW5hYmxlX3RoaW5raW5nQhUKE19pbmNyZW1lbnRhbF9vdXRwdXQicQoZU2VsZkhvc3RlZFJlYXNvbmluZ0Zvcm1hdBIcCg9lbmFibGVfdGhpbmtpbmcYASABKAhIAIgBARIVCgh0aGlua2luZxgCIAEoCEgBiAEBQhIKEF9lbmFibGVfdGhpbmtpbmdCCwoJX3RoaW5raW5nItcEChdQcm92aWRlclJlYXNvbmluZ0Zvcm1hdBI8CgtvcGVuYWlfY2hhdBgBIAEoCzIlLmNhdGFsb2cudjEuT3BlbkFJQ2hhdFJlYXNvbmluZ0Zvcm1hdEgAEkYKEG9wZW5haV9yZXNwb25zZXMYAiABKAsyKi5jYXRhbG9nLnYxLk9wZW5BSVJlc3BvbnNlc1JlYXNvbmluZ0Zvcm1hdEgAEjkKCWFudGhyb3BpYxgDIAEoCzIkLmNhdGFsb2cudjEuQW50aHJvcGljUmVhc29uaW5nRm9ybWF0SAASMwoGZ2VtaW5pGAQgASgLMiEuY2F0YWxvZy52MS5HZW1pbmlSZWFzb25pbmdGb3JtYXRIABI7CgpvcGVucm91dGVyGAUgASgLMiUuY2F0YWxvZy52MS5PcGVuUm91dGVyUmVhc29uaW5nRm9ybWF0SAASRAoPZW5hYmxlX3RoaW5raW5nGAYgASgLMikuY2F0YWxvZy52MS5FbmFibGVUaGlua2luZ1JlYXNvbmluZ0Zvcm1hdEgAEkAKDXRoaW5raW5nX3R5cGUYByABKAsyJy5jYXRhbG9nLnYxLlRoaW5raW5nVHlwZVJlYXNvbmluZ0Zvcm1hdEgAEjkKCWRhc2hzY29wZRgIIAEoCzIkLmNhdGFsb2cudjEuRGFzaHNjb3BlUmVhc29uaW5nRm9ybWF0SAASPAoLc2VsZl9ob3N0ZWQYCSABKAsyJS5jYXRhbG9nLnYxLlNlbGZIb3N0ZWRSZWFzb25pbmdGb3JtYXRIAEIICgZmb3JtYXQikwEKD1Byb3ZpZGVyV2Vic2l0ZRIVCghvZmZpY2lhbBgBIAEoCUgAiAEBEhEKBGRvY3MYAiABKAlIAYgBARIUCgdhcGlfa2V5GAMgASgJSAKIAQESEwoGbW9kZWxzGAQgASgJSAOIAQFCCwoJX29mZmljaWFsQgcKBV9kb2NzQgoKCF9hcGlfa2V5QgkKB19tb2RlbHMiewoNTW9kZWxzQXBpVXJscxIUCgdkZWZhdWx0GAEgASgJSACIAQESFgoJZW1iZWRkaW5nGAIgASgJSAGIAQESFQoIcmVyYW5rZXIYAyABKAlIAogBAUIKCghfZGVmYXVsdEIMCgpfZW1iZWRkaW5nQgsKCV9yZXJhbmtlciJRChBQcm92aWRlck1ldGFkYXRhEjEKB3dlYnNpdGUYASABKAsyGy5jYXRhbG9nLnYxLlByb3ZpZGVyV2Vic2l0ZUgAiAEBQgoKCF93ZWJzaXRlIscECg5Qcm92aWRlckNvbmZpZxIKCgJpZBgBIAEoCRIMCgRuYW1lGAIgASgJEhgKC2Rlc2NyaXB0aW9uGAMgASgJSACIAQESOwoJYmFzZV91cmxzGAQgAygLMiguY2F0YWxvZy52MS5Qcm92aWRlckNvbmZpZy5CYXNlVXJsc0VudHJ5EjwKFWRlZmF1bHRfY2hhdF9lbmRwb2ludBgFIAEoDjIYLmNhdGFsb2cudjEuRW5kcG9pbnRUeXBlSAGIAQESMgoMYXBpX2ZlYXR1cmVzGAYgASgLMhcuY2F0YWxvZy52MS5BcGlGZWF0dXJlc0gCiAEBEjcKD21vZGVsc19hcGlfdXJscxgHIAEoCzIZLmNhdGFsb2cudjEuTW9kZWxzQXBpVXJsc0gDiAEBEjMKCG1ldGFkYXRhGAggASgLMhwuY2F0YWxvZy52MS5Qcm92aWRlck1ldGFkYXRhSASIAQESQgoQcmVhc29uaW5nX2Zvcm1hdBgJIAEoCzIjLmNhdGFsb2cudjEuUHJvdmlkZXJSZWFzb25pbmdGb3JtYXRIBYgBARovCg1CYXNlVXJsc0VudHJ5EgsKA2tleRgBIAEoBRINCgV2YWx1ZRgCIAEoCToCOAFCDgoMX2Rlc2NyaXB0aW9uQhgKFl9kZWZhdWx0X2NoYXRfZW5kcG9pbnRCDwoNX2FwaV9mZWF0dXJlc0ISChBfbW9kZWxzX2FwaV91cmxzQgsKCV9tZXRhZGF0YUITChFfcmVhc29uaW5nX2Zvcm1hdCJRCg9Qcm92aWRlckNhdGFsb2cSDwoHdmVyc2lvbhgBIAEoCRItCglwcm92aWRlcnMYAiADKAsyGi5jYXRhbG9nLnYxLlByb3ZpZGVyQ29uZmlnKqgBChRSZXNwb25zZXNTdW1tYXJ5TW9kZRImCiJSRVNQT05TRVNfU1VNTUFSWV9NT0RFX1VOU1BFQ0lGSUVEEAASHwobUkVTUE9OU0VTX1NVTU1BUllfTU9ERV9BVVRPEAESIgoeUkVTUE9OU0VTX1NVTU1BUllfTU9ERV9DT05DSVNFEAISIwofUkVTUE9OU0VTX1NVTU1BUllfTU9ERV9ERVRBSUxFRBADKrEBChVBbnRocm9waWNUaGlua2luZ1R5cGUSJwojQU5USFJPUElDX1RISU5LSU5HX1RZUEVfVU5TUEVDSUZJRUQQABIjCh9BTlRIUk9QSUNfVEhJTktJTkdfVFlQRV9FTkFCTEVEEAESJAogQU5USFJPUElDX1RISU5LSU5HX1RZUEVfRElTQUJMRUQQAhIkCiBBTlRIUk9QSUNfVEhJTktJTkdfVFlQRV9BREFQVElWRRADKsABChNHZW1pbmlUaGlua2luZ0xldmVsEiUKIUdFTUlOSV9USElOS0lOR19MRVZFTF9VTlNQRUNJRklFRBAAEiEKHUdFTUlOSV9USElOS0lOR19MRVZFTF9NSU5JTUFMEAESHQoZR0VNSU5JX1RISU5LSU5HX0xFVkVMX0xPVxACEiAKHEdFTUlOSV9USElOS0lOR19MRVZFTF9NRURJVU0QAxIeChpHRU1JTklfVEhJTktJTkdfTEVWRUxfSElHSBAEKnwKDFRoaW5raW5nVHlwZRIdChlUSElOS0lOR19UWVBFX1VOU1BFQ0lGSUVEEAASGQoVVEhJTktJTkdfVFlQRV9FTkFCTEVEEAESGgoWVEhJTktJTkdfVFlQRV9ESVNBQkxFRBACEhYKElRISU5LSU5HX1RZUEVfQVVUTxADYgZwcm90bzM', + 'ChF2MS9wcm92aWRlci5wcm90bxIKY2F0YWxvZy52MSKfAgoLQXBpRmVhdHVyZXMSGgoNYXJyYXlfY29udGVudBgBIAEoCEgAiAEBEhsKDnN0cmVhbV9vcHRpb25zGAIgASgISAGIAQESGwoOZGV2ZWxvcGVyX3JvbGUYAyABKAhIAogBARIZCgxzZXJ2aWNlX3RpZXIYBCABKAhIA4gBARIWCgl2ZXJib3NpdHkYBSABKAhIBIgBARIcCg9lbmFibGVfdGhpbmtpbmcYBiABKAhIBYgBAUIQCg5fYXJyYXlfY29udGVudEIRCg9fc3RyZWFtX29wdGlvbnNCEQoPX2RldmVsb3Blcl9yb2xlQg8KDV9zZXJ2aWNlX3RpZXJCDAoKX3ZlcmJvc2l0eUISChBfZW5hYmxlX3RoaW5raW5nInIKGU9wZW5BSUNoYXRSZWFzb25pbmdGb3JtYXQSQAoQcmVhc29uaW5nX2VmZm9ydBgBIAEoDjIhLmNhdGFsb2cudjEuT3BlbkFJUmVhc29uaW5nRWZmb3J0SACIAQFCEwoRX3JlYXNvbmluZ19lZmZvcnQipwEKHk9wZW5BSVJlc3BvbnNlc1JlYXNvbmluZ0Zvcm1hdBI2CgZlZmZvcnQYASABKA4yIS5jYXRhbG9nLnYxLk9wZW5BSVJlYXNvbmluZ0VmZm9ydEgAiAEBEjYKB3N1bW1hcnkYAiABKA4yIC5jYXRhbG9nLnYxLlJlc3BvbnNlc1N1bW1hcnlNb2RlSAGIAQFCCQoHX2VmZm9ydEIKCghfc3VtbWFyeSLNAQoYQW50aHJvcGljUmVhc29uaW5nRm9ybWF0EjQKBHR5cGUYASABKA4yIS5jYXRhbG9nLnYxLkFudGhyb3BpY1RoaW5raW5nVHlwZUgAiAEBEhoKDWJ1ZGdldF90b2tlbnMYAiABKA1IAYgBARI5CgZlZmZvcnQYAyABKA4yJC5jYXRhbG9nLnYxLkFudGhyb3BpY1JlYXNvbmluZ0VmZm9ydEgCiAEBQgcKBV90eXBlQhAKDl9idWRnZXRfdG9rZW5zQgkKB19lZmZvcnQifAoUR2VtaW5pVGhpbmtpbmdDb25maWcSHQoQaW5jbHVkZV90aG91Z2h0cxgBIAEoCEgAiAEBEhwKD3RoaW5raW5nX2J1ZGdldBgCIAEoDUgBiAEBQhMKEV9pbmNsdWRlX3Rob3VnaHRzQhIKEF90aGlua2luZ19idWRnZXQimQEKFUdlbWluaVJlYXNvbmluZ0Zvcm1hdBI7Cg90aGlua2luZ19jb25maWcYASABKAsyIC5jYXRhbG9nLnYxLkdlbWluaVRoaW5raW5nQ29uZmlnSAASOQoOdGhpbmtpbmdfbGV2ZWwYAiABKA4yHy5jYXRhbG9nLnYxLkdlbWluaVRoaW5raW5nTGV2ZWxIAEIICgZjb25maWciqAEKGU9wZW5Sb3V0ZXJSZWFzb25pbmdGb3JtYXQSNgoGZWZmb3J0GAEgASgOMiEuY2F0YWxvZy52MS5PcGVuQUlSZWFzb25pbmdFZmZvcnRIAIgBARIXCgptYXhfdG9rZW5zGAIgASgNSAGIAQESFAoHZXhjbHVkZRgDIAEoCEgCiAEBQgkKB19lZmZvcnRCDQoLX21heF90b2tlbnNCCgoIX2V4Y2x1ZGUigwEKHUVuYWJsZVRoaW5raW5nUmVhc29uaW5nRm9ybWF0EhwKD2VuYWJsZV90aGlua2luZxgBIAEoCEgAiAEBEhwKD3RoaW5raW5nX2J1ZGdldBgCIAEoDUgBiAEBQhIKEF9lbmFibGVfdGhpbmtpbmdCEgoQX3RoaW5raW5nX2J1ZGdldCJlChtUaGlua2luZ1R5cGVSZWFzb25pbmdGb3JtYXQSNAoNdGhpbmtpbmdfdHlwZRgBIAEoDjIYLmNhdGFsb2cudjEuVGhpbmtpbmdUeXBlSACIAQFCEAoOX3RoaW5raW5nX3R5cGUihAEKGERhc2hzY29wZVJlYXNvbmluZ0Zvcm1hdBIcCg9lbmFibGVfdGhpbmtpbmcYASABKAhIAIgBARIfChJpbmNyZW1lbnRhbF9vdXRwdXQYAiABKAhIAYgBAUISChBfZW5hYmxlX3RoaW5raW5nQhUKE19pbmNyZW1lbnRhbF9vdXRwdXQicQoZU2VsZkhvc3RlZFJlYXNvbmluZ0Zvcm1hdBIcCg9lbmFibGVfdGhpbmtpbmcYASABKAhIAIgBARIVCgh0aGlua2luZxgCIAEoCEgBiAEBQhIKEF9lbmFibGVfdGhpbmtpbmdCCwoJX3RoaW5raW5nItcEChdQcm92aWRlclJlYXNvbmluZ0Zvcm1hdBI8CgtvcGVuYWlfY2hhdBgBIAEoCzIlLmNhdGFsb2cudjEuT3BlbkFJQ2hhdFJlYXNvbmluZ0Zvcm1hdEgAEkYKEG9wZW5haV9yZXNwb25zZXMYAiABKAsyKi5jYXRhbG9nLnYxLk9wZW5BSVJlc3BvbnNlc1JlYXNvbmluZ0Zvcm1hdEgAEjkKCWFudGhyb3BpYxgDIAEoCzIkLmNhdGFsb2cudjEuQW50aHJvcGljUmVhc29uaW5nRm9ybWF0SAASMwoGZ2VtaW5pGAQgASgLMiEuY2F0YWxvZy52MS5HZW1pbmlSZWFzb25pbmdGb3JtYXRIABI7CgpvcGVucm91dGVyGAUgASgLMiUuY2F0YWxvZy52MS5PcGVuUm91dGVyUmVhc29uaW5nRm9ybWF0SAASRAoPZW5hYmxlX3RoaW5raW5nGAYgASgLMikuY2F0YWxvZy52MS5FbmFibGVUaGlua2luZ1JlYXNvbmluZ0Zvcm1hdEgAEkAKDXRoaW5raW5nX3R5cGUYByABKAsyJy5jYXRhbG9nLnYxLlRoaW5raW5nVHlwZVJlYXNvbmluZ0Zvcm1hdEgAEjkKCWRhc2hzY29wZRgIIAEoCzIkLmNhdGFsb2cudjEuRGFzaHNjb3BlUmVhc29uaW5nRm9ybWF0SAASPAoLc2VsZl9ob3N0ZWQYCSABKAsyJS5jYXRhbG9nLnYxLlNlbGZIb3N0ZWRSZWFzb25pbmdGb3JtYXRIAEIICgZmb3JtYXQikwEKD1Byb3ZpZGVyV2Vic2l0ZRIVCghvZmZpY2lhbBgBIAEoCUgAiAEBEhEKBGRvY3MYAiABKAlIAYgBARIUCgdhcGlfa2V5GAMgASgJSAKIAQESEwoGbW9kZWxzGAQgASgJSAOIAQFCCwoJX29mZmljaWFsQgcKBV9kb2NzQgoKCF9hcGlfa2V5QgkKB19tb2RlbHMiewoNTW9kZWxzQXBpVXJscxIUCgdkZWZhdWx0GAEgASgJSACIAQESFgoJZW1iZWRkaW5nGAIgASgJSAGIAQESFQoIcmVyYW5rZXIYAyABKAlIAogBAUIKCghfZGVmYXVsdEIMCgpfZW1iZWRkaW5nQgsKCV9yZXJhbmtlciJRChBQcm92aWRlck1ldGFkYXRhEjEKB3dlYnNpdGUYASABKAsyGy5jYXRhbG9nLnYxLlByb3ZpZGVyV2Vic2l0ZUgAiAEBQgoKCF93ZWJzaXRlItoBCg5FbmRwb2ludENvbmZpZxIVCghiYXNlX3VybBgBIAEoCUgAiAEBEjcKD21vZGVsc19hcGlfdXJscxgCIAEoCzIZLmNhdGFsb2cudjEuTW9kZWxzQXBpVXJsc0gBiAEBEkIKEHJlYXNvbmluZ19mb3JtYXQYAyABKAsyIy5jYXRhbG9nLnYxLlByb3ZpZGVyUmVhc29uaW5nRm9ybWF0SAKIAQFCCwoJX2Jhc2VfdXJsQhIKEF9tb2RlbHNfYXBpX3VybHNCEwoRX3JlYXNvbmluZ19mb3JtYXQikgQKDlByb3ZpZGVyQ29uZmlnEgoKAmlkGAEgASgJEgwKBG5hbWUYAiABKAkSGAoLZGVzY3JpcHRpb24YAyABKAlIAIgBARJJChBlbmRwb2ludF9jb25maWdzGAogAygLMi8uY2F0YWxvZy52MS5Qcm92aWRlckNvbmZpZy5FbmRwb2ludENvbmZpZ3NFbnRyeRI8ChVkZWZhdWx0X2NoYXRfZW5kcG9pbnQYBSABKA4yGC5jYXRhbG9nLnYxLkVuZHBvaW50VHlwZUgBiAEBEjIKDGFwaV9mZWF0dXJlcxgGIAEoCzIXLmNhdGFsb2cudjEuQXBpRmVhdHVyZXNIAogBARIzCghtZXRhZGF0YRgIIAEoCzIcLmNhdGFsb2cudjEuUHJvdmlkZXJNZXRhZGF0YUgDiAEBGlIKFEVuZHBvaW50Q29uZmlnc0VudHJ5EgsKA2tleRgBIAEoBRIpCgV2YWx1ZRgCIAEoCzIaLmNhdGFsb2cudjEuRW5kcG9pbnRDb25maWc6AjgBQg4KDF9kZXNjcmlwdGlvbkIYChZfZGVmYXVsdF9jaGF0X2VuZHBvaW50Qg8KDV9hcGlfZmVhdHVyZXNCCwoJX21ldGFkYXRhSgQIBBAFSgQIBxAISgQICRAKUgliYXNlX3VybHNSD21vZGVsc19hcGlfdXJsc1IQcmVhc29uaW5nX2Zvcm1hdCJRCg9Qcm92aWRlckNhdGFsb2cSDwoHdmVyc2lvbhgBIAEoCRItCglwcm92aWRlcnMYAiADKAsyGi5jYXRhbG9nLnYxLlByb3ZpZGVyQ29uZmlnKqgBChRSZXNwb25zZXNTdW1tYXJ5TW9kZRImCiJSRVNQT05TRVNfU1VNTUFSWV9NT0RFX1VOU1BFQ0lGSUVEEAASHwobUkVTUE9OU0VTX1NVTU1BUllfTU9ERV9BVVRPEAESIgoeUkVTUE9OU0VTX1NVTU1BUllfTU9ERV9DT05DSVNFEAISIwofUkVTUE9OU0VTX1NVTU1BUllfTU9ERV9ERVRBSUxFRBADKrEBChVBbnRocm9waWNUaGlua2luZ1R5cGUSJwojQU5USFJPUElDX1RISU5LSU5HX1RZUEVfVU5TUEVDSUZJRUQQABIjCh9BTlRIUk9QSUNfVEhJTktJTkdfVFlQRV9FTkFCTEVEEAESJAogQU5USFJPUElDX1RISU5LSU5HX1RZUEVfRElTQUJMRUQQAhIkCiBBTlRIUk9QSUNfVEhJTktJTkdfVFlQRV9BREFQVElWRRADKsABChNHZW1pbmlUaGlua2luZ0xldmVsEiUKIUdFTUlOSV9USElOS0lOR19MRVZFTF9VTlNQRUNJRklFRBAAEiEKHUdFTUlOSV9USElOS0lOR19MRVZFTF9NSU5JTUFMEAESHQoZR0VNSU5JX1RISU5LSU5HX0xFVkVMX0xPVxACEiAKHEdFTUlOSV9USElOS0lOR19MRVZFTF9NRURJVU0QAxIeChpHRU1JTklfVEhJTktJTkdfTEVWRUxfSElHSBAEKnwKDFRoaW5raW5nVHlwZRIdChlUSElOS0lOR19UWVBFX1VOU1BFQ0lGSUVEEAASGQoVVEhJTktJTkdfVFlQRV9FTkFCTEVEEAESGgoWVEhJTktJTkdfVFlQRV9ESVNBQkxFRBACEhYKElRISU5LSU5HX1RZUEVfQVVUTxADYgZwcm90bzM', [file_v1_common] ) @@ -478,6 +478,38 @@ export type ProviderMetadata = Message<'catalog.v1.ProviderMetadata'> & { */ export const ProviderMetadataSchema: GenMessage = /*@__PURE__*/ messageDesc(file_v1_provider, 14) +/** + * @generated from message catalog.v1.EndpointConfig + */ +export type EndpointConfig = Message<'catalog.v1.EndpointConfig'> & { + /** + * Base URL for this endpoint type's API + * + * @generated from field: optional string base_url = 1; + */ + baseUrl?: string + + /** + * URLs for fetching available models via this endpoint type + * + * @generated from field: optional catalog.v1.ModelsApiUrls models_api_urls = 2; + */ + modelsApiUrls?: ModelsApiUrls + + /** + * How this endpoint type expects reasoning parameters to be formatted + * + * @generated from field: optional catalog.v1.ProviderReasoningFormat reasoning_format = 3; + */ + reasoningFormat?: ProviderReasoningFormat +} + +/** + * Describes the message catalog.v1.EndpointConfig. + * Use `create(EndpointConfigSchema)` to create a new message. + */ +export const EndpointConfigSchema: GenMessage = /*@__PURE__*/ messageDesc(file_v1_provider, 15) + /** * @generated from message catalog.v1.ProviderConfig */ @@ -498,13 +530,11 @@ export type ProviderConfig = Message<'catalog.v1.ProviderConfig'> & { description?: string /** - * Base URLs keyed by endpoint type - * - * key = EndpointType enum value + * Per-endpoint-type configuration (key = EndpointType enum value) * - * @generated from field: map base_urls = 4; + * @generated from field: map endpoint_configs = 10; */ - baseUrls: { [key: number]: string } + endpointConfigs: { [key: number]: EndpointConfig } /** * @generated from field: optional catalog.v1.EndpointType default_chat_endpoint = 5; @@ -516,29 +546,17 @@ export type ProviderConfig = Message<'catalog.v1.ProviderConfig'> & { */ apiFeatures?: ApiFeatures - /** - * @generated from field: optional catalog.v1.ModelsApiUrls models_api_urls = 7; - */ - modelsApiUrls?: ModelsApiUrls - /** * @generated from field: optional catalog.v1.ProviderMetadata metadata = 8; */ metadata?: ProviderMetadata - - /** - * How this provider's API expects reasoning parameters to be formatted - * - * @generated from field: optional catalog.v1.ProviderReasoningFormat reasoning_format = 9; - */ - reasoningFormat?: ProviderReasoningFormat } /** * Describes the message catalog.v1.ProviderConfig. * Use `create(ProviderConfigSchema)` to create a new message. */ -export const ProviderConfigSchema: GenMessage = /*@__PURE__*/ messageDesc(file_v1_provider, 15) +export const ProviderConfigSchema: GenMessage = /*@__PURE__*/ messageDesc(file_v1_provider, 16) /** * Top-level container @@ -561,7 +579,7 @@ export type ProviderCatalog = Message<'catalog.v1.ProviderCatalog'> & { * Describes the message catalog.v1.ProviderCatalog. * Use `create(ProviderCatalogSchema)` to create a new message. */ -export const ProviderCatalogSchema: GenMessage = /*@__PURE__*/ messageDesc(file_v1_provider, 16) +export const ProviderCatalogSchema: GenMessage = /*@__PURE__*/ messageDesc(file_v1_provider, 17) /** * @generated from enum catalog.v1.ResponsesSummaryMode diff --git a/packages/provider-catalog/src/schemas/provider.ts b/packages/provider-catalog/src/schemas/provider.ts index cdd69717fc0..c5fee4dfc88 100644 --- a/packages/provider-catalog/src/schemas/provider.ts +++ b/packages/provider-catalog/src/schemas/provider.ts @@ -177,6 +177,25 @@ export const ProviderWebsiteSchema = z.object({ }) }) +/** Per-endpoint-type configuration in catalog */ +export const CatalogEndpointConfigSchema = z.object({ + /** Base URL for this endpoint type's API */ + baseUrl: z.url().optional(), + /** URLs for fetching available models via this endpoint type */ + modelsApiUrls: z + .object({ + /** Default models listing endpoint */ + default: z.url().optional(), + /** Embedding models listing endpoint (if separate from default) */ + embedding: z.url().optional(), + /** Reranker models listing endpoint (if separate from default) */ + reranker: z.url().optional() + }) + .optional(), + /** How this endpoint type expects reasoning parameters to be formatted */ + reasoningFormat: ProviderReasoningFormatSchema.optional() +}) + export const ProviderConfigSchema = z .object({ /** Unique provider identifier */ @@ -185,37 +204,24 @@ export const ProviderConfigSchema = z name: z.string(), /** Provider description */ description: z.string().optional(), - /** Base URLs keyed by endpoint type */ - baseUrls: z.record(EndpointTypeSchema, z.url()).optional(), - /** Default endpoint type for chat requests (must exist in baseUrls) */ + /** Per-endpoint-type configuration */ + endpointConfigs: z.record(EndpointTypeSchema, CatalogEndpointConfigSchema).optional(), + /** Default endpoint type for chat requests (must exist in endpointConfigs) */ defaultChatEndpoint: EndpointTypeSchema.optional(), /** API feature flags controlling request construction */ apiFeatures: ApiFeaturesSchema.optional(), - /** URLs for fetching available models, separated by model category */ - modelsApiUrls: z - .object({ - /** Default models listing endpoint */ - default: z.url().optional(), - /** Embedding models listing endpoint (if separate from default) */ - embedding: z.url().optional(), - /** Reranker models listing endpoint (if separate from default) */ - reranker: z.url().optional() - }) - .optional(), /** Additional metadata including website URLs */ - metadata: MetadataSchema.and(ProviderWebsiteSchema), - /** How this provider's API expects reasoning parameters to be formatted */ - reasoningFormat: ProviderReasoningFormatSchema.optional() + metadata: MetadataSchema.and(ProviderWebsiteSchema) }) .refine( (data) => { - if (data.defaultChatEndpoint && data.baseUrls) { - return data.defaultChatEndpoint in data.baseUrls + if (data.defaultChatEndpoint && data.endpointConfigs) { + return data.defaultChatEndpoint in data.endpointConfigs } return true }, { - message: 'defaultChatEndpoint must exist as a key in baseUrls' + message: 'defaultChatEndpoint must exist as a key in endpointConfigs' } ) @@ -227,5 +233,6 @@ export const ProviderListSchema = z.object({ export { ENDPOINT_TYPE } from './enums' export type ApiFeatures = z.infer export type ProviderReasoningFormat = z.infer +export type CatalogEndpointConfig = z.infer export type ProviderConfig = z.infer export type ProviderList = z.infer diff --git a/packages/shared/data/api/schemas/providers.ts b/packages/shared/data/api/schemas/providers.ts index e8d4f793631..33662e8f0f8 100644 --- a/packages/shared/data/api/schemas/providers.ts +++ b/packages/shared/data/api/schemas/providers.ts @@ -11,9 +11,9 @@ import type { ApiFeatures, ApiKeyEntry, AuthConfig, + EndpointConfig, Provider, - ProviderSettings, - ReasoningFormatType + ProviderSettings } from '../../types/provider' export interface ListProvidersQuery { @@ -25,14 +25,10 @@ export interface ListProvidersQuery { interface ProviderMutableFields { /** Display name */ name?: string - /** Base URL mapping (EndpointType → baseURL) */ - baseUrls?: Partial> - /** Model list API URLs */ - modelsApiUrls?: Record + /** Per-endpoint-type configuration (baseUrl, reasoningFormatType, modelsApiUrls) */ + endpointConfigs?: Partial> /** Default text generation endpoint (numeric EndpointType enum value) */ defaultChatEndpoint?: EndpointType - /** Reasoning format mapping by endpoint type */ - reasoningFormatTypes?: Partial> /** API keys */ apiKeys?: ApiKeyEntry[] /** Authentication configuration */ diff --git a/packages/shared/data/types/provider.ts b/packages/shared/data/types/provider.ts index 7144c1b8587..f58249a01f6 100644 --- a/packages/shared/data/types/provider.ts +++ b/packages/shared/data/types/provider.ts @@ -75,9 +75,7 @@ export const ApiKeyEntrySchema = z.object({ /** User-friendly label */ label: z.string().optional(), /** Whether this key is enabled */ - isEnabled: z.boolean(), - /** Creation timestamp */ - createdAt: z.number().optional() + isEnabled: z.boolean() }) export type ApiKeyEntry = z.infer @@ -202,6 +200,27 @@ export const REASONING_FORMAT_TYPES = [ export const ReasoningFormatTypeSchema = z.enum(REASONING_FORMAT_TYPES) export type ReasoningFormatType = z.infer +/** URLs for fetching available models, separated by model category */ +export const ModelsApiUrlsSchema = z.object({ + default: z.string().optional(), + embedding: z.string().optional(), + reranker: z.string().optional() +}) + +export type ModelsApiUrls = z.infer + +/** Per-endpoint-type configuration */ +export const EndpointConfigSchema = z.object({ + /** Base URL for this endpoint type's API */ + baseUrl: z.string().optional(), + /** How this endpoint type expects reasoning parameters */ + reasoningFormatType: ReasoningFormatTypeSchema.optional(), + /** URLs for fetching available models via this endpoint type */ + modelsApiUrls: ModelsApiUrlsSchema.optional() +}) + +export type EndpointConfig = z.infer + export const ProviderSchema = z.object({ /** Provider ID */ id: z.string(), @@ -211,17 +230,10 @@ export const ProviderSchema = z.object({ name: z.string(), /** Description */ description: z.string().optional(), - /** Base URL mapping (endpoint type → baseURL), sparse — only populated endpoints have entries */ - baseUrls: z.record(EndpointTypeSchema, z.url()).optional() as z.ZodOptional< - z.ZodType>> + /** Per-endpoint-type configuration (baseUrl, reasoningFormatType, modelsApiUrls) */ + endpointConfigs: z.record(EndpointTypeSchema, EndpointConfigSchema).optional() as z.ZodOptional< + z.ZodType>> >, - modelsApiUrls: z - .object({ - default: z.url().optional(), - embedding: z.url().optional(), - reranker: z.url().optional() - }) - .optional(), /** Default text generation endpoint type */ defaultChatEndpoint: EndpointTypeSchema.optional(), /** API Keys (without actual key values) */ @@ -234,10 +246,6 @@ export const ProviderSchema = z.object({ settings: ProviderSettingsSchema, /** Website links (official, apiKey, docs, models) */ websites: ProviderWebsitesSchema.optional(), - /** How this provider's API expects reasoning parameters per endpoint type */ - reasoningFormatTypes: z.record(EndpointTypeSchema, ReasoningFormatTypeSchema).optional() as z.ZodOptional< - z.ZodType>> - >, /** Whether this provider is enabled */ isEnabled: z.boolean() }) diff --git a/packages/shared/data/utils/modelMerger.ts b/packages/shared/data/utils/modelMerger.ts index df8ea678871..3d3527a000a 100644 --- a/packages/shared/data/utils/modelMerger.ts +++ b/packages/shared/data/utils/modelMerger.ts @@ -18,14 +18,20 @@ import * as z from 'zod' import type { Model, RuntimeModelPricing, RuntimeReasoning } from '../types/model' import { createUniqueModelId } from '../types/model' -import type { Provider, ProviderSettings, ReasoningFormatType, RuntimeApiFeatures } from '../types/provider' +import type { + EndpointConfig, + Provider, + ProviderSettings, + ReasoningFormatType, + RuntimeApiFeatures +} from '../types/provider' import { ApiFeaturesSchema, ApiKeyEntrySchema, DEFAULT_API_FEATURES, DEFAULT_PROVIDER_SETTINGS, - ProviderSettingsSchema, - ReasoningFormatTypeSchema + EndpointConfigSchema, + ProviderSettingsSchema } from '../types/provider' export type { ProtoModelConfig as CatalogModel, ProtoProviderModelOverride as CatalogProviderModelOverride } @@ -72,9 +78,8 @@ const UserProviderRowSchema = z.object({ providerId: z.string(), presetProviderId: z.string().nullish(), name: z.string(), - baseUrls: z.record(z.string(), z.string()).nullish(), + endpointConfigs: z.record(z.string(), EndpointConfigSchema).nullish(), defaultChatEndpoint: z.nativeEnum(EndpointType).nullish(), - reasoningFormatTypes: z.record(z.string(), ReasoningFormatTypeSchema).nullish(), apiKeys: z.array(ApiKeyEntrySchema.pick({ id: true, key: true, label: true, isEnabled: true })).nullish(), authConfig: z.object({ type: z.string() }).catchall(z.unknown()).nullish(), apiFeatures: ApiFeaturesSchema.nullish(), @@ -328,27 +333,9 @@ export function mergeProviderConfig( const providerId = userProvider?.providerId ?? presetProvider!.id - // Merge baseUrls — proto uses map, convert to Record - const presetBaseUrls: Record = {} - if (presetProvider?.baseUrls) { - for (const [k, v] of Object.entries(presetProvider.baseUrls)) { - presetBaseUrls[k] = v - } - } - const baseUrls: Record = { - ...presetBaseUrls, - ...userProvider?.baseUrls - } - - const presetReasoningFormat = extractReasoningFormatType(presetProvider?.reasoningFormat) - const presetReasoningFormatTypes = buildReasoningFormatTypes( - presetProvider?.defaultChatEndpoint, - presetReasoningFormat - ) - const reasoningFormatTypes = { - ...presetReasoningFormatTypes, - ...userProvider?.reasoningFormatTypes - } + // Merge endpointConfigs — build from preset then overlay user config + const presetEndpointConfigs = buildPresetEndpointConfigs(presetProvider) + const endpointConfigs = mergeEndpointConfigs(presetEndpointConfigs, userProvider?.endpointConfigs) // Merge API features (catalog now uses the same field names) const apiFeatures: RuntimeApiFeatures = { @@ -382,13 +369,12 @@ export function mergeProviderConfig( presetProviderId: userProvider?.presetProviderId ?? undefined, name: userProvider?.name ?? presetProvider?.name ?? providerId, description: presetProvider?.description, - baseUrls, + endpointConfigs: Object.keys(endpointConfigs).length > 0 ? endpointConfigs : undefined, defaultChatEndpoint: userProvider?.defaultChatEndpoint ?? presetProvider?.defaultChatEndpoint, apiKeys, authType, apiFeatures, settings, - reasoningFormatTypes: Object.keys(reasoningFormatTypes).length > 0 ? reasoningFormatTypes : undefined, isEnabled: userProvider?.isEnabled ?? true } } @@ -446,17 +432,88 @@ function isChatReasoningEndpointType(endpointType: EndpointType): boolean { return CHAT_REASONING_ENDPOINT_PRIORITY.includes(endpointType) } -function buildReasoningFormatTypes( - defaultChatEndpoint: EndpointType | undefined, - reasoningFormatType: ReasoningFormatType | undefined -): Partial> { - if (defaultChatEndpoint === undefined || !reasoningFormatType) { - return {} +/** + * Build endpointConfigs from preset provider's proto data. + * Converts proto endpointConfigs (with proto message types) into runtime EndpointConfig. + */ +function buildPresetEndpointConfigs( + presetProvider: ProtoProviderConfig | null +): Partial> { + if (!presetProvider) return {} + + const configs: Partial> = {} + + for (const [k, protoConfig] of Object.entries(presetProvider.endpointConfigs)) { + const ep = Number(k) as EndpointType + const config: EndpointConfig = {} + + if (protoConfig.baseUrl) { + config.baseUrl = protoConfig.baseUrl + } + + // Convert proto ModelsApiUrls message to plain object + if (protoConfig.modelsApiUrls) { + const modelsApiUrls: Record = {} + if (protoConfig.modelsApiUrls.default) modelsApiUrls.default = protoConfig.modelsApiUrls.default + if (protoConfig.modelsApiUrls.embedding) modelsApiUrls.embedding = protoConfig.modelsApiUrls.embedding + if (protoConfig.modelsApiUrls.reranker) modelsApiUrls.reranker = protoConfig.modelsApiUrls.reranker + if (Object.keys(modelsApiUrls).length > 0) { + config.modelsApiUrls = modelsApiUrls + } + } + + // Convert proto ProviderReasoningFormat to runtime type string + const reasoningFormatType = extractReasoningFormatType(protoConfig.reasoningFormat) + if (reasoningFormatType) { + config.reasoningFormatType = reasoningFormatType + } + + if (Object.keys(config).length > 0) { + configs[ep] = config + } } - return { - [defaultChatEndpoint]: reasoningFormatType + return configs +} + +/** + * Deep-merge two endpointConfigs. User config takes priority per field within each endpoint. + */ +function mergeEndpointConfigs( + preset: Partial> | null | undefined, + user: Partial> | null | undefined +): Partial> { + const result: Partial> = {} + + const allKeys = new Set([...Object.keys(preset ?? {}), ...Object.keys(user ?? {})]) + + for (const k of allKeys) { + const endpointType = Number(k) as EndpointType + const presetConfig = preset?.[endpointType] + const userConfig = user?.[endpointType] + result[endpointType] = { + ...presetConfig, + ...userConfig + } + } + + return result +} + +/** + * Extract reasoningFormatTypes map from endpointConfigs (for backward-compatible access) + */ +export function extractReasoningFormatTypes( + endpointConfigs: Partial> | null | undefined +): Partial> | undefined { + if (!endpointConfigs) return undefined + const result: Partial> = {} + for (const [k, v] of Object.entries(endpointConfigs)) { + if (v?.reasoningFormatType) { + result[Number(k) as EndpointType] = v.reasoningFormatType + } } + return Object.keys(result).length > 0 ? result : undefined } function resolveReasoningEndpointType( diff --git a/src/main/data/db/schemas/userProvider.ts b/src/main/data/db/schemas/userProvider.ts index 8506fbbb5d2..c40b7d04785 100644 --- a/src/main/data/db/schemas/userProvider.ts +++ b/src/main/data/db/schemas/userProvider.ts @@ -17,12 +17,12 @@ import { ApiKeyEntrySchema, type AuthConfig, AuthConfigSchema, + type EndpointConfig, + EndpointConfigSchema, type ProviderSettings, ProviderSettingsSchema, type ProviderWebsites, - ProviderWebsitesSchema, - type ReasoningFormatType, - ReasoningFormatTypeSchema + ProviderWebsitesSchema } from '@shared/data/types/provider' import { index, integer, sqliteTable, text } from 'drizzle-orm/sqlite-core' import { createSchemaFactory } from 'drizzle-zod' @@ -47,9 +47,8 @@ export const userProviderTable = sqliteTable( name: text().notNull(), - baseUrls: text('base_urls', { mode: 'json' }).$type>>(), - - modelsApiUrls: text('models_api_urls', { mode: 'json' }).$type>(), + /** Per-endpoint-type configuration (baseUrl, reasoningFormatType, modelsApiUrls) */ + endpointConfigs: text('endpoint_configs', { mode: 'json' }).$type>>(), /** Default text generation endpoint (when supporting multiple) */ defaultChatEndpoint: text().$type(), @@ -66,11 +65,6 @@ export const userProviderTable = sqliteTable( /** Provider-specific settings as JSON */ providerSettings: text({ mode: 'json' }).$type(), - /** How this provider's API expects reasoning parameters for each endpoint type */ - reasoningFormatTypes: text('reasoning_format_types', { mode: 'json' }).$type< - Partial> - >(), - /** Website links (official, apiKey, docs, models) */ websites: text({ mode: 'json' }).$type(), @@ -93,9 +87,7 @@ export type UserProvider = typeof userProviderTable.$inferSelect export type NewUserProvider = typeof userProviderTable.$inferInsert const jsonColumnOverrides = { - baseUrls: () => z.record(z.string(), z.string()).nullable(), - modelsApiUrls: () => z.record(z.string(), z.string()).nullable(), - reasoningFormatTypes: () => z.record(z.string(), ReasoningFormatTypeSchema).nullable(), + endpointConfigs: () => z.record(z.string(), EndpointConfigSchema).nullable(), apiKeys: () => z.array(ApiKeyEntrySchema).nullable(), authConfig: () => AuthConfigSchema.nullable(), apiFeatures: () => ApiFeaturesSchema.nullable(), diff --git a/src/main/data/migration/v2/migrators/mappings/ProviderModelMappings.ts b/src/main/data/migration/v2/migrators/mappings/ProviderModelMappings.ts index be18f2f55cc..18945f61952 100644 --- a/src/main/data/migration/v2/migrators/mappings/ProviderModelMappings.ts +++ b/src/main/data/migration/v2/migrators/mappings/ProviderModelMappings.ts @@ -16,6 +16,7 @@ import type { ApiFeatures, ApiKeyEntry, AuthConfig, + EndpointConfig, ProviderSettings, ReasoningFormatType } from '@shared/data/types/provider' @@ -159,9 +160,8 @@ export function transformProvider( providerId: legacy.id, presetProviderId: SYSTEM_PROVIDER_IDS.has(legacy.id) ? legacy.id : null, name: legacy.name, - baseUrls: buildBaseUrls(legacy, endpointType), + endpointConfigs: buildEndpointConfigs(legacy, endpointType), defaultChatEndpoint: endpointType ?? null, - reasoningFormatTypes: buildReasoningFormatTypes(endpointType, REASONING_FORMAT_MAP[legacy.type]), apiKeys: buildApiKeys(legacy.apiKey), authConfig: buildAuthConfig(legacy, settings), apiFeatures: buildApiFeatures(legacy), @@ -171,31 +171,28 @@ export function transformProvider( } } -function buildBaseUrls(legacy: LegacyProvider, endpointType: EndpointType | undefined): NewUserProvider['baseUrls'] { - const urls: Partial> = {} +function buildEndpointConfigs( + legacy: LegacyProvider, + endpointType: EndpointType | undefined +): NewUserProvider['endpointConfigs'] { + const configs: Partial> = {} if (legacy.apiHost && endpointType !== undefined) { - urls[endpointType] = legacy.apiHost + configs[endpointType] = { ...configs[endpointType], baseUrl: legacy.apiHost } } if (legacy.anthropicApiHost) { - urls[ENDPOINT_TYPE.ANTHROPIC_MESSAGES] = legacy.anthropicApiHost + const ep = ENDPOINT_TYPE.ANTHROPIC_MESSAGES + configs[ep] = { ...configs[ep], baseUrl: legacy.anthropicApiHost } } - return Object.keys(urls).length > 0 ? urls : null -} - -function buildReasoningFormatTypes( - defaultChatEndpoint: EndpointType | undefined, - reasoningFormatType: ReasoningFormatType | undefined -): NewUserProvider['reasoningFormatTypes'] { - if (defaultChatEndpoint === undefined || !reasoningFormatType) { - return null + // Assign reasoning format type to the default endpoint + const reasoningFormatType = REASONING_FORMAT_MAP[legacy.type] + if (endpointType !== undefined && reasoningFormatType) { + configs[endpointType] = { ...configs[endpointType], reasoningFormatType } } - return { - [defaultChatEndpoint]: reasoningFormatType - } + return Object.keys(configs).length > 0 ? configs : null } function buildApiKeys(apiKey: string): ApiKeyEntry[] { @@ -210,8 +207,7 @@ function buildApiKeys(apiKey: string): ApiKeyEntry[] { .map((key) => ({ id: uuidv4(), key, - isEnabled: true, - createdAt: Date.now() + isEnabled: true })) } diff --git a/src/main/data/services/ProviderCatalogService.ts b/src/main/data/services/ProviderCatalogService.ts index 77d4c88a20f..f0e27bfcb29 100644 --- a/src/main/data/services/ProviderCatalogService.ts +++ b/src/main/data/services/ProviderCatalogService.ts @@ -26,8 +26,8 @@ import { loggerService } from '@logger' import { isDev } from '@main/constant' import { application } from '@main/core/application' import type { Model } from '@shared/data/types/model' -import type { ReasoningFormatType } from '@shared/data/types/provider' -import { mergeModelConfig } from '@shared/data/utils/modelMerger' +import type { EndpointConfig, ReasoningFormatType } from '@shared/data/types/provider' +import { extractReasoningFormatTypes, mergeModelConfig } from '@shared/data/utils/modelMerger' import { eq, isNotNull } from 'drizzle-orm' import { modelService } from './ModelService' @@ -48,27 +48,45 @@ const CASE_TO_TYPE: Record = { selfHosted: 'self-hosted' } -function buildReasoningFormatTypes( - defaultChatEndpoint: EndpointType | undefined, - reasoningFormatType: ReasoningFormatType | undefined -): Partial> | undefined { - if (defaultChatEndpoint === undefined || !reasoningFormatType) { - return undefined - } +/** + * Build runtime endpointConfigs from proto provider data. + * Converts proto EndpointConfig messages to plain runtime objects. + */ +function buildEndpointConfigsFromProto(p: ProtoProviderConfig): Partial> | null { + const configs: Partial> = {} - return { - [defaultChatEndpoint]: reasoningFormatType - } -} + for (const [k, protoConfig] of Object.entries(p.endpointConfigs)) { + const ep = Number(k) as EndpointType + const config: EndpointConfig = {} + + if (protoConfig.baseUrl) { + config.baseUrl = protoConfig.baseUrl + } + + // Convert proto ModelsApiUrls message to plain object + if (protoConfig.modelsApiUrls) { + const modelsApiUrls: Record = {} + if (protoConfig.modelsApiUrls.default) modelsApiUrls.default = protoConfig.modelsApiUrls.default + if (protoConfig.modelsApiUrls.embedding) modelsApiUrls.embedding = protoConfig.modelsApiUrls.embedding + if (protoConfig.modelsApiUrls.reranker) modelsApiUrls.reranker = protoConfig.modelsApiUrls.reranker + if (Object.keys(modelsApiUrls).length > 0) { + config.modelsApiUrls = modelsApiUrls + } + } + + // Convert proto ProviderReasoningFormat to runtime type string + const formatCase = protoConfig.reasoningFormat?.format.case + const reasoningFormatType = formatCase ? CASE_TO_TYPE[formatCase] : undefined + if (reasoningFormatType) { + config.reasoningFormatType = reasoningFormatType + } -function getSingleReasoningFormatType( - reasoningFormatTypes: Partial> | undefined -): ReasoningFormatType | undefined { - if (!reasoningFormatTypes) { - return undefined + if (Object.keys(config).length > 0) { + configs[ep] = config + } } - return Object.values(reasoningFormatTypes)[0] + return Object.keys(configs).length > 0 ? configs : null } export class CatalogService { @@ -162,12 +180,11 @@ export class CatalogService { } { const providers = this.loadCatalogProviders() const provider = providers.find((p) => p.id === providerId) - const formatCase = provider?.reasoningFormat?.format.case - const reasoningFormatType = formatCase ? CASE_TO_TYPE[formatCase] : undefined + const endpointConfigs = provider ? buildEndpointConfigsFromProto(provider) : null return { defaultChatEndpoint: provider?.defaultChatEndpoint, - reasoningFormatTypes: buildReasoningFormatTypes(provider?.defaultChatEndpoint, reasoningFormatType) + reasoningFormatTypes: extractReasoningFormatTypes(endpointConfigs) } } @@ -183,7 +200,7 @@ export class CatalogService { const [provider] = await db .select({ defaultChatEndpoint: userProviderTable.defaultChatEndpoint, - reasoningFormatTypes: userProviderTable.reasoningFormatTypes + endpointConfigs: userProviderTable.endpointConfigs }) .from(userProviderTable) .where(eq(userProviderTable.providerId, providerId)) @@ -192,8 +209,7 @@ export class CatalogService { if (provider) { const defaultChatEndpoint = provider.defaultChatEndpoint ?? catalogConfig.defaultChatEndpoint const reasoningFormatTypes = - provider.reasoningFormatTypes ?? - buildReasoningFormatTypes(defaultChatEndpoint, getSingleReasoningFormatType(catalogConfig.reasoningFormatTypes)) + extractReasoningFormatTypes(provider.endpointConfigs) ?? catalogConfig.reasoningFormatTypes return { defaultChatEndpoint, @@ -346,23 +362,6 @@ export class CatalogService { } : null - // Proto baseUrls uses map — convert to Record - const baseUrls: Record = {} - if (p.baseUrls) { - for (const [k, v] of Object.entries(p.baseUrls)) { - baseUrls[String(k)] = v - } - } - - // Convert proto message types to plain objects for DB storage - const modelsApiUrls: Record | null = p.modelsApiUrls - ? { - ...(p.modelsApiUrls.default ? { default: p.modelsApiUrls.default } : {}), - ...(p.modelsApiUrls.embedding ? { embedding: p.modelsApiUrls.embedding } : {}), - ...(p.modelsApiUrls.reranker ? { reranker: p.modelsApiUrls.reranker } : {}) - } - : null - const apiFeatures = p.apiFeatures ? { arrayContent: p.apiFeatures.arrayContent, @@ -374,19 +373,15 @@ export class CatalogService { } : null - // Extract reasoning format type from proto oneof - const formatCase = p.reasoningFormat?.format.case - const reasoningFormatType = formatCase ? CASE_TO_TYPE[formatCase] : undefined - const reasoningFormatTypes = buildReasoningFormatTypes(p.defaultChatEndpoint, reasoningFormatType) + // Build unified endpointConfigs from proto baseUrls + modelsApiUrls + reasoningFormat + const endpointConfigs = buildEndpointConfigsFromProto(p) return { providerId: p.id, presetProviderId: p.id, name: p.name, - baseUrls: Object.keys(baseUrls).length > 0 ? baseUrls : null, - modelsApiUrls: Object.keys(modelsApiUrls ?? {}).length > 0 ? modelsApiUrls : null, + endpointConfigs, defaultChatEndpoint: p.defaultChatEndpoint ?? null, - reasoningFormatTypes: reasoningFormatTypes ?? null, apiFeatures, websites } @@ -395,8 +390,10 @@ export class CatalogService { dbRows.push({ providerId: 'cherryai', name: 'CherryAI', - baseUrls: { - [EndpointType.OPENAI_CHAT_COMPLETIONS]: 'https://api.cherry-ai.com' + endpointConfigs: { + [EndpointType.OPENAI_CHAT_COMPLETIONS]: { + baseUrl: 'https://api.cherry-ai.com' + } }, defaultChatEndpoint: EndpointType.OPENAI_CHAT_COMPLETIONS }) @@ -472,7 +469,7 @@ export class CatalogService { .select({ providerId: userProviderTable.providerId, defaultChatEndpoint: userProviderTable.defaultChatEndpoint, - reasoningFormatTypes: userProviderTable.reasoningFormatTypes + endpointConfigs: userProviderTable.endpointConfigs }) .from(userProviderTable) const providerConfigMap = new Map(providerRows.map((row) => [row.providerId, row])) @@ -491,7 +488,8 @@ export class CatalogService { const providerConfig = providerConfigMap.get(row.providerId) const catalogReasoningConfig = this.getCatalogReasoningConfig(row.providerId) const defaultChatEndpoint = providerConfig?.defaultChatEndpoint ?? catalogReasoningConfig.defaultChatEndpoint - const reasoningFormatTypes = providerConfig?.reasoningFormatTypes ?? catalogReasoningConfig.reasoningFormatTypes + const reasoningFormatTypes = + extractReasoningFormatTypes(providerConfig?.endpointConfigs) ?? catalogReasoningConfig.reasoningFormatTypes // Merge catalog data with user data const merged = mergeModelConfig( diff --git a/src/main/data/services/ProviderService.ts b/src/main/data/services/ProviderService.ts index 4efe26b2127..df84b814d85 100644 --- a/src/main/data/services/ProviderService.ts +++ b/src/main/data/services/ProviderService.ts @@ -55,15 +55,13 @@ function rowToRuntimeProvider(row: UserProvider): Provider { id: row.providerId, presetProviderId: row.presetProviderId ?? undefined, name: row.name, - baseUrls: row.baseUrls ?? undefined, - modelsApiUrls: row.modelsApiUrls ?? undefined, + endpointConfigs: row.endpointConfigs ?? undefined, defaultChatEndpoint: row.defaultChatEndpoint ?? undefined, apiKeys, authType, apiFeatures, settings, websites: row.websites ?? undefined, - reasoningFormatTypes: row.reasoningFormatTypes ?? undefined, isEnabled: row.isEnabled ?? true } } @@ -126,10 +124,8 @@ export class ProviderService { providerId: dto.providerId, presetProviderId: dto.presetProviderId ?? null, name: dto.name, - baseUrls: dto.baseUrls ?? null, - modelsApiUrls: dto.modelsApiUrls ?? null, + endpointConfigs: dto.endpointConfigs ?? null, defaultChatEndpoint: dto.defaultChatEndpoint ?? null, - reasoningFormatTypes: dto.reasoningFormatTypes ?? null, apiKeys: dto.apiKeys ?? [], authConfig: dto.authConfig ?? null, apiFeatures: dto.apiFeatures ?? null, @@ -156,10 +152,8 @@ export class ProviderService { const updates: Partial = {} if (dto.name !== undefined) updates.name = dto.name - if (dto.baseUrls !== undefined) updates.baseUrls = dto.baseUrls - if (dto.modelsApiUrls !== undefined) updates.modelsApiUrls = dto.modelsApiUrls + if (dto.endpointConfigs !== undefined) updates.endpointConfigs = dto.endpointConfigs if (dto.defaultChatEndpoint !== undefined) updates.defaultChatEndpoint = dto.defaultChatEndpoint - if (dto.reasoningFormatTypes !== undefined) updates.reasoningFormatTypes = dto.reasoningFormatTypes if (dto.apiKeys !== undefined) updates.apiKeys = dto.apiKeys if (dto.authConfig !== undefined) updates.authConfig = dto.authConfig if (dto.apiFeatures !== undefined) updates.apiFeatures = dto.apiFeatures @@ -197,10 +191,8 @@ export class ProviderService { set: { presetProviderId: provider.presetProviderId, name: provider.name, - baseUrls: provider.baseUrls, - modelsApiUrls: provider.modelsApiUrls, + endpointConfigs: provider.endpointConfigs, defaultChatEndpoint: provider.defaultChatEndpoint, - reasoningFormatTypes: provider.reasoningFormatTypes, apiFeatures: provider.apiFeatures, providerSettings: provider.providerSettings, websites: provider.websites @@ -307,8 +299,7 @@ export class ProviderService { id: crypto.randomUUID(), key, label, - isEnabled: true, - createdAt: Date.now() + isEnabled: true } const updatedKeys = [...existingKeys, newEntry] From 4333632d79aacb91544a0bb09c54eddd58c803c0 Mon Sep 17 00:00:00 2001 From: suyao Date: Mon, 6 Apr 2026 22:41:28 +0800 Subject: [PATCH 18/29] refactor: rename provider-catalog to provider-registry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rename the entire provider-catalog package, service, and all related references to provider-registry for clearer naming semantics. - Package: @cherrystudio/provider-catalog → @cherrystudio/provider-registry - Directory: packages/provider-catalog → packages/provider-registry - Proto namespace: catalog.v1 → registry.v1 - Service: ProviderCatalogService → ProviderRegistryService - All type/function/variable names updated accordingly Co-Authored-By: Claude Opus 4.6 Signed-off-by: suyao --- ...-v2.md => provider-registry-runtime-v2.md} | 0 ...ovider-catalog.md => provider-registry.md} | 40 +- electron.vite.config.ts | 4 +- package.json | 2 +- .../provider-catalog/src/catalog-reader.ts | 35 -- .../provider-catalog/src/gen/v1/model_pb.ts | 387 -------------- .../src/gen/v1/provider_models_pb.ts | 208 -------- .../README.md | 0 .../data/models.pb | Bin .../data/provider-models.pb | Bin .../data/providers.pb | Bin .../package.json | 6 +- .../proto/buf.gen.yaml | 0 .../proto/buf.yaml | 2 +- .../proto/v1/common.proto | 2 +- .../proto/v1/model.proto | 4 +- .../proto/v1/provider.proto | 4 +- .../proto/v1/provider_models.proto | 4 +- .../src/gen/v1/common_pb.ts | 52 +- .../provider-registry/src/gen/v1/model_pb.ts | 387 ++++++++++++++ .../src/gen/v1/provider_models_pb.ts | 208 ++++++++ .../src/gen/v1/provider_pb.ts | 188 +++---- .../src/index.ts | 18 +- .../provider-registry/src/registry-reader.ts | 35 ++ .../src/schemas/common.ts | 2 +- .../src/schemas/enums.ts | 4 +- .../src/schemas/index.ts | 2 +- .../src/schemas/model.ts | 0 .../src/schemas/provider-models.ts | 0 .../src/schemas/provider.ts | 8 +- .../utils/importers/base/base-transformer.ts | 2 +- packages/shared/data/api/schemas/providers.ts | 6 +- packages/shared/data/types/model.ts | 4 +- packages/shared/data/types/provider.ts | 4 +- packages/shared/data/utils/modelMerger.ts | 6 +- pnpm-lock.yaml | 491 ++++++++++++++---- src/main/data/api/handlers/models.ts | 4 +- src/main/data/api/handlers/providers.ts | 6 +- src/main/data/db/schemas/userModel.ts | 26 +- .../mappings/ProviderModelMappings.ts | 2 +- src/main/data/services/ModelService.ts | 32 +- ...gService.ts => ProviderRegistryService.ts} | 226 ++++---- src/main/data/services/ProviderService.ts | 2 +- src/main/index.ts | 8 +- .../src/aiCore/services/listModels.ts | 4 +- tsconfig.json | 2 +- tsconfig.node.json | 4 +- tsconfig.web.json | 4 +- 48 files changed, 1362 insertions(+), 1073 deletions(-) rename .changeset/{provider-catalog-runtime-v2.md => provider-registry-runtime-v2.md} (100%) rename docs/en/references/data/{provider-catalog.md => provider-registry.md} (91%) delete mode 100644 packages/provider-catalog/src/catalog-reader.ts delete mode 100644 packages/provider-catalog/src/gen/v1/model_pb.ts delete mode 100644 packages/provider-catalog/src/gen/v1/provider_models_pb.ts rename packages/{provider-catalog => provider-registry}/README.md (100%) rename packages/{provider-catalog => provider-registry}/data/models.pb (100%) rename packages/{provider-catalog => provider-registry}/data/provider-models.pb (100%) rename packages/{provider-catalog => provider-registry}/data/providers.pb (100%) rename packages/{provider-catalog => provider-registry}/package.json (93%) rename packages/{provider-catalog => provider-registry}/proto/buf.gen.yaml (100%) rename packages/{provider-catalog => provider-registry}/proto/buf.yaml (75%) rename packages/{provider-catalog => provider-registry}/proto/v1/common.proto (99%) rename packages/{provider-catalog => provider-registry}/proto/v1/model.proto (98%) rename packages/{provider-catalog => provider-registry}/proto/v1/provider.proto (99%) rename packages/{provider-catalog => provider-registry}/proto/v1/provider_models.proto (98%) rename packages/{provider-catalog => provider-registry}/src/gen/v1/common_pb.ts (65%) create mode 100644 packages/provider-registry/src/gen/v1/model_pb.ts create mode 100644 packages/provider-registry/src/gen/v1/provider_models_pb.ts rename packages/{provider-catalog => provider-registry}/src/gen/v1/provider_pb.ts (50%) rename packages/{provider-catalog => provider-registry}/src/index.ts (65%) create mode 100644 packages/provider-registry/src/registry-reader.ts rename packages/{provider-catalog => provider-registry}/src/schemas/common.ts (97%) rename packages/{provider-catalog => provider-registry}/src/schemas/enums.ts (96%) rename packages/{provider-catalog => provider-registry}/src/schemas/index.ts (88%) rename packages/{provider-catalog => provider-registry}/src/schemas/model.ts (100%) rename packages/{provider-catalog => provider-registry}/src/schemas/provider-models.ts (100%) rename packages/{provider-catalog => provider-registry}/src/schemas/provider.ts (96%) rename packages/{provider-catalog => provider-registry}/src/utils/importers/base/base-transformer.ts (99%) rename src/main/data/services/{ProviderCatalogService.ts => ProviderRegistryService.ts} (72%) diff --git a/.changeset/provider-catalog-runtime-v2.md b/.changeset/provider-registry-runtime-v2.md similarity index 100% rename from .changeset/provider-catalog-runtime-v2.md rename to .changeset/provider-registry-runtime-v2.md diff --git a/docs/en/references/data/provider-catalog.md b/docs/en/references/data/provider-registry.md similarity index 91% rename from docs/en/references/data/provider-catalog.md rename to docs/en/references/data/provider-registry.md index c85999a37ca..1622c7fd58e 100644 --- a/docs/en/references/data/provider-catalog.md +++ b/docs/en/references/data/provider-registry.md @@ -1,12 +1,12 @@ -# Provider Catalog Reference +# Provider Registry Reference -This document describes the Provider/Model catalog system architecture, schemas, and data flows. +This document describes the Provider/Model registry system architecture, schemas, and data flows. ## Overview -The catalog system manages AI model and provider configurations with a three-layer merge architecture: +The registry system manages AI model and provider configurations with a three-layer merge architecture: -1. **Preset Layer** (read-only, bundled in app) - Catalog definitions +1. **Preset Layer** (read-only, bundled in app) - Registry definitions 2. **Override Layer** (read-only) - Provider-specific model overrides 3. **User Layer** (SQLite, writable) - User customizations @@ -48,7 +48,7 @@ When resolving a model or provider configuration: ## Preset Schemas -Location: `packages/provider-catalog/src/schemas/` +Location: `packages/provider-registry/src/schemas/` ### Provider Schema (`provider.ts`) @@ -240,7 +240,7 @@ Uses `::` separator to avoid conflicts with model IDs containing `:` (e.g., `ope The merged "final state" model configuration for consumers. ```typescript -// Type-safe union types (mirroring catalog Zod enums) +// Type-safe union types (mirroring registry Zod enums) type Modality = 'TEXT' | 'VISION' | 'AUDIO' | 'VIDEO' | 'VECTOR' type EndpointType = | 'CHAT_COMPLETIONS' | 'TEXT_COMPLETIONS' | 'MESSAGES' @@ -332,7 +332,7 @@ Stores user's provider configurations. |--------|------|-------------| | id | UUID | Primary key | | providerId | TEXT | User-defined unique ID | -| presetProviderId | TEXT | Links to catalog preset | +| presetProviderId | TEXT | Links to registry preset | | name | TEXT | Display name | | endpoints | JSON | Endpoint URL overrides | | defaultChatEndpoint | TEXT | Default text generation endpoint | @@ -347,7 +347,7 @@ Stores user's provider configurations. ### user_model Table -Stores all user models with fully resolved configurations. Capabilities are resolved once at add-time from catalog, so no runtime merge is needed. +Stores all user models with fully resolved configurations. Capabilities are resolved once at add-time from registry, so no runtime merge is needed. | Column | Type | Description | |--------|------|-------------| @@ -386,12 +386,12 @@ Merges model configurations with proper priority. ```typescript function mergeModelConfig( userModel: UserModel | null, - catalogOverride: CatalogProviderModelOverride | null, - presetModel: CatalogModel | null, + registryOverride: RegistryProviderModelOverride | null, + presetModel: RegistryModel | null, providerId: string ): RuntimeModel -// Priority: userModel > catalogOverride > presetModel +// Priority: userModel > registryOverride > presetModel ``` ### mergeProviderConfig @@ -401,7 +401,7 @@ Merges provider configurations. ```typescript function mergeProviderConfig( userProvider: UserProvider | null, - presetProvider: CatalogProvider | null + presetProvider: RegistryProvider | null ): RuntimeProvider // Priority: userProvider > presetProvider @@ -409,7 +409,7 @@ function mergeProviderConfig( ### applyCapabilityOverride -Applies catalog provider-model capability modifications (not user-level). +Applies registry provider-model capability modifications (not user-level). ```typescript function applyCapabilityOverride( @@ -447,12 +447,12 @@ function applyCapabilityOverride( | File | Description | |------|-------------| -| `packages/provider-catalog/data/providers.json` | Provider configurations | -| `packages/provider-catalog/data/models.json` | Base model definitions | -| `packages/provider-catalog/data/provider-models.json` | Provider-model overrides | -| `packages/provider-catalog/data/openrouter-models.json` | OpenRouter import data | -| `packages/provider-catalog/data/aihubmix-models.json` | AIHubMix import data | -| `packages/provider-catalog/data/modelsdev-models.json` | models.dev import data | +| `packages/provider-registry/data/providers.json` | Provider configurations | +| `packages/provider-registry/data/models.json` | Base model definitions | +| `packages/provider-registry/data/provider-models.json` | Provider-model overrides | +| `packages/provider-registry/data/openrouter-models.json` | OpenRouter import data | +| `packages/provider-registry/data/aihubmix-models.json` | AIHubMix import data | +| `packages/provider-registry/data/modelsdev-models.json` | models.dev import data | --- @@ -471,4 +471,4 @@ function applyCapabilityOverride( ## See Also - [Data Management Overview](./README.md) - System selection and patterns -- [Catalog Web UI](../../packages/provider-catalog/web/) - Review and edit interface +- [Registry Web UI](../../packages/provider-registry/web/) - Review and edit interface diff --git a/electron.vite.config.ts b/electron.vite.config.ts index e55a9d9793c..25ff98f1a15 100644 --- a/electron.vite.config.ts +++ b/electron.vite.config.ts @@ -36,7 +36,7 @@ export default defineConfig({ '@logger': resolve('src/main/services/LoggerService'), '@mcp-trace/trace-core': resolve('packages/mcp-trace/trace-core'), '@mcp-trace/trace-node': resolve('packages/mcp-trace/trace-node'), - '@cherrystudio/provider-catalog': resolve('packages/provider-catalog/src'), + '@cherrystudio/provider-registry': resolve('packages/provider-registry/src'), '@test-mocks': resolve('tests/__mocks__') } }, @@ -117,7 +117,7 @@ export default defineConfig({ '@cherrystudio/ai-core': resolve('packages/aiCore/src'), '@cherrystudio/extension-table-plus': resolve('packages/extension-table-plus/src'), '@cherrystudio/ai-sdk-provider': resolve('packages/ai-sdk-provider/src'), - '@cherrystudio/provider-catalog': resolve('packages/provider-catalog/src'), + '@cherrystudio/provider-registry': resolve('packages/provider-registry/src'), '@cherrystudio/ui/icons': resolve('packages/ui/src/components/icons'), '@cherrystudio/ui': resolve('packages/ui/src'), '@test-mocks': resolve('tests/__mocks__') diff --git a/package.json b/package.json index f3434379d68..b52cf49851f 100644 --- a/package.json +++ b/package.json @@ -162,7 +162,7 @@ "@cherrystudio/embedjs-utils": "0.1.31", "@cherrystudio/extension-table-plus": "workspace:^", "@cherrystudio/openai": "6.15.0", - "@cherrystudio/provider-catalog": "workspace:*", + "@cherrystudio/provider-registry": "workspace:*", "@cherrystudio/ui": "workspace:*", "@codemirror/lang-json": "6.0.2", "@codemirror/lint": "6.9.5", diff --git a/packages/provider-catalog/src/catalog-reader.ts b/packages/provider-catalog/src/catalog-reader.ts deleted file mode 100644 index 15046784103..00000000000 --- a/packages/provider-catalog/src/catalog-reader.ts +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Read-only catalog reader for .pb files. - * - * Reads protobuf catalog data and returns proto Message types directly. - * No JSON conversion — proto types are the single source of truth. - */ - -import { readFileSync } from 'node:fs' - -import { fromBinary } from '@bufbuild/protobuf' - -import type { ModelConfig } from './gen/v1/model_pb' -import { ModelCatalogSchema } from './gen/v1/model_pb' -import type { ProviderModelOverride } from './gen/v1/provider_models_pb' -import { ProviderModelCatalogSchema } from './gen/v1/provider_models_pb' -import type { ProviderConfig } from './gen/v1/provider_pb' -import { ProviderCatalogSchema } from './gen/v1/provider_pb' - -export function readModelCatalog(pbPath: string): { version: string; models: ModelConfig[] } { - const bytes = readFileSync(pbPath) - const catalog = fromBinary(ModelCatalogSchema, new Uint8Array(bytes)) - return { version: catalog.version, models: [...catalog.models] } -} - -export function readProviderCatalog(pbPath: string): { version: string; providers: ProviderConfig[] } { - const bytes = readFileSync(pbPath) - const catalog = fromBinary(ProviderCatalogSchema, new Uint8Array(bytes)) - return { version: catalog.version, providers: [...catalog.providers] } -} - -export function readProviderModelCatalog(pbPath: string): { version: string; overrides: ProviderModelOverride[] } { - const bytes = readFileSync(pbPath) - const catalog = fromBinary(ProviderModelCatalogSchema, new Uint8Array(bytes)) - return { version: catalog.version, overrides: [...catalog.overrides] } -} diff --git a/packages/provider-catalog/src/gen/v1/model_pb.ts b/packages/provider-catalog/src/gen/v1/model_pb.ts deleted file mode 100644 index 271e7d11f5d..00000000000 --- a/packages/provider-catalog/src/gen/v1/model_pb.ts +++ /dev/null @@ -1,387 +0,0 @@ -// @generated by protoc-gen-es v2.11.0 with parameter "target=ts" -// @generated from file v1/model.proto (package catalog.v1, syntax proto3) -/* eslint-disable */ - -import type { GenEnum, GenFile, GenMessage } from '@bufbuild/protobuf/codegenv2' -import { enumDesc, fileDesc, messageDesc } from '@bufbuild/protobuf/codegenv2' -import type { - Currency, - Metadata, - Modality, - ModelCapability, - NumericRange, - PricePerToken, - ReasoningEffort -} from './common_pb' -import { file_v1_common } from './common_pb' -import type { Message } from '@bufbuild/protobuf' - -/** - * Describes the file v1/model.proto. - */ -export const file_v1_model: GenFile = - /*@__PURE__*/ - fileDesc( - 'Cg52MS9tb2RlbC5wcm90bxIKY2F0YWxvZy52MSJrChNUaGlua2luZ1Rva2VuTGltaXRzEhAKA21pbhgBIAEoDUgAiAEBEhAKA21heBgCIAEoDUgBiAEBEhQKB2RlZmF1bHQYAyABKA1IAogBAUIGCgRfbWluQgYKBF9tYXhCCgoIX2RlZmF1bHQi0wEKEFJlYXNvbmluZ1N1cHBvcnQSQwoVdGhpbmtpbmdfdG9rZW5fbGltaXRzGAEgASgLMh8uY2F0YWxvZy52MS5UaGlua2luZ1Rva2VuTGltaXRzSACIAQESNgoRc3VwcG9ydGVkX2VmZm9ydHMYAiADKA4yGy5jYXRhbG9nLnYxLlJlYXNvbmluZ0VmZm9ydBIYCgtpbnRlcmxlYXZlZBgDIAEoCEgBiAEBQhgKFl90aGlua2luZ190b2tlbl9saW1pdHNCDgoMX2ludGVybGVhdmVkImMKFlJhbmdlZFBhcmFtZXRlclN1cHBvcnQSEQoJc3VwcG9ydGVkGAEgASgIEiwKBXJhbmdlGAIgASgLMhguY2F0YWxvZy52MS5OdW1lcmljUmFuZ2VIAIgBAUIICgZfcmFuZ2Ui1gMKEFBhcmFtZXRlclN1cHBvcnQSPAoLdGVtcGVyYXR1cmUYASABKAsyIi5jYXRhbG9nLnYxLlJhbmdlZFBhcmFtZXRlclN1cHBvcnRIAIgBARI2CgV0b3BfcBgCIAEoCzIiLmNhdGFsb2cudjEuUmFuZ2VkUGFyYW1ldGVyU3VwcG9ydEgBiAEBEjYKBXRvcF9rGAMgASgLMiIuY2F0YWxvZy52MS5SYW5nZWRQYXJhbWV0ZXJTdXBwb3J0SAKIAQESHgoRZnJlcXVlbmN5X3BlbmFsdHkYBCABKAhIA4gBARIdChBwcmVzZW5jZV9wZW5hbHR5GAUgASgISASIAQESFwoKbWF4X3Rva2VucxgGIAEoCEgFiAEBEhsKDnN0b3Bfc2VxdWVuY2VzGAcgASgISAaIAQESGwoOc3lzdGVtX21lc3NhZ2UYCCABKAhIB4gBAUIOCgxfdGVtcGVyYXR1cmVCCAoGX3RvcF9wQggKBl90b3Bfa0IUChJfZnJlcXVlbmN5X3BlbmFsdHlCEwoRX3ByZXNlbmNlX3BlbmFsdHlCDQoLX21heF90b2tlbnNCEQoPX3N0b3Bfc2VxdWVuY2VzQhEKD19zeXN0ZW1fbWVzc2FnZSJtCgpJbWFnZVByaWNlEg0KBXByaWNlGAEgASgBEiYKCGN1cnJlbmN5GAIgASgOMhQuY2F0YWxvZy52MS5DdXJyZW5jeRIoCgR1bml0GAMgASgOMhouY2F0YWxvZy52MS5JbWFnZVByaWNlVW5pdCJECgtNaW51dGVQcmljZRINCgVwcmljZRgBIAEoARImCghjdXJyZW5jeRgCIAEoDjIULmNhdGFsb2cudjEuQ3VycmVuY3ki6gIKDE1vZGVsUHJpY2luZxIoCgVpbnB1dBgBIAEoCzIZLmNhdGFsb2cudjEuUHJpY2VQZXJUb2tlbhIpCgZvdXRwdXQYAiABKAsyGS5jYXRhbG9nLnYxLlByaWNlUGVyVG9rZW4SMgoKY2FjaGVfcmVhZBgDIAEoCzIZLmNhdGFsb2cudjEuUHJpY2VQZXJUb2tlbkgAiAEBEjMKC2NhY2hlX3dyaXRlGAQgASgLMhkuY2F0YWxvZy52MS5QcmljZVBlclRva2VuSAGIAQESLgoJcGVyX2ltYWdlGAUgASgLMhYuY2F0YWxvZy52MS5JbWFnZVByaWNlSAKIAQESMAoKcGVyX21pbnV0ZRgGIAEoCzIXLmNhdGFsb2cudjEuTWludXRlUHJpY2VIA4gBAUINCgtfY2FjaGVfcmVhZEIOCgxfY2FjaGVfd3JpdGVCDAoKX3Blcl9pbWFnZUINCgtfcGVyX21pbnV0ZSKaBgoLTW9kZWxDb25maWcSCgoCaWQYASABKAkSEQoEbmFtZRgCIAEoCUgAiAEBEhgKC2Rlc2NyaXB0aW9uGAMgASgJSAGIAQESMQoMY2FwYWJpbGl0aWVzGAQgAygOMhsuY2F0YWxvZy52MS5Nb2RlbENhcGFiaWxpdHkSLgoQaW5wdXRfbW9kYWxpdGllcxgFIAMoDjIULmNhdGFsb2cudjEuTW9kYWxpdHkSLwoRb3V0cHV0X21vZGFsaXRpZXMYBiADKA4yFC5jYXRhbG9nLnYxLk1vZGFsaXR5EhsKDmNvbnRleHRfd2luZG93GAcgASgNSAKIAQESHgoRbWF4X291dHB1dF90b2tlbnMYCCABKA1IA4gBARIdChBtYXhfaW5wdXRfdG9rZW5zGAkgASgNSASIAQESLgoHcHJpY2luZxgKIAEoCzIYLmNhdGFsb2cudjEuTW9kZWxQcmljaW5nSAWIAQESNAoJcmVhc29uaW5nGAsgASgLMhwuY2F0YWxvZy52MS5SZWFzb25pbmdTdXBwb3J0SAaIAQESPAoRcGFyYW1ldGVyX3N1cHBvcnQYDCABKAsyHC5jYXRhbG9nLnYxLlBhcmFtZXRlclN1cHBvcnRIB4gBARITCgZmYW1pbHkYDSABKAlICIgBARIVCghvd25lZF9ieRgOIAEoCUgJiAEBEhkKDG9wZW5fd2VpZ2h0cxgPIAEoCEgKiAEBEg0KBWFsaWFzGBAgAygJEisKCG1ldGFkYXRhGBEgASgLMhQuY2F0YWxvZy52MS5NZXRhZGF0YUgLiAEBQgcKBV9uYW1lQg4KDF9kZXNjcmlwdGlvbkIRCg9fY29udGV4dF93aW5kb3dCFAoSX21heF9vdXRwdXRfdG9rZW5zQhMKEV9tYXhfaW5wdXRfdG9rZW5zQgoKCF9wcmljaW5nQgwKCl9yZWFzb25pbmdCFAoSX3BhcmFtZXRlcl9zdXBwb3J0QgkKB19mYW1pbHlCCwoJX293bmVkX2J5Qg8KDV9vcGVuX3dlaWdodHNCCwoJX21ldGFkYXRhIkgKDE1vZGVsQ2F0YWxvZxIPCgd2ZXJzaW9uGAEgASgJEicKBm1vZGVscxgCIAMoCzIXLmNhdGFsb2cudjEuTW9kZWxDb25maWcqagoOSW1hZ2VQcmljZVVuaXQSIAocSU1BR0VfUFJJQ0VfVU5JVF9VTlNQRUNJRklFRBAAEhoKFklNQUdFX1BSSUNFX1VOSVRfSU1BR0UQARIaChZJTUFHRV9QUklDRV9VTklUX1BJWEVMEAJiBnByb3RvMw', - [file_v1_common] - ) - -/** - * @generated from message catalog.v1.ThinkingTokenLimits - */ -export type ThinkingTokenLimits = Message<'catalog.v1.ThinkingTokenLimits'> & { - /** - * @generated from field: optional uint32 min = 1; - */ - min?: number - - /** - * @generated from field: optional uint32 max = 2; - */ - max?: number - - /** - * @generated from field: optional uint32 default = 3; - */ - default?: number -} - -/** - * Describes the message catalog.v1.ThinkingTokenLimits. - * Use `create(ThinkingTokenLimitsSchema)` to create a new message. - */ -export const ThinkingTokenLimitsSchema: GenMessage = /*@__PURE__*/ messageDesc(file_v1_model, 0) - -/** - * Model-level reasoning capabilities - * - * @generated from message catalog.v1.ReasoningSupport - */ -export type ReasoningSupport = Message<'catalog.v1.ReasoningSupport'> & { - /** - * @generated from field: optional catalog.v1.ThinkingTokenLimits thinking_token_limits = 1; - */ - thinkingTokenLimits?: ThinkingTokenLimits - - /** - * @generated from field: repeated catalog.v1.ReasoningEffort supported_efforts = 2; - */ - supportedEfforts: ReasoningEffort[] - - /** - * @generated from field: optional bool interleaved = 3; - */ - interleaved?: boolean -} - -/** - * Describes the message catalog.v1.ReasoningSupport. - * Use `create(ReasoningSupportSchema)` to create a new message. - */ -export const ReasoningSupportSchema: GenMessage = /*@__PURE__*/ messageDesc(file_v1_model, 1) - -/** - * @generated from message catalog.v1.RangedParameterSupport - */ -export type RangedParameterSupport = Message<'catalog.v1.RangedParameterSupport'> & { - /** - * @generated from field: bool supported = 1; - */ - supported: boolean - - /** - * @generated from field: optional catalog.v1.NumericRange range = 2; - */ - range?: NumericRange -} - -/** - * Describes the message catalog.v1.RangedParameterSupport. - * Use `create(RangedParameterSupportSchema)` to create a new message. - */ -export const RangedParameterSupportSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_v1_model, 2) - -/** - * @generated from message catalog.v1.ParameterSupport - */ -export type ParameterSupport = Message<'catalog.v1.ParameterSupport'> & { - /** - * @generated from field: optional catalog.v1.RangedParameterSupport temperature = 1; - */ - temperature?: RangedParameterSupport - - /** - * @generated from field: optional catalog.v1.RangedParameterSupport top_p = 2; - */ - topP?: RangedParameterSupport - - /** - * @generated from field: optional catalog.v1.RangedParameterSupport top_k = 3; - */ - topK?: RangedParameterSupport - - /** - * @generated from field: optional bool frequency_penalty = 4; - */ - frequencyPenalty?: boolean - - /** - * @generated from field: optional bool presence_penalty = 5; - */ - presencePenalty?: boolean - - /** - * @generated from field: optional bool max_tokens = 6; - */ - maxTokens?: boolean - - /** - * @generated from field: optional bool stop_sequences = 7; - */ - stopSequences?: boolean - - /** - * @generated from field: optional bool system_message = 8; - */ - systemMessage?: boolean -} - -/** - * Describes the message catalog.v1.ParameterSupport. - * Use `create(ParameterSupportSchema)` to create a new message. - */ -export const ParameterSupportSchema: GenMessage = /*@__PURE__*/ messageDesc(file_v1_model, 3) - -/** - * @generated from message catalog.v1.ImagePrice - */ -export type ImagePrice = Message<'catalog.v1.ImagePrice'> & { - /** - * @generated from field: double price = 1; - */ - price: number - - /** - * @generated from field: catalog.v1.Currency currency = 2; - */ - currency: Currency - - /** - * @generated from field: catalog.v1.ImagePriceUnit unit = 3; - */ - unit: ImagePriceUnit -} - -/** - * Describes the message catalog.v1.ImagePrice. - * Use `create(ImagePriceSchema)` to create a new message. - */ -export const ImagePriceSchema: GenMessage = /*@__PURE__*/ messageDesc(file_v1_model, 4) - -/** - * @generated from message catalog.v1.MinutePrice - */ -export type MinutePrice = Message<'catalog.v1.MinutePrice'> & { - /** - * @generated from field: double price = 1; - */ - price: number - - /** - * @generated from field: catalog.v1.Currency currency = 2; - */ - currency: Currency -} - -/** - * Describes the message catalog.v1.MinutePrice. - * Use `create(MinutePriceSchema)` to create a new message. - */ -export const MinutePriceSchema: GenMessage = /*@__PURE__*/ messageDesc(file_v1_model, 5) - -/** - * @generated from message catalog.v1.ModelPricing - */ -export type ModelPricing = Message<'catalog.v1.ModelPricing'> & { - /** - * @generated from field: catalog.v1.PricePerToken input = 1; - */ - input?: PricePerToken - - /** - * @generated from field: catalog.v1.PricePerToken output = 2; - */ - output?: PricePerToken - - /** - * @generated from field: optional catalog.v1.PricePerToken cache_read = 3; - */ - cacheRead?: PricePerToken - - /** - * @generated from field: optional catalog.v1.PricePerToken cache_write = 4; - */ - cacheWrite?: PricePerToken - - /** - * @generated from field: optional catalog.v1.ImagePrice per_image = 5; - */ - perImage?: ImagePrice - - /** - * @generated from field: optional catalog.v1.MinutePrice per_minute = 6; - */ - perMinute?: MinutePrice -} - -/** - * Describes the message catalog.v1.ModelPricing. - * Use `create(ModelPricingSchema)` to create a new message. - */ -export const ModelPricingSchema: GenMessage = /*@__PURE__*/ messageDesc(file_v1_model, 6) - -/** - * @generated from message catalog.v1.ModelConfig - */ -export type ModelConfig = Message<'catalog.v1.ModelConfig'> & { - /** - * @generated from field: string id = 1; - */ - id: string - - /** - * @generated from field: optional string name = 2; - */ - name?: string - - /** - * @generated from field: optional string description = 3; - */ - description?: string - - /** - * @generated from field: repeated catalog.v1.ModelCapability capabilities = 4; - */ - capabilities: ModelCapability[] - - /** - * @generated from field: repeated catalog.v1.Modality input_modalities = 5; - */ - inputModalities: Modality[] - - /** - * @generated from field: repeated catalog.v1.Modality output_modalities = 6; - */ - outputModalities: Modality[] - - /** - * @generated from field: optional uint32 context_window = 7; - */ - contextWindow?: number - - /** - * @generated from field: optional uint32 max_output_tokens = 8; - */ - maxOutputTokens?: number - - /** - * @generated from field: optional uint32 max_input_tokens = 9; - */ - maxInputTokens?: number - - /** - * @generated from field: optional catalog.v1.ModelPricing pricing = 10; - */ - pricing?: ModelPricing - - /** - * @generated from field: optional catalog.v1.ReasoningSupport reasoning = 11; - */ - reasoning?: ReasoningSupport - - /** - * @generated from field: optional catalog.v1.ParameterSupport parameter_support = 12; - */ - parameterSupport?: ParameterSupport - - /** - * @generated from field: optional string family = 13; - */ - family?: string - - /** - * @generated from field: optional string owned_by = 14; - */ - ownedBy?: string - - /** - * @generated from field: optional bool open_weights = 15; - */ - openWeights?: boolean - - /** - * @generated from field: repeated string alias = 16; - */ - alias: string[] - - /** - * @generated from field: optional catalog.v1.Metadata metadata = 17; - */ - metadata?: Metadata -} - -/** - * Describes the message catalog.v1.ModelConfig. - * Use `create(ModelConfigSchema)` to create a new message. - */ -export const ModelConfigSchema: GenMessage = /*@__PURE__*/ messageDesc(file_v1_model, 7) - -/** - * Top-level container - * - * @generated from message catalog.v1.ModelCatalog - */ -export type ModelCatalog = Message<'catalog.v1.ModelCatalog'> & { - /** - * @generated from field: string version = 1; - */ - version: string - - /** - * @generated from field: repeated catalog.v1.ModelConfig models = 2; - */ - models: ModelConfig[] -} - -/** - * Describes the message catalog.v1.ModelCatalog. - * Use `create(ModelCatalogSchema)` to create a new message. - */ -export const ModelCatalogSchema: GenMessage = /*@__PURE__*/ messageDesc(file_v1_model, 8) - -/** - * @generated from enum catalog.v1.ImagePriceUnit - */ -export enum ImagePriceUnit { - /** - * @generated from enum value: IMAGE_PRICE_UNIT_UNSPECIFIED = 0; - */ - UNSPECIFIED = 0, - - /** - * @generated from enum value: IMAGE_PRICE_UNIT_IMAGE = 1; - */ - IMAGE = 1, - - /** - * @generated from enum value: IMAGE_PRICE_UNIT_PIXEL = 2; - */ - PIXEL = 2 -} - -/** - * Describes the enum catalog.v1.ImagePriceUnit. - */ -export const ImagePriceUnitSchema: GenEnum = /*@__PURE__*/ enumDesc(file_v1_model, 0) diff --git a/packages/provider-catalog/src/gen/v1/provider_models_pb.ts b/packages/provider-catalog/src/gen/v1/provider_models_pb.ts deleted file mode 100644 index 5534231b49a..00000000000 --- a/packages/provider-catalog/src/gen/v1/provider_models_pb.ts +++ /dev/null @@ -1,208 +0,0 @@ -// @generated by protoc-gen-es v2.11.0 with parameter "target=ts" -// @generated from file v1/provider_models.proto (package catalog.v1, syntax proto3) -/* eslint-disable */ - -import type { GenFile, GenMessage } from '@bufbuild/protobuf/codegenv2' -import { fileDesc, messageDesc } from '@bufbuild/protobuf/codegenv2' -import type { EndpointType, Modality, ModelCapability } from './common_pb' -import { file_v1_common } from './common_pb' -import type { ModelPricing, ParameterSupport, ReasoningSupport } from './model_pb' -import { file_v1_model } from './model_pb' -import type { Message } from '@bufbuild/protobuf' - -/** - * Describes the file v1/provider_models.proto. - */ -export const file_v1_provider_models: GenFile = - /*@__PURE__*/ - fileDesc( - 'Chh2MS9wcm92aWRlcl9tb2RlbHMucHJvdG8SCmNhdGFsb2cudjEilwEKEkNhcGFiaWxpdHlPdmVycmlkZRIoCgNhZGQYASADKA4yGy5jYXRhbG9nLnYxLk1vZGVsQ2FwYWJpbGl0eRIrCgZyZW1vdmUYAiADKA4yGy5jYXRhbG9nLnYxLk1vZGVsQ2FwYWJpbGl0eRIqCgVmb3JjZRgDIAMoDjIbLmNhdGFsb2cudjEuTW9kZWxDYXBhYmlsaXR5Is8BCgtNb2RlbExpbWl0cxIbCg5jb250ZXh0X3dpbmRvdxgBIAEoDUgAiAEBEh4KEW1heF9vdXRwdXRfdG9rZW5zGAIgASgNSAGIAQESHQoQbWF4X2lucHV0X3Rva2VucxgDIAEoDUgCiAEBEhcKCnJhdGVfbGltaXQYBCABKA1IA4gBAUIRCg9fY29udGV4dF93aW5kb3dCFAoSX21heF9vdXRwdXRfdG9rZW5zQhMKEV9tYXhfaW5wdXRfdG9rZW5zQg0KC19yYXRlX2xpbWl0IoYGChVQcm92aWRlck1vZGVsT3ZlcnJpZGUSEwoLcHJvdmlkZXJfaWQYASABKAkSEAoIbW9kZWxfaWQYAiABKAkSGQoMYXBpX21vZGVsX2lkGAMgASgJSACIAQESGgoNbW9kZWxfdmFyaWFudBgEIAEoCUgBiAEBEjkKDGNhcGFiaWxpdGllcxgFIAEoCzIeLmNhdGFsb2cudjEuQ2FwYWJpbGl0eU92ZXJyaWRlSAKIAQESLAoGbGltaXRzGAYgASgLMhcuY2F0YWxvZy52MS5Nb2RlbExpbWl0c0gDiAEBEi4KB3ByaWNpbmcYByABKAsyGC5jYXRhbG9nLnYxLk1vZGVsUHJpY2luZ0gEiAEBEjQKCXJlYXNvbmluZxgIIAEoCzIcLmNhdGFsb2cudjEuUmVhc29uaW5nU3VwcG9ydEgFiAEBEjwKEXBhcmFtZXRlcl9zdXBwb3J0GAkgASgLMhwuY2F0YWxvZy52MS5QYXJhbWV0ZXJTdXBwb3J0SAaIAQESMAoOZW5kcG9pbnRfdHlwZXMYCiADKA4yGC5jYXRhbG9nLnYxLkVuZHBvaW50VHlwZRIuChBpbnB1dF9tb2RhbGl0aWVzGAsgAygOMhQuY2F0YWxvZy52MS5Nb2RhbGl0eRIvChFvdXRwdXRfbW9kYWxpdGllcxgMIAMoDjIULmNhdGFsb2cudjEuTW9kYWxpdHkSFQoIZGlzYWJsZWQYDSABKAhIB4gBARIZCgxyZXBsYWNlX3dpdGgYDiABKAlICIgBARITCgZyZWFzb24YDyABKAlICYgBARIQCghwcmlvcml0eRgQIAEoDUIPCg1fYXBpX21vZGVsX2lkQhAKDl9tb2RlbF92YXJpYW50Qg8KDV9jYXBhYmlsaXRpZXNCCQoHX2xpbWl0c0IKCghfcHJpY2luZ0IMCgpfcmVhc29uaW5nQhQKEl9wYXJhbWV0ZXJfc3VwcG9ydEILCglfZGlzYWJsZWRCDwoNX3JlcGxhY2Vfd2l0aEIJCgdfcmVhc29uIl0KFFByb3ZpZGVyTW9kZWxDYXRhbG9nEg8KB3ZlcnNpb24YASABKAkSNAoJb3ZlcnJpZGVzGAIgAygLMiEuY2F0YWxvZy52MS5Qcm92aWRlck1vZGVsT3ZlcnJpZGViBnByb3RvMw', - [file_v1_common, file_v1_model] - ) - -/** - * @generated from message catalog.v1.CapabilityOverride - */ -export type CapabilityOverride = Message<'catalog.v1.CapabilityOverride'> & { - /** - * @generated from field: repeated catalog.v1.ModelCapability add = 1; - */ - add: ModelCapability[] - - /** - * @generated from field: repeated catalog.v1.ModelCapability remove = 2; - */ - remove: ModelCapability[] - - /** - * @generated from field: repeated catalog.v1.ModelCapability force = 3; - */ - force: ModelCapability[] -} - -/** - * Describes the message catalog.v1.CapabilityOverride. - * Use `create(CapabilityOverrideSchema)` to create a new message. - */ -export const CapabilityOverrideSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_v1_provider_models, 0) - -/** - * @generated from message catalog.v1.ModelLimits - */ -export type ModelLimits = Message<'catalog.v1.ModelLimits'> & { - /** - * @generated from field: optional uint32 context_window = 1; - */ - contextWindow?: number - - /** - * @generated from field: optional uint32 max_output_tokens = 2; - */ - maxOutputTokens?: number - - /** - * @generated from field: optional uint32 max_input_tokens = 3; - */ - maxInputTokens?: number - - /** - * @generated from field: optional uint32 rate_limit = 4; - */ - rateLimit?: number -} - -/** - * Describes the message catalog.v1.ModelLimits. - * Use `create(ModelLimitsSchema)` to create a new message. - */ -export const ModelLimitsSchema: GenMessage = /*@__PURE__*/ messageDesc(file_v1_provider_models, 1) - -/** - * @generated from message catalog.v1.ProviderModelOverride - */ -export type ProviderModelOverride = Message<'catalog.v1.ProviderModelOverride'> & { - /** - * Identification - * - * @generated from field: string provider_id = 1; - */ - providerId: string - - /** - * @generated from field: string model_id = 2; - */ - modelId: string - - /** - * @generated from field: optional string api_model_id = 3; - */ - apiModelId?: string - - /** - * @generated from field: optional string model_variant = 4; - */ - modelVariant?: string - - /** - * Overrides - * - * @generated from field: optional catalog.v1.CapabilityOverride capabilities = 5; - */ - capabilities?: CapabilityOverride - - /** - * @generated from field: optional catalog.v1.ModelLimits limits = 6; - */ - limits?: ModelLimits - - /** - * @generated from field: optional catalog.v1.ModelPricing pricing = 7; - */ - pricing?: ModelPricing - - /** - * @generated from field: optional catalog.v1.ReasoningSupport reasoning = 8; - */ - reasoning?: ReasoningSupport - - /** - * @generated from field: optional catalog.v1.ParameterSupport parameter_support = 9; - */ - parameterSupport?: ParameterSupport - - /** - * @generated from field: repeated catalog.v1.EndpointType endpoint_types = 10; - */ - endpointTypes: EndpointType[] - - /** - * @generated from field: repeated catalog.v1.Modality input_modalities = 11; - */ - inputModalities: Modality[] - - /** - * @generated from field: repeated catalog.v1.Modality output_modalities = 12; - */ - outputModalities: Modality[] - - /** - * Status - * - * @generated from field: optional bool disabled = 13; - */ - disabled?: boolean - - /** - * @generated from field: optional string replace_with = 14; - */ - replaceWith?: string - - /** - * Metadata - * - * @generated from field: optional string reason = 15; - */ - reason?: string - - /** - * 0 = auto, 100+ = manual - * - * @generated from field: uint32 priority = 16; - */ - priority: number -} - -/** - * Describes the message catalog.v1.ProviderModelOverride. - * Use `create(ProviderModelOverrideSchema)` to create a new message. - */ -export const ProviderModelOverrideSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_v1_provider_models, 2) - -/** - * Top-level container - * - * @generated from message catalog.v1.ProviderModelCatalog - */ -export type ProviderModelCatalog = Message<'catalog.v1.ProviderModelCatalog'> & { - /** - * @generated from field: string version = 1; - */ - version: string - - /** - * @generated from field: repeated catalog.v1.ProviderModelOverride overrides = 2; - */ - overrides: ProviderModelOverride[] -} - -/** - * Describes the message catalog.v1.ProviderModelCatalog. - * Use `create(ProviderModelCatalogSchema)` to create a new message. - */ -export const ProviderModelCatalogSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_v1_provider_models, 3) diff --git a/packages/provider-catalog/README.md b/packages/provider-registry/README.md similarity index 100% rename from packages/provider-catalog/README.md rename to packages/provider-registry/README.md diff --git a/packages/provider-catalog/data/models.pb b/packages/provider-registry/data/models.pb similarity index 100% rename from packages/provider-catalog/data/models.pb rename to packages/provider-registry/data/models.pb diff --git a/packages/provider-catalog/data/provider-models.pb b/packages/provider-registry/data/provider-models.pb similarity index 100% rename from packages/provider-catalog/data/provider-models.pb rename to packages/provider-registry/data/provider-models.pb diff --git a/packages/provider-catalog/data/providers.pb b/packages/provider-registry/data/providers.pb similarity index 100% rename from packages/provider-catalog/data/providers.pb rename to packages/provider-registry/data/providers.pb diff --git a/packages/provider-catalog/package.json b/packages/provider-registry/package.json similarity index 93% rename from packages/provider-catalog/package.json rename to packages/provider-registry/package.json index d222c9dfbe5..d5496706ee1 100644 --- a/packages/provider-catalog/package.json +++ b/packages/provider-registry/package.json @@ -1,7 +1,7 @@ { - "name": "@cherrystudio/provider-catalog", + "name": "@cherrystudio/provider-registry", "version": "0.0.1-alpha.1", - "description": "Provider and Model Catalog", + "description": "Provider and Model Registry", "main": "dist/index.js", "module": "dist/index.mjs", "types": "dist/index.d.ts", @@ -9,7 +9,7 @@ "scripts": { "proto:generate": "cd proto && buf generate .", "proto:lint": "cd proto && buf lint .", - "proto:breaking": "cd proto && buf breaking . --against '../../../.git#subdir=packages/provider-catalog/proto,branch=main'", + "proto:breaking": "cd proto && buf breaking . --against '../../../.git#subdir=packages/provider-registry/proto,branch=main'", "build": "pnpm proto:generate && tsdown", "dev": "pnpm proto:generate && tsc -w", "clean": "rm -rf dist", diff --git a/packages/provider-catalog/proto/buf.gen.yaml b/packages/provider-registry/proto/buf.gen.yaml similarity index 100% rename from packages/provider-catalog/proto/buf.gen.yaml rename to packages/provider-registry/proto/buf.gen.yaml diff --git a/packages/provider-catalog/proto/buf.yaml b/packages/provider-registry/proto/buf.yaml similarity index 75% rename from packages/provider-catalog/proto/buf.yaml rename to packages/provider-registry/proto/buf.yaml index bc6d53f35a2..63a38e8ebc4 100644 --- a/packages/provider-catalog/proto/buf.yaml +++ b/packages/provider-registry/proto/buf.yaml @@ -1,7 +1,7 @@ version: v2 modules: - path: . - name: buf.build/cherrystudio/catalog + name: buf.build/cherrystudio/registry lint: use: - STANDARD diff --git a/packages/provider-catalog/proto/v1/common.proto b/packages/provider-registry/proto/v1/common.proto similarity index 99% rename from packages/provider-catalog/proto/v1/common.proto rename to packages/provider-registry/proto/v1/common.proto index b3042471f0f..5540d5eb30b 100644 --- a/packages/provider-catalog/proto/v1/common.proto +++ b/packages/provider-registry/proto/v1/common.proto @@ -1,5 +1,5 @@ syntax = "proto3"; -package catalog.v1; +package registry.v1; // ═══════════════════════════════════════════════════════════════════════════════ // Enums diff --git a/packages/provider-catalog/proto/v1/model.proto b/packages/provider-registry/proto/v1/model.proto similarity index 98% rename from packages/provider-catalog/proto/v1/model.proto rename to packages/provider-registry/proto/v1/model.proto index 7d453b16818..36cf0ee0983 100644 --- a/packages/provider-catalog/proto/v1/model.proto +++ b/packages/provider-registry/proto/v1/model.proto @@ -1,5 +1,5 @@ syntax = "proto3"; -package catalog.v1; +package registry.v1; import "v1/common.proto"; @@ -104,7 +104,7 @@ message ModelConfig { } // Top-level container -message ModelCatalog { +message ModelRegistry { string version = 1; repeated ModelConfig models = 2; } diff --git a/packages/provider-catalog/proto/v1/provider.proto b/packages/provider-registry/proto/v1/provider.proto similarity index 99% rename from packages/provider-catalog/proto/v1/provider.proto rename to packages/provider-registry/proto/v1/provider.proto index 08548ce8164..e1ee591f1db 100644 --- a/packages/provider-catalog/proto/v1/provider.proto +++ b/packages/provider-registry/proto/v1/provider.proto @@ -1,5 +1,5 @@ syntax = "proto3"; -package catalog.v1; +package registry.v1; import "v1/common.proto"; @@ -217,7 +217,7 @@ message ProviderConfig { } // Top-level container -message ProviderCatalog { +message ProviderRegistry { string version = 1; repeated ProviderConfig providers = 2; } diff --git a/packages/provider-catalog/proto/v1/provider_models.proto b/packages/provider-registry/proto/v1/provider_models.proto similarity index 98% rename from packages/provider-catalog/proto/v1/provider_models.proto rename to packages/provider-registry/proto/v1/provider_models.proto index a3309352d8f..2204a9e1ae2 100644 --- a/packages/provider-catalog/proto/v1/provider_models.proto +++ b/packages/provider-registry/proto/v1/provider_models.proto @@ -1,5 +1,5 @@ syntax = "proto3"; -package catalog.v1; +package registry.v1; import "v1/common.proto"; import "v1/model.proto"; @@ -57,7 +57,7 @@ message ProviderModelOverride { } // Top-level container -message ProviderModelCatalog { +message ProviderModelRegistry { string version = 1; repeated ProviderModelOverride overrides = 2; } diff --git a/packages/provider-catalog/src/gen/v1/common_pb.ts b/packages/provider-registry/src/gen/v1/common_pb.ts similarity index 65% rename from packages/provider-catalog/src/gen/v1/common_pb.ts rename to packages/provider-registry/src/gen/v1/common_pb.ts index 1c74223cf96..f6e1da13432 100644 --- a/packages/provider-catalog/src/gen/v1/common_pb.ts +++ b/packages/provider-registry/src/gen/v1/common_pb.ts @@ -1,5 +1,5 @@ // @generated by protoc-gen-es v2.11.0 with parameter "target=ts" -// @generated from file v1/common.proto (package catalog.v1, syntax proto3) +// @generated from file v1/common.proto (package registry.v1, syntax proto3) /* eslint-disable */ import type { GenEnum, GenFile, GenMessage } from '@bufbuild/protobuf/codegenv2' @@ -12,13 +12,13 @@ import type { Message } from '@bufbuild/protobuf' export const file_v1_common: GenFile = /*@__PURE__*/ fileDesc( - 'Cg92MS9jb21tb24ucHJvdG8SCmNhdGFsb2cudjEiKAoMTnVtZXJpY1JhbmdlEgsKA21pbhgBIAEoARILCgNtYXgYAiABKAEibwoNUHJpY2VQZXJUb2tlbhIfChJwZXJfbWlsbGlvbl90b2tlbnMYASABKAFIAIgBARImCghjdXJyZW5jeRgCIAEoDjIULmNhdGFsb2cudjEuQ3VycmVuY3lCFQoTX3Blcl9taWxsaW9uX3Rva2VucyJuCghNZXRhZGF0YRIyCgdlbnRyaWVzGAEgAygLMiEuY2F0YWxvZy52MS5NZXRhZGF0YS5FbnRyaWVzRW50cnkaLgoMRW50cmllc0VudHJ5EgsKA2tleRgBIAEoCRINCgV2YWx1ZRgCIAEoCToCOAEq/AQKDEVuZHBvaW50VHlwZRIdChlFTkRQT0lOVF9UWVBFX1VOU1BFQ0lGSUVEEAASKQolRU5EUE9JTlRfVFlQRV9PUEVOQUlfQ0hBVF9DT01QTEVUSU9OUxABEikKJUVORFBPSU5UX1RZUEVfT1BFTkFJX1RFWFRfQ09NUExFVElPTlMQAhIkCiBFTkRQT0lOVF9UWVBFX0FOVEhST1BJQ19NRVNTQUdFUxADEiIKHkVORFBPSU5UX1RZUEVfT1BFTkFJX1JFU1BPTlNFUxAEEikKJUVORFBPSU5UX1RZUEVfR09PR0xFX0dFTkVSQVRFX0NPTlRFTlQQBRIdChlFTkRQT0lOVF9UWVBFX09MTEFNQV9DSEFUEAYSIQodRU5EUE9JTlRfVFlQRV9PTExBTUFfR0VORVJBVEUQBxIjCh9FTkRQT0lOVF9UWVBFX09QRU5BSV9FTUJFRERJTkdTEAgSHQoZRU5EUE9JTlRfVFlQRV9KSU5BX1JFUkFOSxAJEikKJUVORFBPSU5UX1RZUEVfT1BFTkFJX0lNQUdFX0dFTkVSQVRJT04QChIjCh9FTkRQT0lOVF9UWVBFX09QRU5BSV9JTUFHRV9FRElUEAsSLAooRU5EUE9JTlRfVFlQRV9PUEVOQUlfQVVESU9fVFJBTlNDUklQVElPThAMEioKJkVORFBPSU5UX1RZUEVfT1BFTkFJX0FVRElPX1RSQU5TTEFUSU9OEA0SJwojRU5EUE9JTlRfVFlQRV9PUEVOQUlfVEVYVF9UT19TUEVFQ0gQDhIpCiVFTkRQT0lOVF9UWVBFX09QRU5BSV9WSURFT19HRU5FUkFUSU9OEA8qnAUKD01vZGVsQ2FwYWJpbGl0eRIgChxNT0RFTF9DQVBBQklMSVRZX1VOU1BFQ0lGSUVEEAASIgoeTU9ERUxfQ0FQQUJJTElUWV9GVU5DVElPTl9DQUxMEAESHgoaTU9ERUxfQ0FQQUJJTElUWV9SRUFTT05JTkcQAhImCiJNT0RFTF9DQVBBQklMSVRZX0lNQUdFX1JFQ09HTklUSU9OEAMSJQohTU9ERUxfQ0FQQUJJTElUWV9JTUFHRV9HRU5FUkFUSU9OEAQSJgoiTU9ERUxfQ0FQQUJJTElUWV9BVURJT19SRUNPR05JVElPThAFEiUKIU1PREVMX0NBUEFCSUxJVFlfQVVESU9fR0VORVJBVElPThAGEh4KGk1PREVMX0NBUEFCSUxJVFlfRU1CRURESU5HEAcSGwoXTU9ERUxfQ0FQQUJJTElUWV9SRVJBTksQCBIlCiFNT0RFTF9DQVBBQklMSVRZX0FVRElPX1RSQU5TQ1JJUFQQCRImCiJNT0RFTF9DQVBBQklMSVRZX1ZJREVPX1JFQ09HTklUSU9OEAoSJQohTU9ERUxfQ0FQQUJJTElUWV9WSURFT19HRU5FUkFUSU9OEAsSJgoiTU9ERUxfQ0FQQUJJTElUWV9TVFJVQ1RVUkVEX09VVFBVVBAMEh8KG01PREVMX0NBUEFCSUxJVFlfRklMRV9JTlBVVBANEh8KG01PREVMX0NBUEFCSUxJVFlfV0VCX1NFQVJDSBAOEiMKH01PREVMX0NBUEFCSUxJVFlfQ09ERV9FWEVDVVRJT04QDxIgChxNT0RFTF9DQVBBQklMSVRZX0ZJTEVfU0VBUkNIEBASIQodTU9ERUxfQ0FQQUJJTElUWV9DT01QVVRFUl9VU0UQESqIAQoITW9kYWxpdHkSGAoUTU9EQUxJVFlfVU5TUEVDSUZJRUQQABIRCg1NT0RBTElUWV9URVhUEAESEgoOTU9EQUxJVFlfSU1BR0UQAhISCg5NT0RBTElUWV9BVURJTxADEhIKDk1PREFMSVRZX1ZJREVPEAQSEwoPTU9EQUxJVFlfVkVDVE9SEAUqSAoIQ3VycmVuY3kSGAoUQ1VSUkVOQ1lfVU5TUEVDSUZJRUQQABIQCgxDVVJSRU5DWV9VU0QQARIQCgxDVVJSRU5DWV9DTlkQAirzAQoPUmVhc29uaW5nRWZmb3J0EiAKHFJFQVNPTklOR19FRkZPUlRfVU5TUEVDSUZJRUQQABIZChVSRUFTT05JTkdfRUZGT1JUX05PTkUQARIcChhSRUFTT05JTkdfRUZGT1JUX01JTklNQUwQAhIYChRSRUFTT05JTkdfRUZGT1JUX0xPVxADEhsKF1JFQVNPTklOR19FRkZPUlRfTUVESVVNEAQSGQoVUkVBU09OSU5HX0VGRk9SVF9ISUdIEAUSGAoUUkVBU09OSU5HX0VGRk9SVF9NQVgQBhIZChVSRUFTT05JTkdfRUZGT1JUX0FVVE8QByqnAQoVT3BlbkFJUmVhc29uaW5nRWZmb3J0EicKI09QRU5BSV9SRUFTT05JTkdfRUZGT1JUX1VOU1BFQ0lGSUVEEAASHwobT1BFTkFJX1JFQVNPTklOR19FRkZPUlRfTE9XEAESIgoeT1BFTkFJX1JFQVNPTklOR19FRkZPUlRfTUVESVVNEAISIAocT1BFTkFJX1JFQVNPTklOR19FRkZPUlRfSElHSBADKtoBChhBbnRocm9waWNSZWFzb25pbmdFZmZvcnQSKgomQU5USFJPUElDX1JFQVNPTklOR19FRkZPUlRfVU5TUEVDSUZJRUQQABIiCh5BTlRIUk9QSUNfUkVBU09OSU5HX0VGRk9SVF9MT1cQARIlCiFBTlRIUk9QSUNfUkVBU09OSU5HX0VGRk9SVF9NRURJVU0QAhIjCh9BTlRIUk9QSUNfUkVBU09OSU5HX0VGRk9SVF9ISUdIEAMSIgoeQU5USFJPUElDX1JFQVNPTklOR19FRkZPUlRfTUFYEARiBnByb3RvMw' + 'Cg92MS9jb21tb24ucHJvdG8SC3JlZ2lzdHJ5LnYxIigKDE51bWVyaWNSYW5nZRILCgNtaW4YASABKAESCwoDbWF4GAIgASgBInAKDVByaWNlUGVyVG9rZW4SHwoScGVyX21pbGxpb25fdG9rZW5zGAEgASgBSACIAQESJwoIY3VycmVuY3kYAiABKA4yFS5yZWdpc3RyeS52MS5DdXJyZW5jeUIVChNfcGVyX21pbGxpb25fdG9rZW5zIm8KCE1ldGFkYXRhEjMKB2VudHJpZXMYASADKAsyIi5yZWdpc3RyeS52MS5NZXRhZGF0YS5FbnRyaWVzRW50cnkaLgoMRW50cmllc0VudHJ5EgsKA2tleRgBIAEoCRINCgV2YWx1ZRgCIAEoCToCOAEq/AQKDEVuZHBvaW50VHlwZRIdChlFTkRQT0lOVF9UWVBFX1VOU1BFQ0lGSUVEEAASKQolRU5EUE9JTlRfVFlQRV9PUEVOQUlfQ0hBVF9DT01QTEVUSU9OUxABEikKJUVORFBPSU5UX1RZUEVfT1BFTkFJX1RFWFRfQ09NUExFVElPTlMQAhIkCiBFTkRQT0lOVF9UWVBFX0FOVEhST1BJQ19NRVNTQUdFUxADEiIKHkVORFBPSU5UX1RZUEVfT1BFTkFJX1JFU1BPTlNFUxAEEikKJUVORFBPSU5UX1RZUEVfR09PR0xFX0dFTkVSQVRFX0NPTlRFTlQQBRIdChlFTkRQT0lOVF9UWVBFX09MTEFNQV9DSEFUEAYSIQodRU5EUE9JTlRfVFlQRV9PTExBTUFfR0VORVJBVEUQBxIjCh9FTkRQT0lOVF9UWVBFX09QRU5BSV9FTUJFRERJTkdTEAgSHQoZRU5EUE9JTlRfVFlQRV9KSU5BX1JFUkFOSxAJEikKJUVORFBPSU5UX1RZUEVfT1BFTkFJX0lNQUdFX0dFTkVSQVRJT04QChIjCh9FTkRQT0lOVF9UWVBFX09QRU5BSV9JTUFHRV9FRElUEAsSLAooRU5EUE9JTlRfVFlQRV9PUEVOQUlfQVVESU9fVFJBTlNDUklQVElPThAMEioKJkVORFBPSU5UX1RZUEVfT1BFTkFJX0FVRElPX1RSQU5TTEFUSU9OEA0SJwojRU5EUE9JTlRfVFlQRV9PUEVOQUlfVEVYVF9UT19TUEVFQ0gQDhIpCiVFTkRQT0lOVF9UWVBFX09QRU5BSV9WSURFT19HRU5FUkFUSU9OEA8qnAUKD01vZGVsQ2FwYWJpbGl0eRIgChxNT0RFTF9DQVBBQklMSVRZX1VOU1BFQ0lGSUVEEAASIgoeTU9ERUxfQ0FQQUJJTElUWV9GVU5DVElPTl9DQUxMEAESHgoaTU9ERUxfQ0FQQUJJTElUWV9SRUFTT05JTkcQAhImCiJNT0RFTF9DQVBBQklMSVRZX0lNQUdFX1JFQ09HTklUSU9OEAMSJQohTU9ERUxfQ0FQQUJJTElUWV9JTUFHRV9HRU5FUkFUSU9OEAQSJgoiTU9ERUxfQ0FQQUJJTElUWV9BVURJT19SRUNPR05JVElPThAFEiUKIU1PREVMX0NBUEFCSUxJVFlfQVVESU9fR0VORVJBVElPThAGEh4KGk1PREVMX0NBUEFCSUxJVFlfRU1CRURESU5HEAcSGwoXTU9ERUxfQ0FQQUJJTElUWV9SRVJBTksQCBIlCiFNT0RFTF9DQVBBQklMSVRZX0FVRElPX1RSQU5TQ1JJUFQQCRImCiJNT0RFTF9DQVBBQklMSVRZX1ZJREVPX1JFQ09HTklUSU9OEAoSJQohTU9ERUxfQ0FQQUJJTElUWV9WSURFT19HRU5FUkFUSU9OEAsSJgoiTU9ERUxfQ0FQQUJJTElUWV9TVFJVQ1RVUkVEX09VVFBVVBAMEh8KG01PREVMX0NBUEFCSUxJVFlfRklMRV9JTlBVVBANEh8KG01PREVMX0NBUEFCSUxJVFlfV0VCX1NFQVJDSBAOEiMKH01PREVMX0NBUEFCSUxJVFlfQ09ERV9FWEVDVVRJT04QDxIgChxNT0RFTF9DQVBBQklMSVRZX0ZJTEVfU0VBUkNIEBASIQodTU9ERUxfQ0FQQUJJTElUWV9DT01QVVRFUl9VU0UQESqIAQoITW9kYWxpdHkSGAoUTU9EQUxJVFlfVU5TUEVDSUZJRUQQABIRCg1NT0RBTElUWV9URVhUEAESEgoOTU9EQUxJVFlfSU1BR0UQAhISCg5NT0RBTElUWV9BVURJTxADEhIKDk1PREFMSVRZX1ZJREVPEAQSEwoPTU9EQUxJVFlfVkVDVE9SEAUqSAoIQ3VycmVuY3kSGAoUQ1VSUkVOQ1lfVU5TUEVDSUZJRUQQABIQCgxDVVJSRU5DWV9VU0QQARIQCgxDVVJSRU5DWV9DTlkQAirzAQoPUmVhc29uaW5nRWZmb3J0EiAKHFJFQVNPTklOR19FRkZPUlRfVU5TUEVDSUZJRUQQABIZChVSRUFTT05JTkdfRUZGT1JUX05PTkUQARIcChhSRUFTT05JTkdfRUZGT1JUX01JTklNQUwQAhIYChRSRUFTT05JTkdfRUZGT1JUX0xPVxADEhsKF1JFQVNPTklOR19FRkZPUlRfTUVESVVNEAQSGQoVUkVBU09OSU5HX0VGRk9SVF9ISUdIEAUSGAoUUkVBU09OSU5HX0VGRk9SVF9NQVgQBhIZChVSRUFTT05JTkdfRUZGT1JUX0FVVE8QByqnAQoVT3BlbkFJUmVhc29uaW5nRWZmb3J0EicKI09QRU5BSV9SRUFTT05JTkdfRUZGT1JUX1VOU1BFQ0lGSUVEEAASHwobT1BFTkFJX1JFQVNPTklOR19FRkZPUlRfTE9XEAESIgoeT1BFTkFJX1JFQVNPTklOR19FRkZPUlRfTUVESVVNEAISIAocT1BFTkFJX1JFQVNPTklOR19FRkZPUlRfSElHSBADKtoBChhBbnRocm9waWNSZWFzb25pbmdFZmZvcnQSKgomQU5USFJPUElDX1JFQVNPTklOR19FRkZPUlRfVU5TUEVDSUZJRUQQABIiCh5BTlRIUk9QSUNfUkVBU09OSU5HX0VGRk9SVF9MT1cQARIlCiFBTlRIUk9QSUNfUkVBU09OSU5HX0VGRk9SVF9NRURJVU0QAhIjCh9BTlRIUk9QSUNfUkVBU09OSU5HX0VGRk9SVF9ISUdIEAMSIgoeQU5USFJPUElDX1JFQVNPTklOR19FRkZPUlRfTUFYEARiBnByb3RvMw' ) /** - * @generated from message catalog.v1.NumericRange + * @generated from message registry.v1.NumericRange */ -export type NumericRange = Message<'catalog.v1.NumericRange'> & { +export type NumericRange = Message<'registry.v1.NumericRange'> & { /** * @generated from field: double min = 1; */ @@ -31,15 +31,15 @@ export type NumericRange = Message<'catalog.v1.NumericRange'> & { } /** - * Describes the message catalog.v1.NumericRange. + * Describes the message registry.v1.NumericRange. * Use `create(NumericRangeSchema)` to create a new message. */ export const NumericRangeSchema: GenMessage = /*@__PURE__*/ messageDesc(file_v1_common, 0) /** - * @generated from message catalog.v1.PricePerToken + * @generated from message registry.v1.PricePerToken */ -export type PricePerToken = Message<'catalog.v1.PricePerToken'> & { +export type PricePerToken = Message<'registry.v1.PricePerToken'> & { /** * nullable — absent means unknown * @@ -48,13 +48,13 @@ export type PricePerToken = Message<'catalog.v1.PricePerToken'> & { perMillionTokens?: number /** - * @generated from field: catalog.v1.Currency currency = 2; + * @generated from field: registry.v1.Currency currency = 2; */ currency: Currency } /** - * Describes the message catalog.v1.PricePerToken. + * Describes the message registry.v1.PricePerToken. * Use `create(PricePerTokenSchema)` to create a new message. */ export const PricePerTokenSchema: GenMessage = /*@__PURE__*/ messageDesc(file_v1_common, 1) @@ -62,9 +62,9 @@ export const PricePerTokenSchema: GenMessage = /*@__PURE__*/ mess /** * Generic key-value metadata (replaces Record) * - * @generated from message catalog.v1.Metadata + * @generated from message registry.v1.Metadata */ -export type Metadata = Message<'catalog.v1.Metadata'> & { +export type Metadata = Message<'registry.v1.Metadata'> & { /** * @generated from field: map entries = 1; */ @@ -72,13 +72,13 @@ export type Metadata = Message<'catalog.v1.Metadata'> & { } /** - * Describes the message catalog.v1.Metadata. + * Describes the message registry.v1.Metadata. * Use `create(MetadataSchema)` to create a new message. */ export const MetadataSchema: GenMessage = /*@__PURE__*/ messageDesc(file_v1_common, 2) /** - * @generated from enum catalog.v1.EndpointType + * @generated from enum registry.v1.EndpointType */ export enum EndpointType { /** @@ -163,12 +163,12 @@ export enum EndpointType { } /** - * Describes the enum catalog.v1.EndpointType. + * Describes the enum registry.v1.EndpointType. */ export const EndpointTypeSchema: GenEnum = /*@__PURE__*/ enumDesc(file_v1_common, 0) /** - * @generated from enum catalog.v1.ModelCapability + * @generated from enum registry.v1.ModelCapability */ export enum ModelCapability { /** @@ -263,12 +263,12 @@ export enum ModelCapability { } /** - * Describes the enum catalog.v1.ModelCapability. + * Describes the enum registry.v1.ModelCapability. */ export const ModelCapabilitySchema: GenEnum = /*@__PURE__*/ enumDesc(file_v1_common, 1) /** - * @generated from enum catalog.v1.Modality + * @generated from enum registry.v1.Modality */ export enum Modality { /** @@ -303,12 +303,12 @@ export enum Modality { } /** - * Describes the enum catalog.v1.Modality. + * Describes the enum registry.v1.Modality. */ export const ModalitySchema: GenEnum = /*@__PURE__*/ enumDesc(file_v1_common, 2) /** - * @generated from enum catalog.v1.Currency + * @generated from enum registry.v1.Currency */ export enum Currency { /** @@ -330,7 +330,7 @@ export enum Currency { } /** - * Describes the enum catalog.v1.Currency. + * Describes the enum registry.v1.Currency. */ export const CurrencySchema: GenEnum = /*@__PURE__*/ enumDesc(file_v1_common, 3) @@ -339,7 +339,7 @@ export const CurrencySchema: GenEnum = /*@__PURE__*/ enumDesc(file_v1_ * Used in ReasoningCommon.supported_efforts to describe catalog-level capabilities. * Per-provider params use their own specific effort enums below. * - * @generated from enum catalog.v1.ReasoningEffort + * @generated from enum registry.v1.ReasoningEffort */ export enum ReasoningEffort { /** @@ -384,12 +384,12 @@ export enum ReasoningEffort { } /** - * Describes the enum catalog.v1.ReasoningEffort. + * Describes the enum registry.v1.ReasoningEffort. */ export const ReasoningEffortSchema: GenEnum = /*@__PURE__*/ enumDesc(file_v1_common, 4) /** - * @generated from enum catalog.v1.OpenAIReasoningEffort + * @generated from enum registry.v1.OpenAIReasoningEffort */ export enum OpenAIReasoningEffort { /** @@ -414,12 +414,12 @@ export enum OpenAIReasoningEffort { } /** - * Describes the enum catalog.v1.OpenAIReasoningEffort. + * Describes the enum registry.v1.OpenAIReasoningEffort. */ export const OpenAIReasoningEffortSchema: GenEnum = /*@__PURE__*/ enumDesc(file_v1_common, 5) /** - * @generated from enum catalog.v1.AnthropicReasoningEffort + * @generated from enum registry.v1.AnthropicReasoningEffort */ export enum AnthropicReasoningEffort { /** @@ -449,7 +449,7 @@ export enum AnthropicReasoningEffort { } /** - * Describes the enum catalog.v1.AnthropicReasoningEffort. + * Describes the enum registry.v1.AnthropicReasoningEffort. */ export const AnthropicReasoningEffortSchema: GenEnum = /*@__PURE__*/ diff --git a/packages/provider-registry/src/gen/v1/model_pb.ts b/packages/provider-registry/src/gen/v1/model_pb.ts new file mode 100644 index 00000000000..087008be8fe --- /dev/null +++ b/packages/provider-registry/src/gen/v1/model_pb.ts @@ -0,0 +1,387 @@ +// @generated by protoc-gen-es v2.11.0 with parameter "target=ts" +// @generated from file v1/model.proto (package registry.v1, syntax proto3) +/* eslint-disable */ + +import type { GenEnum, GenFile, GenMessage } from '@bufbuild/protobuf/codegenv2' +import { enumDesc, fileDesc, messageDesc } from '@bufbuild/protobuf/codegenv2' +import type { + Currency, + Metadata, + Modality, + ModelCapability, + NumericRange, + PricePerToken, + ReasoningEffort +} from './common_pb' +import { file_v1_common } from './common_pb' +import type { Message } from '@bufbuild/protobuf' + +/** + * Describes the file v1/model.proto. + */ +export const file_v1_model: GenFile = + /*@__PURE__*/ + fileDesc( + 'Cg52MS9tb2RlbC5wcm90bxILcmVnaXN0cnkudjEiawoTVGhpbmtpbmdUb2tlbkxpbWl0cxIQCgNtaW4YASABKA1IAIgBARIQCgNtYXgYAiABKA1IAYgBARIUCgdkZWZhdWx0GAMgASgNSAKIAQFCBgoEX21pbkIGCgRfbWF4QgoKCF9kZWZhdWx0ItUBChBSZWFzb25pbmdTdXBwb3J0EkQKFXRoaW5raW5nX3Rva2VuX2xpbWl0cxgBIAEoCzIgLnJlZ2lzdHJ5LnYxLlRoaW5raW5nVG9rZW5MaW1pdHNIAIgBARI3ChFzdXBwb3J0ZWRfZWZmb3J0cxgCIAMoDjIcLnJlZ2lzdHJ5LnYxLlJlYXNvbmluZ0VmZm9ydBIYCgtpbnRlcmxlYXZlZBgDIAEoCEgBiAEBQhgKFl90aGlua2luZ190b2tlbl9saW1pdHNCDgoMX2ludGVybGVhdmVkImQKFlJhbmdlZFBhcmFtZXRlclN1cHBvcnQSEQoJc3VwcG9ydGVkGAEgASgIEi0KBXJhbmdlGAIgASgLMhkucmVnaXN0cnkudjEuTnVtZXJpY1JhbmdlSACIAQFCCAoGX3JhbmdlItkDChBQYXJhbWV0ZXJTdXBwb3J0Ej0KC3RlbXBlcmF0dXJlGAEgASgLMiMucmVnaXN0cnkudjEuUmFuZ2VkUGFyYW1ldGVyU3VwcG9ydEgAiAEBEjcKBXRvcF9wGAIgASgLMiMucmVnaXN0cnkudjEuUmFuZ2VkUGFyYW1ldGVyU3VwcG9ydEgBiAEBEjcKBXRvcF9rGAMgASgLMiMucmVnaXN0cnkudjEuUmFuZ2VkUGFyYW1ldGVyU3VwcG9ydEgCiAEBEh4KEWZyZXF1ZW5jeV9wZW5hbHR5GAQgASgISAOIAQESHQoQcHJlc2VuY2VfcGVuYWx0eRgFIAEoCEgEiAEBEhcKCm1heF90b2tlbnMYBiABKAhIBYgBARIbCg5zdG9wX3NlcXVlbmNlcxgHIAEoCEgGiAEBEhsKDnN5c3RlbV9tZXNzYWdlGAggASgISAeIAQFCDgoMX3RlbXBlcmF0dXJlQggKBl90b3BfcEIICgZfdG9wX2tCFAoSX2ZyZXF1ZW5jeV9wZW5hbHR5QhMKEV9wcmVzZW5jZV9wZW5hbHR5Qg0KC19tYXhfdG9rZW5zQhEKD19zdG9wX3NlcXVlbmNlc0IRCg9fc3lzdGVtX21lc3NhZ2UibwoKSW1hZ2VQcmljZRINCgVwcmljZRgBIAEoARInCghjdXJyZW5jeRgCIAEoDjIVLnJlZ2lzdHJ5LnYxLkN1cnJlbmN5EikKBHVuaXQYAyABKA4yGy5yZWdpc3RyeS52MS5JbWFnZVByaWNlVW5pdCJFCgtNaW51dGVQcmljZRINCgVwcmljZRgBIAEoARInCghjdXJyZW5jeRgCIAEoDjIVLnJlZ2lzdHJ5LnYxLkN1cnJlbmN5IvACCgxNb2RlbFByaWNpbmcSKQoFaW5wdXQYASABKAsyGi5yZWdpc3RyeS52MS5QcmljZVBlclRva2VuEioKBm91dHB1dBgCIAEoCzIaLnJlZ2lzdHJ5LnYxLlByaWNlUGVyVG9rZW4SMwoKY2FjaGVfcmVhZBgDIAEoCzIaLnJlZ2lzdHJ5LnYxLlByaWNlUGVyVG9rZW5IAIgBARI0CgtjYWNoZV93cml0ZRgEIAEoCzIaLnJlZ2lzdHJ5LnYxLlByaWNlUGVyVG9rZW5IAYgBARIvCglwZXJfaW1hZ2UYBSABKAsyFy5yZWdpc3RyeS52MS5JbWFnZVByaWNlSAKIAQESMQoKcGVyX21pbnV0ZRgGIAEoCzIYLnJlZ2lzdHJ5LnYxLk1pbnV0ZVByaWNlSAOIAQFCDQoLX2NhY2hlX3JlYWRCDgoMX2NhY2hlX3dyaXRlQgwKCl9wZXJfaW1hZ2VCDQoLX3Blcl9taW51dGUioQYKC01vZGVsQ29uZmlnEgoKAmlkGAEgASgJEhEKBG5hbWUYAiABKAlIAIgBARIYCgtkZXNjcmlwdGlvbhgDIAEoCUgBiAEBEjIKDGNhcGFiaWxpdGllcxgEIAMoDjIcLnJlZ2lzdHJ5LnYxLk1vZGVsQ2FwYWJpbGl0eRIvChBpbnB1dF9tb2RhbGl0aWVzGAUgAygOMhUucmVnaXN0cnkudjEuTW9kYWxpdHkSMAoRb3V0cHV0X21vZGFsaXRpZXMYBiADKA4yFS5yZWdpc3RyeS52MS5Nb2RhbGl0eRIbCg5jb250ZXh0X3dpbmRvdxgHIAEoDUgCiAEBEh4KEW1heF9vdXRwdXRfdG9rZW5zGAggASgNSAOIAQESHQoQbWF4X2lucHV0X3Rva2VucxgJIAEoDUgEiAEBEi8KB3ByaWNpbmcYCiABKAsyGS5yZWdpc3RyeS52MS5Nb2RlbFByaWNpbmdIBYgBARI1CglyZWFzb25pbmcYCyABKAsyHS5yZWdpc3RyeS52MS5SZWFzb25pbmdTdXBwb3J0SAaIAQESPQoRcGFyYW1ldGVyX3N1cHBvcnQYDCABKAsyHS5yZWdpc3RyeS52MS5QYXJhbWV0ZXJTdXBwb3J0SAeIAQESEwoGZmFtaWx5GA0gASgJSAiIAQESFQoIb3duZWRfYnkYDiABKAlICYgBARIZCgxvcGVuX3dlaWdodHMYDyABKAhICogBARINCgVhbGlhcxgQIAMoCRIsCghtZXRhZGF0YRgRIAEoCzIVLnJlZ2lzdHJ5LnYxLk1ldGFkYXRhSAuIAQFCBwoFX25hbWVCDgoMX2Rlc2NyaXB0aW9uQhEKD19jb250ZXh0X3dpbmRvd0IUChJfbWF4X291dHB1dF90b2tlbnNCEwoRX21heF9pbnB1dF90b2tlbnNCCgoIX3ByaWNpbmdCDAoKX3JlYXNvbmluZ0IUChJfcGFyYW1ldGVyX3N1cHBvcnRCCQoHX2ZhbWlseUILCglfb3duZWRfYnlCDwoNX29wZW5fd2VpZ2h0c0ILCglfbWV0YWRhdGEiSgoNTW9kZWxSZWdpc3RyeRIPCgd2ZXJzaW9uGAEgASgJEigKBm1vZGVscxgCIAMoCzIYLnJlZ2lzdHJ5LnYxLk1vZGVsQ29uZmlnKmoKDkltYWdlUHJpY2VVbml0EiAKHElNQUdFX1BSSUNFX1VOSVRfVU5TUEVDSUZJRUQQABIaChZJTUFHRV9QUklDRV9VTklUX0lNQUdFEAESGgoWSU1BR0VfUFJJQ0VfVU5JVF9QSVhFTBACYgZwcm90bzM', + [file_v1_common] + ) + +/** + * @generated from message registry.v1.ThinkingTokenLimits + */ +export type ThinkingTokenLimits = Message<'registry.v1.ThinkingTokenLimits'> & { + /** + * @generated from field: optional uint32 min = 1; + */ + min?: number + + /** + * @generated from field: optional uint32 max = 2; + */ + max?: number + + /** + * @generated from field: optional uint32 default = 3; + */ + default?: number +} + +/** + * Describes the message registry.v1.ThinkingTokenLimits. + * Use `create(ThinkingTokenLimitsSchema)` to create a new message. + */ +export const ThinkingTokenLimitsSchema: GenMessage = /*@__PURE__*/ messageDesc(file_v1_model, 0) + +/** + * Model-level reasoning capabilities + * + * @generated from message registry.v1.ReasoningSupport + */ +export type ReasoningSupport = Message<'registry.v1.ReasoningSupport'> & { + /** + * @generated from field: optional registry.v1.ThinkingTokenLimits thinking_token_limits = 1; + */ + thinkingTokenLimits?: ThinkingTokenLimits + + /** + * @generated from field: repeated registry.v1.ReasoningEffort supported_efforts = 2; + */ + supportedEfforts: ReasoningEffort[] + + /** + * @generated from field: optional bool interleaved = 3; + */ + interleaved?: boolean +} + +/** + * Describes the message registry.v1.ReasoningSupport. + * Use `create(ReasoningSupportSchema)` to create a new message. + */ +export const ReasoningSupportSchema: GenMessage = /*@__PURE__*/ messageDesc(file_v1_model, 1) + +/** + * @generated from message registry.v1.RangedParameterSupport + */ +export type RangedParameterSupport = Message<'registry.v1.RangedParameterSupport'> & { + /** + * @generated from field: bool supported = 1; + */ + supported: boolean + + /** + * @generated from field: optional registry.v1.NumericRange range = 2; + */ + range?: NumericRange +} + +/** + * Describes the message registry.v1.RangedParameterSupport. + * Use `create(RangedParameterSupportSchema)` to create a new message. + */ +export const RangedParameterSupportSchema: GenMessage = + /*@__PURE__*/ + messageDesc(file_v1_model, 2) + +/** + * @generated from message registry.v1.ParameterSupport + */ +export type ParameterSupport = Message<'registry.v1.ParameterSupport'> & { + /** + * @generated from field: optional registry.v1.RangedParameterSupport temperature = 1; + */ + temperature?: RangedParameterSupport + + /** + * @generated from field: optional registry.v1.RangedParameterSupport top_p = 2; + */ + topP?: RangedParameterSupport + + /** + * @generated from field: optional registry.v1.RangedParameterSupport top_k = 3; + */ + topK?: RangedParameterSupport + + /** + * @generated from field: optional bool frequency_penalty = 4; + */ + frequencyPenalty?: boolean + + /** + * @generated from field: optional bool presence_penalty = 5; + */ + presencePenalty?: boolean + + /** + * @generated from field: optional bool max_tokens = 6; + */ + maxTokens?: boolean + + /** + * @generated from field: optional bool stop_sequences = 7; + */ + stopSequences?: boolean + + /** + * @generated from field: optional bool system_message = 8; + */ + systemMessage?: boolean +} + +/** + * Describes the message registry.v1.ParameterSupport. + * Use `create(ParameterSupportSchema)` to create a new message. + */ +export const ParameterSupportSchema: GenMessage = /*@__PURE__*/ messageDesc(file_v1_model, 3) + +/** + * @generated from message registry.v1.ImagePrice + */ +export type ImagePrice = Message<'registry.v1.ImagePrice'> & { + /** + * @generated from field: double price = 1; + */ + price: number + + /** + * @generated from field: registry.v1.Currency currency = 2; + */ + currency: Currency + + /** + * @generated from field: registry.v1.ImagePriceUnit unit = 3; + */ + unit: ImagePriceUnit +} + +/** + * Describes the message registry.v1.ImagePrice. + * Use `create(ImagePriceSchema)` to create a new message. + */ +export const ImagePriceSchema: GenMessage = /*@__PURE__*/ messageDesc(file_v1_model, 4) + +/** + * @generated from message registry.v1.MinutePrice + */ +export type MinutePrice = Message<'registry.v1.MinutePrice'> & { + /** + * @generated from field: double price = 1; + */ + price: number + + /** + * @generated from field: registry.v1.Currency currency = 2; + */ + currency: Currency +} + +/** + * Describes the message registry.v1.MinutePrice. + * Use `create(MinutePriceSchema)` to create a new message. + */ +export const MinutePriceSchema: GenMessage = /*@__PURE__*/ messageDesc(file_v1_model, 5) + +/** + * @generated from message registry.v1.ModelPricing + */ +export type ModelPricing = Message<'registry.v1.ModelPricing'> & { + /** + * @generated from field: registry.v1.PricePerToken input = 1; + */ + input?: PricePerToken + + /** + * @generated from field: registry.v1.PricePerToken output = 2; + */ + output?: PricePerToken + + /** + * @generated from field: optional registry.v1.PricePerToken cache_read = 3; + */ + cacheRead?: PricePerToken + + /** + * @generated from field: optional registry.v1.PricePerToken cache_write = 4; + */ + cacheWrite?: PricePerToken + + /** + * @generated from field: optional registry.v1.ImagePrice per_image = 5; + */ + perImage?: ImagePrice + + /** + * @generated from field: optional registry.v1.MinutePrice per_minute = 6; + */ + perMinute?: MinutePrice +} + +/** + * Describes the message registry.v1.ModelPricing. + * Use `create(ModelPricingSchema)` to create a new message. + */ +export const ModelPricingSchema: GenMessage = /*@__PURE__*/ messageDesc(file_v1_model, 6) + +/** + * @generated from message registry.v1.ModelConfig + */ +export type ModelConfig = Message<'registry.v1.ModelConfig'> & { + /** + * @generated from field: string id = 1; + */ + id: string + + /** + * @generated from field: optional string name = 2; + */ + name?: string + + /** + * @generated from field: optional string description = 3; + */ + description?: string + + /** + * @generated from field: repeated registry.v1.ModelCapability capabilities = 4; + */ + capabilities: ModelCapability[] + + /** + * @generated from field: repeated registry.v1.Modality input_modalities = 5; + */ + inputModalities: Modality[] + + /** + * @generated from field: repeated registry.v1.Modality output_modalities = 6; + */ + outputModalities: Modality[] + + /** + * @generated from field: optional uint32 context_window = 7; + */ + contextWindow?: number + + /** + * @generated from field: optional uint32 max_output_tokens = 8; + */ + maxOutputTokens?: number + + /** + * @generated from field: optional uint32 max_input_tokens = 9; + */ + maxInputTokens?: number + + /** + * @generated from field: optional registry.v1.ModelPricing pricing = 10; + */ + pricing?: ModelPricing + + /** + * @generated from field: optional registry.v1.ReasoningSupport reasoning = 11; + */ + reasoning?: ReasoningSupport + + /** + * @generated from field: optional registry.v1.ParameterSupport parameter_support = 12; + */ + parameterSupport?: ParameterSupport + + /** + * @generated from field: optional string family = 13; + */ + family?: string + + /** + * @generated from field: optional string owned_by = 14; + */ + ownedBy?: string + + /** + * @generated from field: optional bool open_weights = 15; + */ + openWeights?: boolean + + /** + * @generated from field: repeated string alias = 16; + */ + alias: string[] + + /** + * @generated from field: optional registry.v1.Metadata metadata = 17; + */ + metadata?: Metadata +} + +/** + * Describes the message registry.v1.ModelConfig. + * Use `create(ModelConfigSchema)` to create a new message. + */ +export const ModelConfigSchema: GenMessage = /*@__PURE__*/ messageDesc(file_v1_model, 7) + +/** + * Top-level container + * + * @generated from message registry.v1.ModelRegistry + */ +export type ModelRegistry = Message<'registry.v1.ModelRegistry'> & { + /** + * @generated from field: string version = 1; + */ + version: string + + /** + * @generated from field: repeated registry.v1.ModelConfig models = 2; + */ + models: ModelConfig[] +} + +/** + * Describes the message registry.v1.ModelRegistry. + * Use `create(ModelRegistrySchema)` to create a new message. + */ +export const ModelRegistrySchema: GenMessage = /*@__PURE__*/ messageDesc(file_v1_model, 8) + +/** + * @generated from enum registry.v1.ImagePriceUnit + */ +export enum ImagePriceUnit { + /** + * @generated from enum value: IMAGE_PRICE_UNIT_UNSPECIFIED = 0; + */ + UNSPECIFIED = 0, + + /** + * @generated from enum value: IMAGE_PRICE_UNIT_IMAGE = 1; + */ + IMAGE = 1, + + /** + * @generated from enum value: IMAGE_PRICE_UNIT_PIXEL = 2; + */ + PIXEL = 2 +} + +/** + * Describes the enum registry.v1.ImagePriceUnit. + */ +export const ImagePriceUnitSchema: GenEnum = /*@__PURE__*/ enumDesc(file_v1_model, 0) diff --git a/packages/provider-registry/src/gen/v1/provider_models_pb.ts b/packages/provider-registry/src/gen/v1/provider_models_pb.ts new file mode 100644 index 00000000000..ab0bbc51222 --- /dev/null +++ b/packages/provider-registry/src/gen/v1/provider_models_pb.ts @@ -0,0 +1,208 @@ +// @generated by protoc-gen-es v2.11.0 with parameter "target=ts" +// @generated from file v1/provider_models.proto (package registry.v1, syntax proto3) +/* eslint-disable */ + +import type { GenFile, GenMessage } from '@bufbuild/protobuf/codegenv2' +import { fileDesc, messageDesc } from '@bufbuild/protobuf/codegenv2' +import type { EndpointType, Modality, ModelCapability } from './common_pb' +import { file_v1_common } from './common_pb' +import type { ModelPricing, ParameterSupport, ReasoningSupport } from './model_pb' +import { file_v1_model } from './model_pb' +import type { Message } from '@bufbuild/protobuf' + +/** + * Describes the file v1/provider_models.proto. + */ +export const file_v1_provider_models: GenFile = + /*@__PURE__*/ + fileDesc( + 'Chh2MS9wcm92aWRlcl9tb2RlbHMucHJvdG8SC3JlZ2lzdHJ5LnYxIpoBChJDYXBhYmlsaXR5T3ZlcnJpZGUSKQoDYWRkGAEgAygOMhwucmVnaXN0cnkudjEuTW9kZWxDYXBhYmlsaXR5EiwKBnJlbW92ZRgCIAMoDjIcLnJlZ2lzdHJ5LnYxLk1vZGVsQ2FwYWJpbGl0eRIrCgVmb3JjZRgDIAMoDjIcLnJlZ2lzdHJ5LnYxLk1vZGVsQ2FwYWJpbGl0eSLPAQoLTW9kZWxMaW1pdHMSGwoOY29udGV4dF93aW5kb3cYASABKA1IAIgBARIeChFtYXhfb3V0cHV0X3Rva2VucxgCIAEoDUgBiAEBEh0KEG1heF9pbnB1dF90b2tlbnMYAyABKA1IAogBARIXCgpyYXRlX2xpbWl0GAQgASgNSAOIAQFCEQoPX2NvbnRleHRfd2luZG93QhQKEl9tYXhfb3V0cHV0X3Rva2Vuc0ITChFfbWF4X2lucHV0X3Rva2Vuc0INCgtfcmF0ZV9saW1pdCKOBgoVUHJvdmlkZXJNb2RlbE92ZXJyaWRlEhMKC3Byb3ZpZGVyX2lkGAEgASgJEhAKCG1vZGVsX2lkGAIgASgJEhkKDGFwaV9tb2RlbF9pZBgDIAEoCUgAiAEBEhoKDW1vZGVsX3ZhcmlhbnQYBCABKAlIAYgBARI6CgxjYXBhYmlsaXRpZXMYBSABKAsyHy5yZWdpc3RyeS52MS5DYXBhYmlsaXR5T3ZlcnJpZGVIAogBARItCgZsaW1pdHMYBiABKAsyGC5yZWdpc3RyeS52MS5Nb2RlbExpbWl0c0gDiAEBEi8KB3ByaWNpbmcYByABKAsyGS5yZWdpc3RyeS52MS5Nb2RlbFByaWNpbmdIBIgBARI1CglyZWFzb25pbmcYCCABKAsyHS5yZWdpc3RyeS52MS5SZWFzb25pbmdTdXBwb3J0SAWIAQESPQoRcGFyYW1ldGVyX3N1cHBvcnQYCSABKAsyHS5yZWdpc3RyeS52MS5QYXJhbWV0ZXJTdXBwb3J0SAaIAQESMQoOZW5kcG9pbnRfdHlwZXMYCiADKA4yGS5yZWdpc3RyeS52MS5FbmRwb2ludFR5cGUSLwoQaW5wdXRfbW9kYWxpdGllcxgLIAMoDjIVLnJlZ2lzdHJ5LnYxLk1vZGFsaXR5EjAKEW91dHB1dF9tb2RhbGl0aWVzGAwgAygOMhUucmVnaXN0cnkudjEuTW9kYWxpdHkSFQoIZGlzYWJsZWQYDSABKAhIB4gBARIZCgxyZXBsYWNlX3dpdGgYDiABKAlICIgBARITCgZyZWFzb24YDyABKAlICYgBARIQCghwcmlvcml0eRgQIAEoDUIPCg1fYXBpX21vZGVsX2lkQhAKDl9tb2RlbF92YXJpYW50Qg8KDV9jYXBhYmlsaXRpZXNCCQoHX2xpbWl0c0IKCghfcHJpY2luZ0IMCgpfcmVhc29uaW5nQhQKEl9wYXJhbWV0ZXJfc3VwcG9ydEILCglfZGlzYWJsZWRCDwoNX3JlcGxhY2Vfd2l0aEIJCgdfcmVhc29uIl8KFVByb3ZpZGVyTW9kZWxSZWdpc3RyeRIPCgd2ZXJzaW9uGAEgASgJEjUKCW92ZXJyaWRlcxgCIAMoCzIiLnJlZ2lzdHJ5LnYxLlByb3ZpZGVyTW9kZWxPdmVycmlkZWIGcHJvdG8z', + [file_v1_common, file_v1_model] + ) + +/** + * @generated from message registry.v1.CapabilityOverride + */ +export type CapabilityOverride = Message<'registry.v1.CapabilityOverride'> & { + /** + * @generated from field: repeated registry.v1.ModelCapability add = 1; + */ + add: ModelCapability[] + + /** + * @generated from field: repeated registry.v1.ModelCapability remove = 2; + */ + remove: ModelCapability[] + + /** + * @generated from field: repeated registry.v1.ModelCapability force = 3; + */ + force: ModelCapability[] +} + +/** + * Describes the message registry.v1.CapabilityOverride. + * Use `create(CapabilityOverrideSchema)` to create a new message. + */ +export const CapabilityOverrideSchema: GenMessage = + /*@__PURE__*/ + messageDesc(file_v1_provider_models, 0) + +/** + * @generated from message registry.v1.ModelLimits + */ +export type ModelLimits = Message<'registry.v1.ModelLimits'> & { + /** + * @generated from field: optional uint32 context_window = 1; + */ + contextWindow?: number + + /** + * @generated from field: optional uint32 max_output_tokens = 2; + */ + maxOutputTokens?: number + + /** + * @generated from field: optional uint32 max_input_tokens = 3; + */ + maxInputTokens?: number + + /** + * @generated from field: optional uint32 rate_limit = 4; + */ + rateLimit?: number +} + +/** + * Describes the message registry.v1.ModelLimits. + * Use `create(ModelLimitsSchema)` to create a new message. + */ +export const ModelLimitsSchema: GenMessage = /*@__PURE__*/ messageDesc(file_v1_provider_models, 1) + +/** + * @generated from message registry.v1.ProviderModelOverride + */ +export type ProviderModelOverride = Message<'registry.v1.ProviderModelOverride'> & { + /** + * Identification + * + * @generated from field: string provider_id = 1; + */ + providerId: string + + /** + * @generated from field: string model_id = 2; + */ + modelId: string + + /** + * @generated from field: optional string api_model_id = 3; + */ + apiModelId?: string + + /** + * @generated from field: optional string model_variant = 4; + */ + modelVariant?: string + + /** + * Overrides + * + * @generated from field: optional registry.v1.CapabilityOverride capabilities = 5; + */ + capabilities?: CapabilityOverride + + /** + * @generated from field: optional registry.v1.ModelLimits limits = 6; + */ + limits?: ModelLimits + + /** + * @generated from field: optional registry.v1.ModelPricing pricing = 7; + */ + pricing?: ModelPricing + + /** + * @generated from field: optional registry.v1.ReasoningSupport reasoning = 8; + */ + reasoning?: ReasoningSupport + + /** + * @generated from field: optional registry.v1.ParameterSupport parameter_support = 9; + */ + parameterSupport?: ParameterSupport + + /** + * @generated from field: repeated registry.v1.EndpointType endpoint_types = 10; + */ + endpointTypes: EndpointType[] + + /** + * @generated from field: repeated registry.v1.Modality input_modalities = 11; + */ + inputModalities: Modality[] + + /** + * @generated from field: repeated registry.v1.Modality output_modalities = 12; + */ + outputModalities: Modality[] + + /** + * Status + * + * @generated from field: optional bool disabled = 13; + */ + disabled?: boolean + + /** + * @generated from field: optional string replace_with = 14; + */ + replaceWith?: string + + /** + * Metadata + * + * @generated from field: optional string reason = 15; + */ + reason?: string + + /** + * 0 = auto, 100+ = manual + * + * @generated from field: uint32 priority = 16; + */ + priority: number +} + +/** + * Describes the message registry.v1.ProviderModelOverride. + * Use `create(ProviderModelOverrideSchema)` to create a new message. + */ +export const ProviderModelOverrideSchema: GenMessage = + /*@__PURE__*/ + messageDesc(file_v1_provider_models, 2) + +/** + * Top-level container + * + * @generated from message registry.v1.ProviderModelRegistry + */ +export type ProviderModelRegistry = Message<'registry.v1.ProviderModelRegistry'> & { + /** + * @generated from field: string version = 1; + */ + version: string + + /** + * @generated from field: repeated registry.v1.ProviderModelOverride overrides = 2; + */ + overrides: ProviderModelOverride[] +} + +/** + * Describes the message registry.v1.ProviderModelRegistry. + * Use `create(ProviderModelRegistrySchema)` to create a new message. + */ +export const ProviderModelRegistrySchema: GenMessage = + /*@__PURE__*/ + messageDesc(file_v1_provider_models, 3) diff --git a/packages/provider-catalog/src/gen/v1/provider_pb.ts b/packages/provider-registry/src/gen/v1/provider_pb.ts similarity index 50% rename from packages/provider-catalog/src/gen/v1/provider_pb.ts rename to packages/provider-registry/src/gen/v1/provider_pb.ts index 20537e8ba5f..a8245985139 100644 --- a/packages/provider-catalog/src/gen/v1/provider_pb.ts +++ b/packages/provider-registry/src/gen/v1/provider_pb.ts @@ -1,5 +1,5 @@ // @generated by protoc-gen-es v2.11.0 with parameter "target=ts" -// @generated from file v1/provider.proto (package catalog.v1, syntax proto3) +// @generated from file v1/provider.proto (package registry.v1, syntax proto3) /* eslint-disable */ import type { GenEnum, GenFile, GenMessage } from '@bufbuild/protobuf/codegenv2' @@ -14,16 +14,16 @@ import type { Message } from '@bufbuild/protobuf' export const file_v1_provider: GenFile = /*@__PURE__*/ fileDesc( - 'ChF2MS9wcm92aWRlci5wcm90bxIKY2F0YWxvZy52MSKfAgoLQXBpRmVhdHVyZXMSGgoNYXJyYXlfY29udGVudBgBIAEoCEgAiAEBEhsKDnN0cmVhbV9vcHRpb25zGAIgASgISAGIAQESGwoOZGV2ZWxvcGVyX3JvbGUYAyABKAhIAogBARIZCgxzZXJ2aWNlX3RpZXIYBCABKAhIA4gBARIWCgl2ZXJib3NpdHkYBSABKAhIBIgBARIcCg9lbmFibGVfdGhpbmtpbmcYBiABKAhIBYgBAUIQCg5fYXJyYXlfY29udGVudEIRCg9fc3RyZWFtX29wdGlvbnNCEQoPX2RldmVsb3Blcl9yb2xlQg8KDV9zZXJ2aWNlX3RpZXJCDAoKX3ZlcmJvc2l0eUISChBfZW5hYmxlX3RoaW5raW5nInIKGU9wZW5BSUNoYXRSZWFzb25pbmdGb3JtYXQSQAoQcmVhc29uaW5nX2VmZm9ydBgBIAEoDjIhLmNhdGFsb2cudjEuT3BlbkFJUmVhc29uaW5nRWZmb3J0SACIAQFCEwoRX3JlYXNvbmluZ19lZmZvcnQipwEKHk9wZW5BSVJlc3BvbnNlc1JlYXNvbmluZ0Zvcm1hdBI2CgZlZmZvcnQYASABKA4yIS5jYXRhbG9nLnYxLk9wZW5BSVJlYXNvbmluZ0VmZm9ydEgAiAEBEjYKB3N1bW1hcnkYAiABKA4yIC5jYXRhbG9nLnYxLlJlc3BvbnNlc1N1bW1hcnlNb2RlSAGIAQFCCQoHX2VmZm9ydEIKCghfc3VtbWFyeSLNAQoYQW50aHJvcGljUmVhc29uaW5nRm9ybWF0EjQKBHR5cGUYASABKA4yIS5jYXRhbG9nLnYxLkFudGhyb3BpY1RoaW5raW5nVHlwZUgAiAEBEhoKDWJ1ZGdldF90b2tlbnMYAiABKA1IAYgBARI5CgZlZmZvcnQYAyABKA4yJC5jYXRhbG9nLnYxLkFudGhyb3BpY1JlYXNvbmluZ0VmZm9ydEgCiAEBQgcKBV90eXBlQhAKDl9idWRnZXRfdG9rZW5zQgkKB19lZmZvcnQifAoUR2VtaW5pVGhpbmtpbmdDb25maWcSHQoQaW5jbHVkZV90aG91Z2h0cxgBIAEoCEgAiAEBEhwKD3RoaW5raW5nX2J1ZGdldBgCIAEoDUgBiAEBQhMKEV9pbmNsdWRlX3Rob3VnaHRzQhIKEF90aGlua2luZ19idWRnZXQimQEKFUdlbWluaVJlYXNvbmluZ0Zvcm1hdBI7Cg90aGlua2luZ19jb25maWcYASABKAsyIC5jYXRhbG9nLnYxLkdlbWluaVRoaW5raW5nQ29uZmlnSAASOQoOdGhpbmtpbmdfbGV2ZWwYAiABKA4yHy5jYXRhbG9nLnYxLkdlbWluaVRoaW5raW5nTGV2ZWxIAEIICgZjb25maWciqAEKGU9wZW5Sb3V0ZXJSZWFzb25pbmdGb3JtYXQSNgoGZWZmb3J0GAEgASgOMiEuY2F0YWxvZy52MS5PcGVuQUlSZWFzb25pbmdFZmZvcnRIAIgBARIXCgptYXhfdG9rZW5zGAIgASgNSAGIAQESFAoHZXhjbHVkZRgDIAEoCEgCiAEBQgkKB19lZmZvcnRCDQoLX21heF90b2tlbnNCCgoIX2V4Y2x1ZGUigwEKHUVuYWJsZVRoaW5raW5nUmVhc29uaW5nRm9ybWF0EhwKD2VuYWJsZV90aGlua2luZxgBIAEoCEgAiAEBEhwKD3RoaW5raW5nX2J1ZGdldBgCIAEoDUgBiAEBQhIKEF9lbmFibGVfdGhpbmtpbmdCEgoQX3RoaW5raW5nX2J1ZGdldCJlChtUaGlua2luZ1R5cGVSZWFzb25pbmdGb3JtYXQSNAoNdGhpbmtpbmdfdHlwZRgBIAEoDjIYLmNhdGFsb2cudjEuVGhpbmtpbmdUeXBlSACIAQFCEAoOX3RoaW5raW5nX3R5cGUihAEKGERhc2hzY29wZVJlYXNvbmluZ0Zvcm1hdBIcCg9lbmFibGVfdGhpbmtpbmcYASABKAhIAIgBARIfChJpbmNyZW1lbnRhbF9vdXRwdXQYAiABKAhIAYgBAUISChBfZW5hYmxlX3RoaW5raW5nQhUKE19pbmNyZW1lbnRhbF9vdXRwdXQicQoZU2VsZkhvc3RlZFJlYXNvbmluZ0Zvcm1hdBIcCg9lbmFibGVfdGhpbmtpbmcYASABKAhIAIgBARIVCgh0aGlua2luZxgCIAEoCEgBiAEBQhIKEF9lbmFibGVfdGhpbmtpbmdCCwoJX3RoaW5raW5nItcEChdQcm92aWRlclJlYXNvbmluZ0Zvcm1hdBI8CgtvcGVuYWlfY2hhdBgBIAEoCzIlLmNhdGFsb2cudjEuT3BlbkFJQ2hhdFJlYXNvbmluZ0Zvcm1hdEgAEkYKEG9wZW5haV9yZXNwb25zZXMYAiABKAsyKi5jYXRhbG9nLnYxLk9wZW5BSVJlc3BvbnNlc1JlYXNvbmluZ0Zvcm1hdEgAEjkKCWFudGhyb3BpYxgDIAEoCzIkLmNhdGFsb2cudjEuQW50aHJvcGljUmVhc29uaW5nRm9ybWF0SAASMwoGZ2VtaW5pGAQgASgLMiEuY2F0YWxvZy52MS5HZW1pbmlSZWFzb25pbmdGb3JtYXRIABI7CgpvcGVucm91dGVyGAUgASgLMiUuY2F0YWxvZy52MS5PcGVuUm91dGVyUmVhc29uaW5nRm9ybWF0SAASRAoPZW5hYmxlX3RoaW5raW5nGAYgASgLMikuY2F0YWxvZy52MS5FbmFibGVUaGlua2luZ1JlYXNvbmluZ0Zvcm1hdEgAEkAKDXRoaW5raW5nX3R5cGUYByABKAsyJy5jYXRhbG9nLnYxLlRoaW5raW5nVHlwZVJlYXNvbmluZ0Zvcm1hdEgAEjkKCWRhc2hzY29wZRgIIAEoCzIkLmNhdGFsb2cudjEuRGFzaHNjb3BlUmVhc29uaW5nRm9ybWF0SAASPAoLc2VsZl9ob3N0ZWQYCSABKAsyJS5jYXRhbG9nLnYxLlNlbGZIb3N0ZWRSZWFzb25pbmdGb3JtYXRIAEIICgZmb3JtYXQikwEKD1Byb3ZpZGVyV2Vic2l0ZRIVCghvZmZpY2lhbBgBIAEoCUgAiAEBEhEKBGRvY3MYAiABKAlIAYgBARIUCgdhcGlfa2V5GAMgASgJSAKIAQESEwoGbW9kZWxzGAQgASgJSAOIAQFCCwoJX29mZmljaWFsQgcKBV9kb2NzQgoKCF9hcGlfa2V5QgkKB19tb2RlbHMiewoNTW9kZWxzQXBpVXJscxIUCgdkZWZhdWx0GAEgASgJSACIAQESFgoJZW1iZWRkaW5nGAIgASgJSAGIAQESFQoIcmVyYW5rZXIYAyABKAlIAogBAUIKCghfZGVmYXVsdEIMCgpfZW1iZWRkaW5nQgsKCV9yZXJhbmtlciJRChBQcm92aWRlck1ldGFkYXRhEjEKB3dlYnNpdGUYASABKAsyGy5jYXRhbG9nLnYxLlByb3ZpZGVyV2Vic2l0ZUgAiAEBQgoKCF93ZWJzaXRlItoBCg5FbmRwb2ludENvbmZpZxIVCghiYXNlX3VybBgBIAEoCUgAiAEBEjcKD21vZGVsc19hcGlfdXJscxgCIAEoCzIZLmNhdGFsb2cudjEuTW9kZWxzQXBpVXJsc0gBiAEBEkIKEHJlYXNvbmluZ19mb3JtYXQYAyABKAsyIy5jYXRhbG9nLnYxLlByb3ZpZGVyUmVhc29uaW5nRm9ybWF0SAKIAQFCCwoJX2Jhc2VfdXJsQhIKEF9tb2RlbHNfYXBpX3VybHNCEwoRX3JlYXNvbmluZ19mb3JtYXQikgQKDlByb3ZpZGVyQ29uZmlnEgoKAmlkGAEgASgJEgwKBG5hbWUYAiABKAkSGAoLZGVzY3JpcHRpb24YAyABKAlIAIgBARJJChBlbmRwb2ludF9jb25maWdzGAogAygLMi8uY2F0YWxvZy52MS5Qcm92aWRlckNvbmZpZy5FbmRwb2ludENvbmZpZ3NFbnRyeRI8ChVkZWZhdWx0X2NoYXRfZW5kcG9pbnQYBSABKA4yGC5jYXRhbG9nLnYxLkVuZHBvaW50VHlwZUgBiAEBEjIKDGFwaV9mZWF0dXJlcxgGIAEoCzIXLmNhdGFsb2cudjEuQXBpRmVhdHVyZXNIAogBARIzCghtZXRhZGF0YRgIIAEoCzIcLmNhdGFsb2cudjEuUHJvdmlkZXJNZXRhZGF0YUgDiAEBGlIKFEVuZHBvaW50Q29uZmlnc0VudHJ5EgsKA2tleRgBIAEoBRIpCgV2YWx1ZRgCIAEoCzIaLmNhdGFsb2cudjEuRW5kcG9pbnRDb25maWc6AjgBQg4KDF9kZXNjcmlwdGlvbkIYChZfZGVmYXVsdF9jaGF0X2VuZHBvaW50Qg8KDV9hcGlfZmVhdHVyZXNCCwoJX21ldGFkYXRhSgQIBBAFSgQIBxAISgQICRAKUgliYXNlX3VybHNSD21vZGVsc19hcGlfdXJsc1IQcmVhc29uaW5nX2Zvcm1hdCJRCg9Qcm92aWRlckNhdGFsb2cSDwoHdmVyc2lvbhgBIAEoCRItCglwcm92aWRlcnMYAiADKAsyGi5jYXRhbG9nLnYxLlByb3ZpZGVyQ29uZmlnKqgBChRSZXNwb25zZXNTdW1tYXJ5TW9kZRImCiJSRVNQT05TRVNfU1VNTUFSWV9NT0RFX1VOU1BFQ0lGSUVEEAASHwobUkVTUE9OU0VTX1NVTU1BUllfTU9ERV9BVVRPEAESIgoeUkVTUE9OU0VTX1NVTU1BUllfTU9ERV9DT05DSVNFEAISIwofUkVTUE9OU0VTX1NVTU1BUllfTU9ERV9ERVRBSUxFRBADKrEBChVBbnRocm9waWNUaGlua2luZ1R5cGUSJwojQU5USFJPUElDX1RISU5LSU5HX1RZUEVfVU5TUEVDSUZJRUQQABIjCh9BTlRIUk9QSUNfVEhJTktJTkdfVFlQRV9FTkFCTEVEEAESJAogQU5USFJPUElDX1RISU5LSU5HX1RZUEVfRElTQUJMRUQQAhIkCiBBTlRIUk9QSUNfVEhJTktJTkdfVFlQRV9BREFQVElWRRADKsABChNHZW1pbmlUaGlua2luZ0xldmVsEiUKIUdFTUlOSV9USElOS0lOR19MRVZFTF9VTlNQRUNJRklFRBAAEiEKHUdFTUlOSV9USElOS0lOR19MRVZFTF9NSU5JTUFMEAESHQoZR0VNSU5JX1RISU5LSU5HX0xFVkVMX0xPVxACEiAKHEdFTUlOSV9USElOS0lOR19MRVZFTF9NRURJVU0QAxIeChpHRU1JTklfVEhJTktJTkdfTEVWRUxfSElHSBAEKnwKDFRoaW5raW5nVHlwZRIdChlUSElOS0lOR19UWVBFX1VOU1BFQ0lGSUVEEAASGQoVVEhJTktJTkdfVFlQRV9FTkFCTEVEEAESGgoWVEhJTktJTkdfVFlQRV9ESVNBQkxFRBACEhYKElRISU5LSU5HX1RZUEVfQVVUTxADYgZwcm90bzM', + 'ChF2MS9wcm92aWRlci5wcm90bxILcmVnaXN0cnkudjEinwIKC0FwaUZlYXR1cmVzEhoKDWFycmF5X2NvbnRlbnQYASABKAhIAIgBARIbCg5zdHJlYW1fb3B0aW9ucxgCIAEoCEgBiAEBEhsKDmRldmVsb3Blcl9yb2xlGAMgASgISAKIAQESGQoMc2VydmljZV90aWVyGAQgASgISAOIAQESFgoJdmVyYm9zaXR5GAUgASgISASIAQESHAoPZW5hYmxlX3RoaW5raW5nGAYgASgISAWIAQFCEAoOX2FycmF5X2NvbnRlbnRCEQoPX3N0cmVhbV9vcHRpb25zQhEKD19kZXZlbG9wZXJfcm9sZUIPCg1fc2VydmljZV90aWVyQgwKCl92ZXJib3NpdHlCEgoQX2VuYWJsZV90aGlua2luZyJzChlPcGVuQUlDaGF0UmVhc29uaW5nRm9ybWF0EkEKEHJlYXNvbmluZ19lZmZvcnQYASABKA4yIi5yZWdpc3RyeS52MS5PcGVuQUlSZWFzb25pbmdFZmZvcnRIAIgBAUITChFfcmVhc29uaW5nX2VmZm9ydCKpAQoeT3BlbkFJUmVzcG9uc2VzUmVhc29uaW5nRm9ybWF0EjcKBmVmZm9ydBgBIAEoDjIiLnJlZ2lzdHJ5LnYxLk9wZW5BSVJlYXNvbmluZ0VmZm9ydEgAiAEBEjcKB3N1bW1hcnkYAiABKA4yIS5yZWdpc3RyeS52MS5SZXNwb25zZXNTdW1tYXJ5TW9kZUgBiAEBQgkKB19lZmZvcnRCCgoIX3N1bW1hcnkizwEKGEFudGhyb3BpY1JlYXNvbmluZ0Zvcm1hdBI1CgR0eXBlGAEgASgOMiIucmVnaXN0cnkudjEuQW50aHJvcGljVGhpbmtpbmdUeXBlSACIAQESGgoNYnVkZ2V0X3Rva2VucxgCIAEoDUgBiAEBEjoKBmVmZm9ydBgDIAEoDjIlLnJlZ2lzdHJ5LnYxLkFudGhyb3BpY1JlYXNvbmluZ0VmZm9ydEgCiAEBQgcKBV90eXBlQhAKDl9idWRnZXRfdG9rZW5zQgkKB19lZmZvcnQifAoUR2VtaW5pVGhpbmtpbmdDb25maWcSHQoQaW5jbHVkZV90aG91Z2h0cxgBIAEoCEgAiAEBEhwKD3RoaW5raW5nX2J1ZGdldBgCIAEoDUgBiAEBQhMKEV9pbmNsdWRlX3Rob3VnaHRzQhIKEF90aGlua2luZ19idWRnZXQimwEKFUdlbWluaVJlYXNvbmluZ0Zvcm1hdBI8Cg90aGlua2luZ19jb25maWcYASABKAsyIS5yZWdpc3RyeS52MS5HZW1pbmlUaGlua2luZ0NvbmZpZ0gAEjoKDnRoaW5raW5nX2xldmVsGAIgASgOMiAucmVnaXN0cnkudjEuR2VtaW5pVGhpbmtpbmdMZXZlbEgAQggKBmNvbmZpZyKpAQoZT3BlblJvdXRlclJlYXNvbmluZ0Zvcm1hdBI3CgZlZmZvcnQYASABKA4yIi5yZWdpc3RyeS52MS5PcGVuQUlSZWFzb25pbmdFZmZvcnRIAIgBARIXCgptYXhfdG9rZW5zGAIgASgNSAGIAQESFAoHZXhjbHVkZRgDIAEoCEgCiAEBQgkKB19lZmZvcnRCDQoLX21heF90b2tlbnNCCgoIX2V4Y2x1ZGUigwEKHUVuYWJsZVRoaW5raW5nUmVhc29uaW5nRm9ybWF0EhwKD2VuYWJsZV90aGlua2luZxgBIAEoCEgAiAEBEhwKD3RoaW5raW5nX2J1ZGdldBgCIAEoDUgBiAEBQhIKEF9lbmFibGVfdGhpbmtpbmdCEgoQX3RoaW5raW5nX2J1ZGdldCJmChtUaGlua2luZ1R5cGVSZWFzb25pbmdGb3JtYXQSNQoNdGhpbmtpbmdfdHlwZRgBIAEoDjIZLnJlZ2lzdHJ5LnYxLlRoaW5raW5nVHlwZUgAiAEBQhAKDl90aGlua2luZ190eXBlIoQBChhEYXNoc2NvcGVSZWFzb25pbmdGb3JtYXQSHAoPZW5hYmxlX3RoaW5raW5nGAEgASgISACIAQESHwoSaW5jcmVtZW50YWxfb3V0cHV0GAIgASgISAGIAQFCEgoQX2VuYWJsZV90aGlua2luZ0IVChNfaW5jcmVtZW50YWxfb3V0cHV0InEKGVNlbGZIb3N0ZWRSZWFzb25pbmdGb3JtYXQSHAoPZW5hYmxlX3RoaW5raW5nGAEgASgISACIAQESFQoIdGhpbmtpbmcYAiABKAhIAYgBAUISChBfZW5hYmxlX3RoaW5raW5nQgsKCV90aGlua2luZyLgBAoXUHJvdmlkZXJSZWFzb25pbmdGb3JtYXQSPQoLb3BlbmFpX2NoYXQYASABKAsyJi5yZWdpc3RyeS52MS5PcGVuQUlDaGF0UmVhc29uaW5nRm9ybWF0SAASRwoQb3BlbmFpX3Jlc3BvbnNlcxgCIAEoCzIrLnJlZ2lzdHJ5LnYxLk9wZW5BSVJlc3BvbnNlc1JlYXNvbmluZ0Zvcm1hdEgAEjoKCWFudGhyb3BpYxgDIAEoCzIlLnJlZ2lzdHJ5LnYxLkFudGhyb3BpY1JlYXNvbmluZ0Zvcm1hdEgAEjQKBmdlbWluaRgEIAEoCzIiLnJlZ2lzdHJ5LnYxLkdlbWluaVJlYXNvbmluZ0Zvcm1hdEgAEjwKCm9wZW5yb3V0ZXIYBSABKAsyJi5yZWdpc3RyeS52MS5PcGVuUm91dGVyUmVhc29uaW5nRm9ybWF0SAASRQoPZW5hYmxlX3RoaW5raW5nGAYgASgLMioucmVnaXN0cnkudjEuRW5hYmxlVGhpbmtpbmdSZWFzb25pbmdGb3JtYXRIABJBCg10aGlua2luZ190eXBlGAcgASgLMigucmVnaXN0cnkudjEuVGhpbmtpbmdUeXBlUmVhc29uaW5nRm9ybWF0SAASOgoJZGFzaHNjb3BlGAggASgLMiUucmVnaXN0cnkudjEuRGFzaHNjb3BlUmVhc29uaW5nRm9ybWF0SAASPQoLc2VsZl9ob3N0ZWQYCSABKAsyJi5yZWdpc3RyeS52MS5TZWxmSG9zdGVkUmVhc29uaW5nRm9ybWF0SABCCAoGZm9ybWF0IpMBCg9Qcm92aWRlcldlYnNpdGUSFQoIb2ZmaWNpYWwYASABKAlIAIgBARIRCgRkb2NzGAIgASgJSAGIAQESFAoHYXBpX2tleRgDIAEoCUgCiAEBEhMKBm1vZGVscxgEIAEoCUgDiAEBQgsKCV9vZmZpY2lhbEIHCgVfZG9jc0IKCghfYXBpX2tleUIJCgdfbW9kZWxzInsKDU1vZGVsc0FwaVVybHMSFAoHZGVmYXVsdBgBIAEoCUgAiAEBEhYKCWVtYmVkZGluZxgCIAEoCUgBiAEBEhUKCHJlcmFua2VyGAMgASgJSAKIAQFCCgoIX2RlZmF1bHRCDAoKX2VtYmVkZGluZ0ILCglfcmVyYW5rZXIiUgoQUHJvdmlkZXJNZXRhZGF0YRIyCgd3ZWJzaXRlGAEgASgLMhwucmVnaXN0cnkudjEuUHJvdmlkZXJXZWJzaXRlSACIAQFCCgoIX3dlYnNpdGUi3AEKDkVuZHBvaW50Q29uZmlnEhUKCGJhc2VfdXJsGAEgASgJSACIAQESOAoPbW9kZWxzX2FwaV91cmxzGAIgASgLMhoucmVnaXN0cnkudjEuTW9kZWxzQXBpVXJsc0gBiAEBEkMKEHJlYXNvbmluZ19mb3JtYXQYAyABKAsyJC5yZWdpc3RyeS52MS5Qcm92aWRlclJlYXNvbmluZ0Zvcm1hdEgCiAEBQgsKCV9iYXNlX3VybEISChBfbW9kZWxzX2FwaV91cmxzQhMKEV9yZWFzb25pbmdfZm9ybWF0IpcECg5Qcm92aWRlckNvbmZpZxIKCgJpZBgBIAEoCRIMCgRuYW1lGAIgASgJEhgKC2Rlc2NyaXB0aW9uGAMgASgJSACIAQESSgoQZW5kcG9pbnRfY29uZmlncxgKIAMoCzIwLnJlZ2lzdHJ5LnYxLlByb3ZpZGVyQ29uZmlnLkVuZHBvaW50Q29uZmlnc0VudHJ5Ej0KFWRlZmF1bHRfY2hhdF9lbmRwb2ludBgFIAEoDjIZLnJlZ2lzdHJ5LnYxLkVuZHBvaW50VHlwZUgBiAEBEjMKDGFwaV9mZWF0dXJlcxgGIAEoCzIYLnJlZ2lzdHJ5LnYxLkFwaUZlYXR1cmVzSAKIAQESNAoIbWV0YWRhdGEYCCABKAsyHS5yZWdpc3RyeS52MS5Qcm92aWRlck1ldGFkYXRhSAOIAQEaUwoURW5kcG9pbnRDb25maWdzRW50cnkSCwoDa2V5GAEgASgFEioKBXZhbHVlGAIgASgLMhsucmVnaXN0cnkudjEuRW5kcG9pbnRDb25maWc6AjgBQg4KDF9kZXNjcmlwdGlvbkIYChZfZGVmYXVsdF9jaGF0X2VuZHBvaW50Qg8KDV9hcGlfZmVhdHVyZXNCCwoJX21ldGFkYXRhSgQIBBAFSgQIBxAISgQICRAKUgliYXNlX3VybHNSD21vZGVsc19hcGlfdXJsc1IQcmVhc29uaW5nX2Zvcm1hdCJTChBQcm92aWRlclJlZ2lzdHJ5Eg8KB3ZlcnNpb24YASABKAkSLgoJcHJvdmlkZXJzGAIgAygLMhsucmVnaXN0cnkudjEuUHJvdmlkZXJDb25maWcqqAEKFFJlc3BvbnNlc1N1bW1hcnlNb2RlEiYKIlJFU1BPTlNFU19TVU1NQVJZX01PREVfVU5TUEVDSUZJRUQQABIfChtSRVNQT05TRVNfU1VNTUFSWV9NT0RFX0FVVE8QARIiCh5SRVNQT05TRVNfU1VNTUFSWV9NT0RFX0NPTkNJU0UQAhIjCh9SRVNQT05TRVNfU1VNTUFSWV9NT0RFX0RFVEFJTEVEEAMqsQEKFUFudGhyb3BpY1RoaW5raW5nVHlwZRInCiNBTlRIUk9QSUNfVEhJTktJTkdfVFlQRV9VTlNQRUNJRklFRBAAEiMKH0FOVEhST1BJQ19USElOS0lOR19UWVBFX0VOQUJMRUQQARIkCiBBTlRIUk9QSUNfVEhJTktJTkdfVFlQRV9ESVNBQkxFRBACEiQKIEFOVEhST1BJQ19USElOS0lOR19UWVBFX0FEQVBUSVZFEAMqwAEKE0dlbWluaVRoaW5raW5nTGV2ZWwSJQohR0VNSU5JX1RISU5LSU5HX0xFVkVMX1VOU1BFQ0lGSUVEEAASIQodR0VNSU5JX1RISU5LSU5HX0xFVkVMX01JTklNQUwQARIdChlHRU1JTklfVEhJTktJTkdfTEVWRUxfTE9XEAISIAocR0VNSU5JX1RISU5LSU5HX0xFVkVMX01FRElVTRADEh4KGkdFTUlOSV9USElOS0lOR19MRVZFTF9ISUdIEAQqfAoMVGhpbmtpbmdUeXBlEh0KGVRISU5LSU5HX1RZUEVfVU5TUEVDSUZJRUQQABIZChVUSElOS0lOR19UWVBFX0VOQUJMRUQQARIaChZUSElOS0lOR19UWVBFX0RJU0FCTEVEEAISFgoSVEhJTktJTkdfVFlQRV9BVVRPEANiBnByb3RvMw', [file_v1_common] ) /** * --- Request format flags --- * - * @generated from message catalog.v1.ApiFeatures + * @generated from message registry.v1.ApiFeatures */ -export type ApiFeatures = Message<'catalog.v1.ApiFeatures'> & { +export type ApiFeatures = Message<'registry.v1.ApiFeatures'> & { /** * Whether the provider supports array-formatted content in messages * @@ -68,23 +68,23 @@ export type ApiFeatures = Message<'catalog.v1.ApiFeatures'> & { } /** - * Describes the message catalog.v1.ApiFeatures. + * Describes the message registry.v1.ApiFeatures. * Use `create(ApiFeaturesSchema)` to create a new message. */ export const ApiFeaturesSchema: GenMessage = /*@__PURE__*/ messageDesc(file_v1_provider, 0) /** - * @generated from message catalog.v1.OpenAIChatReasoningFormat + * @generated from message registry.v1.OpenAIChatReasoningFormat */ -export type OpenAIChatReasoningFormat = Message<'catalog.v1.OpenAIChatReasoningFormat'> & { +export type OpenAIChatReasoningFormat = Message<'registry.v1.OpenAIChatReasoningFormat'> & { /** - * @generated from field: optional catalog.v1.OpenAIReasoningEffort reasoning_effort = 1; + * @generated from field: optional registry.v1.OpenAIReasoningEffort reasoning_effort = 1; */ reasoningEffort?: OpenAIReasoningEffort } /** - * Describes the message catalog.v1.OpenAIChatReasoningFormat. + * Describes the message registry.v1.OpenAIChatReasoningFormat. * Use `create(OpenAIChatReasoningFormatSchema)` to create a new message. */ export const OpenAIChatReasoningFormatSchema: GenMessage = @@ -92,22 +92,22 @@ export const OpenAIChatReasoningFormatSchema: GenMessage & { +export type OpenAIResponsesReasoningFormat = Message<'registry.v1.OpenAIResponsesReasoningFormat'> & { /** - * @generated from field: optional catalog.v1.OpenAIReasoningEffort effort = 1; + * @generated from field: optional registry.v1.OpenAIReasoningEffort effort = 1; */ effort?: OpenAIReasoningEffort /** - * @generated from field: optional catalog.v1.ResponsesSummaryMode summary = 2; + * @generated from field: optional registry.v1.ResponsesSummaryMode summary = 2; */ summary?: ResponsesSummaryMode } /** - * Describes the message catalog.v1.OpenAIResponsesReasoningFormat. + * Describes the message registry.v1.OpenAIResponsesReasoningFormat. * Use `create(OpenAIResponsesReasoningFormatSchema)` to create a new message. */ export const OpenAIResponsesReasoningFormatSchema: GenMessage = @@ -115,11 +115,11 @@ export const OpenAIResponsesReasoningFormatSchema: GenMessage & { +export type AnthropicReasoningFormat = Message<'registry.v1.AnthropicReasoningFormat'> & { /** - * @generated from field: optional catalog.v1.AnthropicThinkingType type = 1; + * @generated from field: optional registry.v1.AnthropicThinkingType type = 1; */ type?: AnthropicThinkingType @@ -129,13 +129,13 @@ export type AnthropicReasoningFormat = Message<'catalog.v1.AnthropicReasoningFor budgetTokens?: number /** - * @generated from field: optional catalog.v1.AnthropicReasoningEffort effort = 3; + * @generated from field: optional registry.v1.AnthropicReasoningEffort effort = 3; */ effort?: AnthropicReasoningEffort } /** - * Describes the message catalog.v1.AnthropicReasoningFormat. + * Describes the message registry.v1.AnthropicReasoningFormat. * Use `create(AnthropicReasoningFormatSchema)` to create a new message. */ export const AnthropicReasoningFormatSchema: GenMessage = @@ -143,9 +143,9 @@ export const AnthropicReasoningFormatSchema: GenMessage & { +export type GeminiThinkingConfig = Message<'registry.v1.GeminiThinkingConfig'> & { /** * @generated from field: optional bool include_thoughts = 1; */ @@ -158,7 +158,7 @@ export type GeminiThinkingConfig = Message<'catalog.v1.GeminiThinkingConfig'> & } /** - * Describes the message catalog.v1.GeminiThinkingConfig. + * Describes the message registry.v1.GeminiThinkingConfig. * Use `create(GeminiThinkingConfigSchema)` to create a new message. */ export const GeminiThinkingConfigSchema: GenMessage = @@ -166,23 +166,23 @@ export const GeminiThinkingConfigSchema: GenMessage = messageDesc(file_v1_provider, 4) /** - * @generated from message catalog.v1.GeminiReasoningFormat + * @generated from message registry.v1.GeminiReasoningFormat */ -export type GeminiReasoningFormat = Message<'catalog.v1.GeminiReasoningFormat'> & { +export type GeminiReasoningFormat = Message<'registry.v1.GeminiReasoningFormat'> & { /** - * @generated from oneof catalog.v1.GeminiReasoningFormat.config + * @generated from oneof registry.v1.GeminiReasoningFormat.config */ config: | { /** - * @generated from field: catalog.v1.GeminiThinkingConfig thinking_config = 1; + * @generated from field: registry.v1.GeminiThinkingConfig thinking_config = 1; */ value: GeminiThinkingConfig case: 'thinkingConfig' } | { /** - * @generated from field: catalog.v1.GeminiThinkingLevel thinking_level = 2; + * @generated from field: registry.v1.GeminiThinkingLevel thinking_level = 2; */ value: GeminiThinkingLevel case: 'thinkingLevel' @@ -191,7 +191,7 @@ export type GeminiReasoningFormat = Message<'catalog.v1.GeminiReasoningFormat'> } /** - * Describes the message catalog.v1.GeminiReasoningFormat. + * Describes the message registry.v1.GeminiReasoningFormat. * Use `create(GeminiReasoningFormatSchema)` to create a new message. */ export const GeminiReasoningFormatSchema: GenMessage = @@ -199,11 +199,11 @@ export const GeminiReasoningFormatSchema: GenMessage = messageDesc(file_v1_provider, 5) /** - * @generated from message catalog.v1.OpenRouterReasoningFormat + * @generated from message registry.v1.OpenRouterReasoningFormat */ -export type OpenRouterReasoningFormat = Message<'catalog.v1.OpenRouterReasoningFormat'> & { +export type OpenRouterReasoningFormat = Message<'registry.v1.OpenRouterReasoningFormat'> & { /** - * @generated from field: optional catalog.v1.OpenAIReasoningEffort effort = 1; + * @generated from field: optional registry.v1.OpenAIReasoningEffort effort = 1; */ effort?: OpenAIReasoningEffort @@ -219,7 +219,7 @@ export type OpenRouterReasoningFormat = Message<'catalog.v1.OpenRouterReasoningF } /** - * Describes the message catalog.v1.OpenRouterReasoningFormat. + * Describes the message registry.v1.OpenRouterReasoningFormat. * Use `create(OpenRouterReasoningFormatSchema)` to create a new message. */ export const OpenRouterReasoningFormatSchema: GenMessage = @@ -229,9 +229,9 @@ export const OpenRouterReasoningFormatSchema: GenMessage & { +export type EnableThinkingReasoningFormat = Message<'registry.v1.EnableThinkingReasoningFormat'> & { /** * @generated from field: optional bool enable_thinking = 1; */ @@ -244,7 +244,7 @@ export type EnableThinkingReasoningFormat = Message<'catalog.v1.EnableThinkingRe } /** - * Describes the message catalog.v1.EnableThinkingReasoningFormat. + * Describes the message registry.v1.EnableThinkingReasoningFormat. * Use `create(EnableThinkingReasoningFormatSchema)` to create a new message. */ export const EnableThinkingReasoningFormatSchema: GenMessage = @@ -254,17 +254,17 @@ export const EnableThinkingReasoningFormatSchema: GenMessage & { +export type ThinkingTypeReasoningFormat = Message<'registry.v1.ThinkingTypeReasoningFormat'> & { /** - * @generated from field: optional catalog.v1.ThinkingType thinking_type = 1; + * @generated from field: optional registry.v1.ThinkingType thinking_type = 1; */ thinkingType?: ThinkingType } /** - * Describes the message catalog.v1.ThinkingTypeReasoningFormat. + * Describes the message registry.v1.ThinkingTypeReasoningFormat. * Use `create(ThinkingTypeReasoningFormatSchema)` to create a new message. */ export const ThinkingTypeReasoningFormatSchema: GenMessage = @@ -274,9 +274,9 @@ export const ThinkingTypeReasoningFormatSchema: GenMessage & { +export type DashscopeReasoningFormat = Message<'registry.v1.DashscopeReasoningFormat'> & { /** * @generated from field: optional bool enable_thinking = 1; */ @@ -289,7 +289,7 @@ export type DashscopeReasoningFormat = Message<'catalog.v1.DashscopeReasoningFor } /** - * Describes the message catalog.v1.DashscopeReasoningFormat. + * Describes the message registry.v1.DashscopeReasoningFormat. * Use `create(DashscopeReasoningFormatSchema)` to create a new message. */ export const DashscopeReasoningFormatSchema: GenMessage = @@ -299,9 +299,9 @@ export const DashscopeReasoningFormatSchema: GenMessage & { +export type SelfHostedReasoningFormat = Message<'registry.v1.SelfHostedReasoningFormat'> & { /** * @generated from field: optional bool enable_thinking = 1; */ @@ -314,7 +314,7 @@ export type SelfHostedReasoningFormat = Message<'catalog.v1.SelfHostedReasoningF } /** - * Describes the message catalog.v1.SelfHostedReasoningFormat. + * Describes the message registry.v1.SelfHostedReasoningFormat. * Use `create(SelfHostedReasoningFormatSchema)` to create a new message. */ export const SelfHostedReasoningFormatSchema: GenMessage = @@ -324,72 +324,72 @@ export const SelfHostedReasoningFormatSchema: GenMessage & { +export type ProviderReasoningFormat = Message<'registry.v1.ProviderReasoningFormat'> & { /** - * @generated from oneof catalog.v1.ProviderReasoningFormat.format + * @generated from oneof registry.v1.ProviderReasoningFormat.format */ format: | { /** - * @generated from field: catalog.v1.OpenAIChatReasoningFormat openai_chat = 1; + * @generated from field: registry.v1.OpenAIChatReasoningFormat openai_chat = 1; */ value: OpenAIChatReasoningFormat case: 'openaiChat' } | { /** - * @generated from field: catalog.v1.OpenAIResponsesReasoningFormat openai_responses = 2; + * @generated from field: registry.v1.OpenAIResponsesReasoningFormat openai_responses = 2; */ value: OpenAIResponsesReasoningFormat case: 'openaiResponses' } | { /** - * @generated from field: catalog.v1.AnthropicReasoningFormat anthropic = 3; + * @generated from field: registry.v1.AnthropicReasoningFormat anthropic = 3; */ value: AnthropicReasoningFormat case: 'anthropic' } | { /** - * @generated from field: catalog.v1.GeminiReasoningFormat gemini = 4; + * @generated from field: registry.v1.GeminiReasoningFormat gemini = 4; */ value: GeminiReasoningFormat case: 'gemini' } | { /** - * @generated from field: catalog.v1.OpenRouterReasoningFormat openrouter = 5; + * @generated from field: registry.v1.OpenRouterReasoningFormat openrouter = 5; */ value: OpenRouterReasoningFormat case: 'openrouter' } | { /** - * @generated from field: catalog.v1.EnableThinkingReasoningFormat enable_thinking = 6; + * @generated from field: registry.v1.EnableThinkingReasoningFormat enable_thinking = 6; */ value: EnableThinkingReasoningFormat case: 'enableThinking' } | { /** - * @generated from field: catalog.v1.ThinkingTypeReasoningFormat thinking_type = 7; + * @generated from field: registry.v1.ThinkingTypeReasoningFormat thinking_type = 7; */ value: ThinkingTypeReasoningFormat case: 'thinkingType' } | { /** - * @generated from field: catalog.v1.DashscopeReasoningFormat dashscope = 8; + * @generated from field: registry.v1.DashscopeReasoningFormat dashscope = 8; */ value: DashscopeReasoningFormat case: 'dashscope' } | { /** - * @generated from field: catalog.v1.SelfHostedReasoningFormat self_hosted = 9; + * @generated from field: registry.v1.SelfHostedReasoningFormat self_hosted = 9; */ value: SelfHostedReasoningFormat case: 'selfHosted' @@ -398,7 +398,7 @@ export type ProviderReasoningFormat = Message<'catalog.v1.ProviderReasoningForma } /** - * Describes the message catalog.v1.ProviderReasoningFormat. + * Describes the message registry.v1.ProviderReasoningFormat. * Use `create(ProviderReasoningFormatSchema)` to create a new message. */ export const ProviderReasoningFormatSchema: GenMessage = @@ -406,9 +406,9 @@ export const ProviderReasoningFormatSchema: GenMessage messageDesc(file_v1_provider, 11) /** - * @generated from message catalog.v1.ProviderWebsite + * @generated from message registry.v1.ProviderWebsite */ -export type ProviderWebsite = Message<'catalog.v1.ProviderWebsite'> & { +export type ProviderWebsite = Message<'registry.v1.ProviderWebsite'> & { /** * @generated from field: optional string official = 1; */ @@ -431,15 +431,15 @@ export type ProviderWebsite = Message<'catalog.v1.ProviderWebsite'> & { } /** - * Describes the message catalog.v1.ProviderWebsite. + * Describes the message registry.v1.ProviderWebsite. * Use `create(ProviderWebsiteSchema)` to create a new message. */ export const ProviderWebsiteSchema: GenMessage = /*@__PURE__*/ messageDesc(file_v1_provider, 12) /** - * @generated from message catalog.v1.ModelsApiUrls + * @generated from message registry.v1.ModelsApiUrls */ -export type ModelsApiUrls = Message<'catalog.v1.ModelsApiUrls'> & { +export type ModelsApiUrls = Message<'registry.v1.ModelsApiUrls'> & { /** * @generated from field: optional string default = 1; */ @@ -457,31 +457,31 @@ export type ModelsApiUrls = Message<'catalog.v1.ModelsApiUrls'> & { } /** - * Describes the message catalog.v1.ModelsApiUrls. + * Describes the message registry.v1.ModelsApiUrls. * Use `create(ModelsApiUrlsSchema)` to create a new message. */ export const ModelsApiUrlsSchema: GenMessage = /*@__PURE__*/ messageDesc(file_v1_provider, 13) /** - * @generated from message catalog.v1.ProviderMetadata + * @generated from message registry.v1.ProviderMetadata */ -export type ProviderMetadata = Message<'catalog.v1.ProviderMetadata'> & { +export type ProviderMetadata = Message<'registry.v1.ProviderMetadata'> & { /** - * @generated from field: optional catalog.v1.ProviderWebsite website = 1; + * @generated from field: optional registry.v1.ProviderWebsite website = 1; */ website?: ProviderWebsite } /** - * Describes the message catalog.v1.ProviderMetadata. + * Describes the message registry.v1.ProviderMetadata. * Use `create(ProviderMetadataSchema)` to create a new message. */ export const ProviderMetadataSchema: GenMessage = /*@__PURE__*/ messageDesc(file_v1_provider, 14) /** - * @generated from message catalog.v1.EndpointConfig + * @generated from message registry.v1.EndpointConfig */ -export type EndpointConfig = Message<'catalog.v1.EndpointConfig'> & { +export type EndpointConfig = Message<'registry.v1.EndpointConfig'> & { /** * Base URL for this endpoint type's API * @@ -492,28 +492,28 @@ export type EndpointConfig = Message<'catalog.v1.EndpointConfig'> & { /** * URLs for fetching available models via this endpoint type * - * @generated from field: optional catalog.v1.ModelsApiUrls models_api_urls = 2; + * @generated from field: optional registry.v1.ModelsApiUrls models_api_urls = 2; */ modelsApiUrls?: ModelsApiUrls /** * How this endpoint type expects reasoning parameters to be formatted * - * @generated from field: optional catalog.v1.ProviderReasoningFormat reasoning_format = 3; + * @generated from field: optional registry.v1.ProviderReasoningFormat reasoning_format = 3; */ reasoningFormat?: ProviderReasoningFormat } /** - * Describes the message catalog.v1.EndpointConfig. + * Describes the message registry.v1.EndpointConfig. * Use `create(EndpointConfigSchema)` to create a new message. */ export const EndpointConfigSchema: GenMessage = /*@__PURE__*/ messageDesc(file_v1_provider, 15) /** - * @generated from message catalog.v1.ProviderConfig + * @generated from message registry.v1.ProviderConfig */ -export type ProviderConfig = Message<'catalog.v1.ProviderConfig'> & { +export type ProviderConfig = Message<'registry.v1.ProviderConfig'> & { /** * @generated from field: string id = 1; */ @@ -532,28 +532,28 @@ export type ProviderConfig = Message<'catalog.v1.ProviderConfig'> & { /** * Per-endpoint-type configuration (key = EndpointType enum value) * - * @generated from field: map endpoint_configs = 10; + * @generated from field: map endpoint_configs = 10; */ endpointConfigs: { [key: number]: EndpointConfig } /** - * @generated from field: optional catalog.v1.EndpointType default_chat_endpoint = 5; + * @generated from field: optional registry.v1.EndpointType default_chat_endpoint = 5; */ defaultChatEndpoint?: EndpointType /** - * @generated from field: optional catalog.v1.ApiFeatures api_features = 6; + * @generated from field: optional registry.v1.ApiFeatures api_features = 6; */ apiFeatures?: ApiFeatures /** - * @generated from field: optional catalog.v1.ProviderMetadata metadata = 8; + * @generated from field: optional registry.v1.ProviderMetadata metadata = 8; */ metadata?: ProviderMetadata } /** - * Describes the message catalog.v1.ProviderConfig. + * Describes the message registry.v1.ProviderConfig. * Use `create(ProviderConfigSchema)` to create a new message. */ export const ProviderConfigSchema: GenMessage = /*@__PURE__*/ messageDesc(file_v1_provider, 16) @@ -561,28 +561,28 @@ export const ProviderConfigSchema: GenMessage = /*@__PURE__*/ me /** * Top-level container * - * @generated from message catalog.v1.ProviderCatalog + * @generated from message registry.v1.ProviderRegistry */ -export type ProviderCatalog = Message<'catalog.v1.ProviderCatalog'> & { +export type ProviderRegistry = Message<'registry.v1.ProviderRegistry'> & { /** * @generated from field: string version = 1; */ version: string /** - * @generated from field: repeated catalog.v1.ProviderConfig providers = 2; + * @generated from field: repeated registry.v1.ProviderConfig providers = 2; */ providers: ProviderConfig[] } /** - * Describes the message catalog.v1.ProviderCatalog. - * Use `create(ProviderCatalogSchema)` to create a new message. + * Describes the message registry.v1.ProviderRegistry. + * Use `create(ProviderRegistrySchema)` to create a new message. */ -export const ProviderCatalogSchema: GenMessage = /*@__PURE__*/ messageDesc(file_v1_provider, 17) +export const ProviderRegistrySchema: GenMessage = /*@__PURE__*/ messageDesc(file_v1_provider, 17) /** - * @generated from enum catalog.v1.ResponsesSummaryMode + * @generated from enum registry.v1.ResponsesSummaryMode */ export enum ResponsesSummaryMode { /** @@ -607,12 +607,12 @@ export enum ResponsesSummaryMode { } /** - * Describes the enum catalog.v1.ResponsesSummaryMode. + * Describes the enum registry.v1.ResponsesSummaryMode. */ export const ResponsesSummaryModeSchema: GenEnum = /*@__PURE__*/ enumDesc(file_v1_provider, 0) /** - * @generated from enum catalog.v1.AnthropicThinkingType + * @generated from enum registry.v1.AnthropicThinkingType */ export enum AnthropicThinkingType { /** @@ -637,12 +637,12 @@ export enum AnthropicThinkingType { } /** - * Describes the enum catalog.v1.AnthropicThinkingType. + * Describes the enum registry.v1.AnthropicThinkingType. */ export const AnthropicThinkingTypeSchema: GenEnum = /*@__PURE__*/ enumDesc(file_v1_provider, 1) /** - * @generated from enum catalog.v1.GeminiThinkingLevel + * @generated from enum registry.v1.GeminiThinkingLevel */ export enum GeminiThinkingLevel { /** @@ -672,12 +672,12 @@ export enum GeminiThinkingLevel { } /** - * Describes the enum catalog.v1.GeminiThinkingLevel. + * Describes the enum registry.v1.GeminiThinkingLevel. */ export const GeminiThinkingLevelSchema: GenEnum = /*@__PURE__*/ enumDesc(file_v1_provider, 2) /** - * @generated from enum catalog.v1.ThinkingType + * @generated from enum registry.v1.ThinkingType */ export enum ThinkingType { /** @@ -702,6 +702,6 @@ export enum ThinkingType { } /** - * Describes the enum catalog.v1.ThinkingType. + * Describes the enum registry.v1.ThinkingType. */ export const ThinkingTypeSchema: GenEnum = /*@__PURE__*/ enumDesc(file_v1_provider, 3) diff --git a/packages/provider-catalog/src/index.ts b/packages/provider-registry/src/index.ts similarity index 65% rename from packages/provider-catalog/src/index.ts rename to packages/provider-registry/src/index.ts index 93d4fc47693..204a38349e1 100644 --- a/packages/provider-catalog/src/index.ts +++ b/packages/provider-registry/src/index.ts @@ -1,6 +1,6 @@ /** - * Cherry Studio Catalog - * Main entry point for the model and provider catalog system + * Cherry Studio Registry + * Main entry point for the model and provider registry system */ // Proto enums (re-exported from schemas/enums.ts which re-exports from gen/) @@ -18,7 +18,7 @@ export { } from './schemas/enums' // Proto types (source of truth) -export type { ModelCatalog, ModelConfig, ModelConfig as ProtoModelConfig } from './gen/v1/model_pb' +export type { ModelConfig, ModelRegistry, ModelConfig as ProtoModelConfig } from './gen/v1/model_pb' export type { ModelPricing, ModelPricing as ProtoModelPricing, @@ -27,19 +27,19 @@ export type { } from './gen/v1/model_pb' export type { ProviderModelOverride as ProtoProviderModelOverride, - ProviderModelCatalog, - ProviderModelOverride + ProviderModelOverride, + ProviderModelRegistry } from './gen/v1/provider_models_pb' export type { ProviderConfig as ProtoProviderConfig, ProviderReasoningFormat as ProtoProviderReasoningFormat, - ProviderCatalog, ProviderConfig, - ProviderReasoningFormat + ProviderReasoningFormat, + ProviderRegistry } from './gen/v1/provider_pb' -// Catalog reader (read .pb files and return proto Message types) -export { readModelCatalog, readProviderCatalog, readProviderModelCatalog } from './catalog-reader' +// Registry reader (read .pb files and return proto Message types) +export { readModelRegistry, readProviderModelRegistry, readProviderRegistry } from './registry-reader' // Model ID normalization utilities export { normalizeModelId } from './utils/importers/base/base-transformer' diff --git a/packages/provider-registry/src/registry-reader.ts b/packages/provider-registry/src/registry-reader.ts new file mode 100644 index 00000000000..225bf116b3c --- /dev/null +++ b/packages/provider-registry/src/registry-reader.ts @@ -0,0 +1,35 @@ +/** + * Read-only registry reader for .pb files. + * + * Reads protobuf registry data and returns proto Message types directly. + * No JSON conversion — proto types are the single source of truth. + */ + +import { readFileSync } from 'node:fs' + +import { fromBinary } from '@bufbuild/protobuf' + +import type { ModelConfig } from './gen/v1/model_pb' +import { ModelRegistrySchema } from './gen/v1/model_pb' +import type { ProviderModelOverride } from './gen/v1/provider_models_pb' +import { ProviderModelRegistrySchema } from './gen/v1/provider_models_pb' +import type { ProviderConfig } from './gen/v1/provider_pb' +import { ProviderRegistrySchema } from './gen/v1/provider_pb' + +export function readModelRegistry(pbPath: string): { version: string; models: ModelConfig[] } { + const bytes = readFileSync(pbPath) + const registry = fromBinary(ModelRegistrySchema, new Uint8Array(bytes)) + return { version: registry.version, models: [...registry.models] } +} + +export function readProviderRegistry(pbPath: string): { version: string; providers: ProviderConfig[] } { + const bytes = readFileSync(pbPath) + const registry = fromBinary(ProviderRegistrySchema, new Uint8Array(bytes)) + return { version: registry.version, providers: [...registry.providers] } +} + +export function readProviderModelRegistry(pbPath: string): { version: string; overrides: ProviderModelOverride[] } { + const bytes = readFileSync(pbPath) + const registry = fromBinary(ProviderModelRegistrySchema, new Uint8Array(bytes)) + return { version: registry.version, overrides: [...registry.overrides] } +} diff --git a/packages/provider-catalog/src/schemas/common.ts b/packages/provider-registry/src/schemas/common.ts similarity index 97% rename from packages/provider-catalog/src/schemas/common.ts rename to packages/provider-registry/src/schemas/common.ts index 612134a5c0f..9cd205b804c 100644 --- a/packages/provider-catalog/src/schemas/common.ts +++ b/packages/provider-registry/src/schemas/common.ts @@ -1,5 +1,5 @@ /** - * Common type definitions for the catalog system + * Common type definitions for the registry system * Shared across model, provider, and override schemas */ diff --git a/packages/provider-catalog/src/schemas/enums.ts b/packages/provider-registry/src/schemas/enums.ts similarity index 96% rename from packages/provider-catalog/src/schemas/enums.ts rename to packages/provider-registry/src/schemas/enums.ts index b2979090b1b..734f67aafc8 100644 --- a/packages/provider-catalog/src/schemas/enums.ts +++ b/packages/provider-registry/src/schemas/enums.ts @@ -1,10 +1,10 @@ /** - * Canonical enum definitions for the catalog system. + * Canonical enum definitions for the registry system. * * Re-exports proto-generated enums as the SINGLE SOURCE OF TRUTH. * Proto numeric enums are used everywhere — no string conversion. * - * - catalog/schemas/ uses these via z.nativeEnum() + * - registry/schemas/ uses these via z.nativeEnum() * - shared/data/types/ re-exports these directly */ diff --git a/packages/provider-catalog/src/schemas/index.ts b/packages/provider-registry/src/schemas/index.ts similarity index 88% rename from packages/provider-catalog/src/schemas/index.ts rename to packages/provider-registry/src/schemas/index.ts index 4d3748046ec..cbee2e2a335 100644 --- a/packages/provider-catalog/src/schemas/index.ts +++ b/packages/provider-registry/src/schemas/index.ts @@ -1,5 +1,5 @@ /** - * Unified export of all catalog schemas and types + * Unified export of all registry schemas and types * This file provides a single entry point for all schema definitions */ diff --git a/packages/provider-catalog/src/schemas/model.ts b/packages/provider-registry/src/schemas/model.ts similarity index 100% rename from packages/provider-catalog/src/schemas/model.ts rename to packages/provider-registry/src/schemas/model.ts diff --git a/packages/provider-catalog/src/schemas/provider-models.ts b/packages/provider-registry/src/schemas/provider-models.ts similarity index 100% rename from packages/provider-catalog/src/schemas/provider-models.ts rename to packages/provider-registry/src/schemas/provider-models.ts diff --git a/packages/provider-catalog/src/schemas/provider.ts b/packages/provider-registry/src/schemas/provider.ts similarity index 96% rename from packages/provider-catalog/src/schemas/provider.ts rename to packages/provider-registry/src/schemas/provider.ts index c5fee4dfc88..a0b6845310a 100644 --- a/packages/provider-catalog/src/schemas/provider.ts +++ b/packages/provider-registry/src/schemas/provider.ts @@ -177,8 +177,8 @@ export const ProviderWebsiteSchema = z.object({ }) }) -/** Per-endpoint-type configuration in catalog */ -export const CatalogEndpointConfigSchema = z.object({ +/** Per-endpoint-type configuration in registry */ +export const RegistryEndpointConfigSchema = z.object({ /** Base URL for this endpoint type's API */ baseUrl: z.url().optional(), /** URLs for fetching available models via this endpoint type */ @@ -205,7 +205,7 @@ export const ProviderConfigSchema = z /** Provider description */ description: z.string().optional(), /** Per-endpoint-type configuration */ - endpointConfigs: z.record(EndpointTypeSchema, CatalogEndpointConfigSchema).optional(), + endpointConfigs: z.record(EndpointTypeSchema, RegistryEndpointConfigSchema).optional(), /** Default endpoint type for chat requests (must exist in endpointConfigs) */ defaultChatEndpoint: EndpointTypeSchema.optional(), /** API feature flags controlling request construction */ @@ -233,6 +233,6 @@ export const ProviderListSchema = z.object({ export { ENDPOINT_TYPE } from './enums' export type ApiFeatures = z.infer export type ProviderReasoningFormat = z.infer -export type CatalogEndpointConfig = z.infer +export type RegistryEndpointConfig = z.infer export type ProviderConfig = z.infer export type ProviderList = z.infer diff --git a/packages/provider-catalog/src/utils/importers/base/base-transformer.ts b/packages/provider-registry/src/utils/importers/base/base-transformer.ts similarity index 99% rename from packages/provider-catalog/src/utils/importers/base/base-transformer.ts rename to packages/provider-registry/src/utils/importers/base/base-transformer.ts index e78ed47c7e7..f906f421f98 100644 --- a/packages/provider-catalog/src/utils/importers/base/base-transformer.ts +++ b/packages/provider-registry/src/utils/importers/base/base-transformer.ts @@ -734,7 +734,7 @@ export class OpenAICompatibleTransformer implements ITransformer { } /** - * Abstract base class for catalog transformers + * Abstract base class for registry transformers * Provides common functionality for normalizing model IDs, inferring publishers, etc. */ export abstract class BaseCatalogTransformer implements ITransformer { diff --git a/packages/shared/data/api/schemas/providers.ts b/packages/shared/data/api/schemas/providers.ts index 33662e8f0f8..4ed955d8b1b 100644 --- a/packages/shared/data/api/schemas/providers.ts +++ b/packages/shared/data/api/schemas/providers.ts @@ -134,10 +134,10 @@ export interface ProviderSchemas { } /** - * Get all catalog preset models for a provider (read-only, no DB writes) - * @example GET /providers/openai/catalog-models + * Get all registry preset models for a provider (read-only, no DB writes) + * @example GET /providers/openai/registry-models */ - '/providers/:providerId/catalog-models': { + '/providers/:providerId/registry-models': { GET: { params: { providerId: string } response: Model[] diff --git a/packages/shared/data/types/model.ts b/packages/shared/data/types/model.ts index 8525d291259..d961cbe3c5a 100644 --- a/packages/shared/data/types/model.ts +++ b/packages/shared/data/types/model.ts @@ -19,14 +19,14 @@ import { MODEL_CAPABILITY, ModelCapability, ReasoningEffort -} from '@cherrystudio/provider-catalog' +} from '@cherrystudio/provider-registry' import * as z from 'zod' // Re-export const objects and types for consumers export { Currency, ENDPOINT_TYPE, EndpointType, MODALITY, Modality, MODEL_CAPABILITY, ModelCapability, ReasoningEffort } // ═══════════════════════════════════════════════════════════════════════════════ -// Zod schemas (formerly in provider-catalog/schemas, now owned by shared) +// Zod schemas (formerly in provider-registry/schemas, now owned by shared) // ═══════════════════════════════════════════════════════════════════════════════ /** Price per token schema */ diff --git a/packages/shared/data/types/provider.ts b/packages/shared/data/types/provider.ts index f58249a01f6..38cf5d85bfc 100644 --- a/packages/shared/data/types/provider.ts +++ b/packages/shared/data/types/provider.ts @@ -11,10 +11,10 @@ * Zod schemas are the single source of truth — all types derived via z.infer<> */ -import { EndpointType } from '@cherrystudio/provider-catalog' +import { EndpointType } from '@cherrystudio/provider-registry' import * as z from 'zod' -// ─── Schemas formerly from provider-catalog/schemas ───────────────────────── +// ─── Schemas formerly from provider-registry/schemas ───────────────────────── const EndpointTypeSchema = z.enum(EndpointType) diff --git a/packages/shared/data/utils/modelMerger.ts b/packages/shared/data/utils/modelMerger.ts index 3d3527a000a..3be1c7e55a1 100644 --- a/packages/shared/data/utils/modelMerger.ts +++ b/packages/shared/data/utils/modelMerger.ts @@ -11,9 +11,9 @@ import type { ProtoProviderModelOverride, ProtoProviderReasoningFormat, ProtoReasoningSupport -} from '@cherrystudio/provider-catalog' -import type { Modality, ModelCapability, ReasoningEffort as ReasoningEffortType } from '@cherrystudio/provider-catalog' -import { EndpointType, ReasoningEffort } from '@cherrystudio/provider-catalog' +} from '@cherrystudio/provider-registry' +import type { Modality, ModelCapability, ReasoningEffort as ReasoningEffortType } from '@cherrystudio/provider-registry' +import { EndpointType, ReasoningEffort } from '@cherrystudio/provider-registry' import * as z from 'zod' import type { Model, RuntimeModelPricing, RuntimeReasoning } from '../types/model' diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 92fc5fbfebb..a0a2f31629d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -309,9 +309,9 @@ importers: '@cherrystudio/openai': specifier: 6.15.0 version: 6.15.0(ws@8.20.0)(zod@4.3.4) - '@cherrystudio/provider-catalog': + '@cherrystudio/provider-registry': specifier: workspace:* - version: link:packages/provider-catalog + version: link:packages/provider-registry '@cherrystudio/ui': specifier: workspace:* version: link:packages/ui @@ -1417,7 +1417,7 @@ importers: specifier: ^0.20.3 version: 0.20.3(@typescript/native-preview@7.0.0-dev.20260204.1)(typescript@5.9.3) - packages/provider-catalog: + packages/provider-registry: dependencies: '@bufbuild/protobuf': specifier: ^2.11.0 @@ -1455,7 +1455,7 @@ importers: version: 5.9.3 vitest: specifier: ^4.0.13 - version: 4.1.2(@opentelemetry/api@1.9.0)(@types/node@24.10.4)(@vitest/ui@3.2.4(vitest@3.2.4))(jsdom@26.1.0)(msw@2.12.7(@types/node@24.10.4)(typescript@5.9.3))(rolldown-vite@7.3.0(@types/node@24.10.4)(esbuild@0.25.12)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2)) + version: 4.1.2(@opentelemetry/api@1.9.0)(@types/node@24.10.4)(@vitest/ui@3.2.4(vitest@3.2.4))(jsdom@26.1.0)(msw@2.12.7(@types/node@24.10.4)(typescript@5.9.3))(vite@8.0.5(@types/node@24.10.4)(esbuild@0.25.12)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2)) zod: specifier: ^4.1.12 version: 4.3.6 @@ -1555,7 +1555,7 @@ importers: version: 0.0.5 '@storybook/addon-docs': specifier: ^10.0.5 - version: 10.3.4(@types/react@19.2.7)(esbuild@0.25.12)(rolldown-vite@7.3.0(@types/node@24.10.4)(esbuild@0.25.12)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2))(rollup@4.55.1)(storybook@10.3.4(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)) + version: 10.3.4(@types/react@19.2.7)(esbuild@0.25.12)(rollup@4.55.1)(storybook@10.3.4(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(vite@8.0.5(@types/node@24.10.4)(esbuild@0.25.12)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2)) '@storybook/addon-themes': specifier: ^10.0.5 version: 10.3.4(storybook@10.3.4(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)) @@ -1564,7 +1564,7 @@ importers: version: 10.3.4(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@10.3.4(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(typescript@5.8.3) '@storybook/react-vite': specifier: ^10.0.5 - version: 10.3.4(esbuild@0.25.12)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rolldown-vite@7.3.0(@types/node@24.10.4)(esbuild@0.25.12)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2))(rollup@4.55.1)(storybook@10.3.4(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(typescript@5.8.3) + version: 10.3.4(esbuild@0.25.12)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rollup@4.55.1)(storybook@10.3.4(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(typescript@5.8.3)(vite@8.0.5(@types/node@24.10.4)(esbuild@0.25.12)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2)) '@svgr/core': specifier: ^8.1.0 version: 8.1.0(typescript@5.8.3) @@ -2164,28 +2164,24 @@ packages: engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] - libc: [musl] '@biomejs/cli-linux-arm64@2.2.4': resolution: {integrity: sha512-M/Iz48p4NAzMXOuH+tsn5BvG/Jb07KOMTdSVwJpicmhN309BeEyRyQX+n1XDF0JVSlu28+hiTQ2L4rZPvu7nMw==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] - libc: [glibc] '@biomejs/cli-linux-x64-musl@2.2.4': resolution: {integrity: sha512-m41nFDS0ksXK2gwXL6W6yZTYPMH0LughqbsxInSKetoH6morVj43szqKx79Iudkp8WRT5SxSh7qVb8KCUiewGg==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] - libc: [musl] '@biomejs/cli-linux-x64@2.2.4': resolution: {integrity: sha512-orr3nnf2Dpb2ssl6aihQtvcKtLySLta4E2UcXdp7+RTa7mfJjBgIsbS0B9GC8gVu0hjOu021aU8b3/I1tn+pVQ==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] - libc: [glibc] '@biomejs/cli-win32-arm64@2.2.4': resolution: {integrity: sha512-NXnfTeKHDFUWfxAefa57DiGmu9VyKi0cDqFpdI+1hJWQjGJhJutHPX0b5m+eXvTKOaf+brU+P0JrQAZMb5yYaQ==} @@ -3604,92 +3600,78 @@ packages: resolution: {integrity: sha512-RXwd0CgG+uPRX5YYrkzKyalt2OJYRiJQ8ED/fi1tq9WQW2jsQIn0tqrlR5l5dr/rjqq6AHAxURhj2DVjyQWSOA==} cpu: [arm64] os: [linux] - libc: [glibc] '@img/sharp-libvips-linux-arm@1.2.0': resolution: {integrity: sha512-mWd2uWvDtL/nvIzThLq3fr2nnGfyr/XMXlq8ZJ9WMR6PXijHlC3ksp0IpuhK6bougvQrchUAfzRLnbsen0Cqvw==} cpu: [arm] os: [linux] - libc: [glibc] '@img/sharp-libvips-linux-ppc64@1.2.0': resolution: {integrity: sha512-Xod/7KaDDHkYu2phxxfeEPXfVXFKx70EAFZ0qyUdOjCcxbjqyJOEUpDe6RIyaunGxT34Anf9ue/wuWOqBW2WcQ==} cpu: [ppc64] os: [linux] - libc: [glibc] '@img/sharp-libvips-linux-s390x@1.2.0': resolution: {integrity: sha512-eMKfzDxLGT8mnmPJTNMcjfO33fLiTDsrMlUVcp6b96ETbnJmd4uvZxVJSKPQfS+odwfVaGifhsB07J1LynFehw==} cpu: [s390x] os: [linux] - libc: [glibc] '@img/sharp-libvips-linux-x64@1.2.0': resolution: {integrity: sha512-ZW3FPWIc7K1sH9E3nxIGB3y3dZkpJlMnkk7z5tu1nSkBoCgw2nSRTFHI5pB/3CQaJM0pdzMF3paf9ckKMSE9Tg==} cpu: [x64] os: [linux] - libc: [glibc] '@img/sharp-libvips-linuxmusl-arm64@1.2.0': resolution: {integrity: sha512-UG+LqQJbf5VJ8NWJ5Z3tdIe/HXjuIdo4JeVNADXBFuG7z9zjoegpzzGIyV5zQKi4zaJjnAd2+g2nna8TZvuW9Q==} cpu: [arm64] os: [linux] - libc: [musl] '@img/sharp-libvips-linuxmusl-x64@1.2.0': resolution: {integrity: sha512-SRYOLR7CXPgNze8akZwjoGBoN1ThNZoqpOgfnOxmWsklTGVfJiGJoC/Lod7aNMGA1jSsKWM1+HRX43OP6p9+6Q==} cpu: [x64] os: [linux] - libc: [musl] '@img/sharp-linux-arm64@0.34.3': resolution: {integrity: sha512-QdrKe3EvQrqwkDrtuTIjI0bu6YEJHTgEeqdzI3uWJOH6G1O8Nl1iEeVYRGdj1h5I21CqxSvQp1Yv7xeU3ZewbA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] - libc: [glibc] '@img/sharp-linux-arm@0.34.3': resolution: {integrity: sha512-oBK9l+h6KBN0i3dC8rYntLiVfW8D8wH+NPNT3O/WBHeW0OQWCjfWksLUaPidsrDKpJgXp3G3/hkmhptAW0I3+A==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm] os: [linux] - libc: [glibc] '@img/sharp-linux-ppc64@0.34.3': resolution: {integrity: sha512-GLtbLQMCNC5nxuImPR2+RgrviwKwVql28FWZIW1zWruy6zLgA5/x2ZXk3mxj58X/tszVF69KK0Is83V8YgWhLA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [ppc64] os: [linux] - libc: [glibc] '@img/sharp-linux-s390x@0.34.3': resolution: {integrity: sha512-3gahT+A6c4cdc2edhsLHmIOXMb17ltffJlxR0aC2VPZfwKoTGZec6u5GrFgdR7ciJSsHT27BD3TIuGcuRT0KmQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [s390x] os: [linux] - libc: [glibc] '@img/sharp-linux-x64@0.34.3': resolution: {integrity: sha512-8kYso8d806ypnSq3/Ly0QEw90V5ZoHh10yH0HnrzOCr6DKAPI6QVHvwleqMkVQ0m+fc7EH8ah0BB0QPuWY6zJQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] - libc: [glibc] '@img/sharp-linuxmusl-arm64@0.34.3': resolution: {integrity: sha512-vAjbHDlr4izEiXM1OTggpCcPg9tn4YriK5vAjowJsHwdBIdx0fYRsURkxLG2RLm9gyBq66gwtWI8Gx0/ov+JKQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] - libc: [musl] '@img/sharp-linuxmusl-x64@0.34.3': resolution: {integrity: sha512-gCWUn9547K5bwvOn9l5XGAEjVTTRji4aPTqLzGXHvIr6bIDZKNTA34seMPgM0WmSf+RYBH411VavCejp3PkOeQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] - libc: [musl] '@img/sharp-wasm32@0.34.3': resolution: {integrity: sha512-+CyRcpagHMGteySaWos8IbnXcHgfDn7pO2fiC2slJxvNq9gDipYBN42/RagzctVRKgxATmfqOSulgZv5e1RdMg==} @@ -4454,35 +4436,30 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [glibc] '@napi-rs/canvas-linux-arm64-musl@0.1.97': resolution: {integrity: sha512-kKmSkQVnWeqg7qdsiXvYxKhAFuHz3tkBjW/zyQv5YKUPhotpaVhpBGv5LqCngzyuRV85SXoe+OFj+Tv0a0QXkQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [musl] '@napi-rs/canvas-linux-riscv64-gnu@0.1.97': resolution: {integrity: sha512-Jc7I3A51jnEOIAXeLsN/M/+Z28LUeakcsXs07FLq9prXc0eYOtVwsDEv913Gr+06IRo34gJJVgT0TXvmz+N2VA==} engines: {node: '>= 10'} cpu: [riscv64] os: [linux] - libc: [glibc] '@napi-rs/canvas-linux-x64-gnu@0.1.97': resolution: {integrity: sha512-iDUBe7AilfuBSRbSa8/IGX38Mf+iCSBqoVKLSQ5XaY2JLOaqz1TVyPFEyIck7wT6mRQhQt5sN6ogfjIDfi74tg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [glibc] '@napi-rs/canvas-linux-x64-musl@0.1.97': resolution: {integrity: sha512-AKLFd/v0Z5fvgqBDqhvqtAdx+fHMJ5t9JcUNKq4FIZ5WH+iegGm8HPdj00NFlCSnm83Fp3Ln8I2f7uq1aIiWaA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [musl] '@napi-rs/canvas-win32-arm64-msvc@0.1.97': resolution: {integrity: sha512-u883Yr6A6fO7Vpsy9YE4FVCIxzzo5sO+7pIUjjoDLjS3vQaNMkVzx5bdIpEL+ob+gU88WDK4VcxYMZ6nmnoX9A==} @@ -4575,28 +4552,24 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [glibc] '@neplex/vectorizer-linux-arm64-musl@0.0.5': resolution: {integrity: sha512-r2a85bAkgwSxAbQTSHnzXaDZCyABgVTYf6f0OSh1oGHHIc9pC97VUZbmQLtGFeIQLQR9j4nKjF1MlOHmnV4EDA==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [musl] '@neplex/vectorizer-linux-x64-gnu@0.0.5': resolution: {integrity: sha512-8pdPe27RNXHwkvYiK3vj5b3/Yi8rWgJzUsBdT/Jm2bjk5c32wiV454yT0fLZQjRB1DCAK2DvyHjf6eZ0R9HaJg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [glibc] '@neplex/vectorizer-linux-x64-musl@0.0.5': resolution: {integrity: sha512-VP/DHuX40I/9KzSFRctxksXzJBGwbPE/E30NCAcPA1mS6iApovWsZe3la5dA9A5kStaKh9wTJcZuVEGL8tGIMg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [musl] '@neplex/vectorizer-win32-arm64-msvc@0.0.5': resolution: {integrity: sha512-VfQRITnqvjABiIcnx5b/9XjyktTbpDHzY2nVt5wplOqGM88f6fPn2JYiia7IEdv2BA/1+oN/Bcw75eq12mW8Ug==} @@ -4780,6 +4753,9 @@ packages: '@oxc-project/types@0.112.0': resolution: {integrity: sha512-m6RebKHIRsax2iCwVpYW2ErQwa4ywHJrE4sCK3/8JK8ZZAWOKXaRJFl/uP51gaVyyXlaS4+chU1nSCdzYf6QqQ==} + '@oxc-project/types@0.122.0': + resolution: {integrity: sha512-oLAl5kBpV4w69UtFZ9xqcmTi+GENWOcPF7FCrczTiBbmC0ibXxCwyvZGbO39rCVEuLGAZM84DH0pUIyyv/YJzA==} + '@oxc-project/types@0.95.0': resolution: {integrity: sha512-vACy7vhpMPhjEJhULNxrdR0D943TkA/MigMpJCHmBHvMXxRStRi/dPtTlfQ3uDwWSzRpT8z+7ImjZVf8JWBocQ==} @@ -4863,56 +4839,48 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [glibc] '@oxlint/binding-linux-arm64-musl@1.56.0': resolution: {integrity: sha512-rkTZkBfJ4TYLjansjSzL6mgZOdN5IvUnSq3oNJSLwBcNvy3dlgQtpHPrRxrCEbbcp7oQ6If0tkNaqfOsphYZ9g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [musl] '@oxlint/binding-linux-ppc64-gnu@1.56.0': resolution: {integrity: sha512-uqL1kMH3u69/e1CH2EJhP3CP28jw2ExLsku4o8RVAZ7fySo9zOyI2fy9pVlTAp4voBLVgzndXi3SgtdyCTa2aA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] - libc: [glibc] '@oxlint/binding-linux-riscv64-gnu@1.56.0': resolution: {integrity: sha512-j0CcMBOgV6KsRaBdsebIeiy7hCjEvq2KdEsiULf2LZqAq0v1M1lWjelhCV57LxsqaIGChXFuFJ0RiFrSRHPhSg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] - libc: [glibc] '@oxlint/binding-linux-riscv64-musl@1.56.0': resolution: {integrity: sha512-7VDOiL8cDG3DQ/CY3yKjbV1c4YPvc4vH8qW09Vv+5ukq3l/Kcyr6XGCd5NvxUmxqDb2vjMpM+eW/4JrEEsUetA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] - libc: [musl] '@oxlint/binding-linux-s390x-gnu@1.56.0': resolution: {integrity: sha512-JGRpX0M+ikD3WpwJ7vKcHKV6Kg0dT52BW2Eu2BupXotYeqGXBrbY+QPkAyKO6MNgKozyTNaRh3r7g+VWgyAQYQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] - libc: [glibc] '@oxlint/binding-linux-x64-gnu@1.56.0': resolution: {integrity: sha512-dNaICPvtmuxFP/VbqdofrLqdS3bM/AKJN3LMJD52si44ea7Be1cBk6NpfIahaysG9Uo+L98QKddU9CD5L8UHnQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - libc: [glibc] '@oxlint/binding-linux-x64-musl@1.56.0': resolution: {integrity: sha512-pF1vOtM+GuXmbklM1hV8WMsn6tCNPvkUzklj/Ej98JhlanbmA2RB1BILgOpwSuCTRTIYx2MXssmEyQQ90QF5aA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - libc: [musl] '@oxlint/binding-openharmony-arm64@1.56.0': resolution: {integrity: sha512-bp8NQ4RE6fDIFLa4bdBiOA+TAvkNkg+rslR+AvvjlLTYXLy9/uKAYLQudaQouWihLD/hgkrXIKKzXi5IXOewwg==} @@ -6080,6 +6048,12 @@ packages: cpu: [arm64] os: [android] + '@rolldown/binding-android-arm64@1.0.0-rc.12': + resolution: {integrity: sha512-pv1y2Fv0JybcykuiiD3qBOBdz6RteYojRFY1d+b95WVuzx211CRh+ytI/+9iVyWQ6koTh5dawe4S/yRfOFjgaA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + '@rolldown/binding-android-arm64@1.0.0-rc.3': resolution: {integrity: sha512-0T1k9FinuBZ/t7rZ8jN6OpUKPnUjNdYHoj/cESWrQ3ZraAJ4OMm6z7QjSfCxqj8mOp9kTKc1zHK3kGz5vMu+nQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -6104,6 +6078,12 @@ packages: cpu: [arm64] os: [darwin] + '@rolldown/binding-darwin-arm64@1.0.0-rc.12': + resolution: {integrity: sha512-cFYr6zTG/3PXXF3pUO+umXxt1wkRK/0AYT8lDwuqvRC+LuKYWSAQAQZjCWDQpAH172ZV6ieYrNnFzVVcnSflAg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + '@rolldown/binding-darwin-arm64@1.0.0-rc.3': resolution: {integrity: sha512-JWWLzvcmc/3pe7qdJqPpuPk91SoE/N+f3PcWx/6ZwuyDVyungAEJPvKm/eEldiDdwTmaEzWfIR+HORxYWrCi1A==} engines: {node: ^20.19.0 || >=22.12.0} @@ -6128,6 +6108,12 @@ packages: cpu: [x64] os: [darwin] + '@rolldown/binding-darwin-x64@1.0.0-rc.12': + resolution: {integrity: sha512-ZCsYknnHzeXYps0lGBz8JrF37GpE9bFVefrlmDrAQhOEi4IOIlcoU1+FwHEtyXGx2VkYAvhu7dyBf75EJQffBw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + '@rolldown/binding-darwin-x64@1.0.0-rc.3': resolution: {integrity: sha512-MTakBxfx3tde5WSmbHxuqlDsIW0EzQym+PJYGF4P6lG2NmKzi128OGynoFUqoD5ryCySEY85dug4v+LWGBElIw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -6152,6 +6138,12 @@ packages: cpu: [x64] os: [freebsd] + '@rolldown/binding-freebsd-x64@1.0.0-rc.12': + resolution: {integrity: sha512-dMLeprcVsyJsKolRXyoTH3NL6qtsT0Y2xeuEA8WQJquWFXkEC4bcu1rLZZSnZRMtAqwtrF/Ib9Ddtpa/Gkge9Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + '@rolldown/binding-freebsd-x64@1.0.0-rc.3': resolution: {integrity: sha512-jje3oopyOLs7IwfvXoS6Lxnmie5JJO7vW29fdGFu5YGY1EDbVDhD+P9vDihqS5X6fFiqL3ZQZCMBg6jyHkSVww==} engines: {node: ^20.19.0 || >=22.12.0} @@ -6176,6 +6168,12 @@ packages: cpu: [arm] os: [linux] + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.12': + resolution: {integrity: sha512-YqWjAgGC/9M1lz3GR1r1rP79nMgo3mQiiA+Hfo+pvKFK1fAJ1bCi0ZQVh8noOqNacuY1qIcfyVfP6HoyBRZ85Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.3': resolution: {integrity: sha512-A0n8P3hdLAaqzSFrQoA42p23ZKBYQOw+8EH5r15Sa9X1kD9/JXe0YT2gph2QTWvdr0CVK2BOXiK6ENfy6DXOag==} engines: {node: ^20.19.0 || >=22.12.0} @@ -6187,112 +6185,132 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [glibc] '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.52': resolution: {integrity: sha512-V48oDR84feRU2KRuzpALp594Uqlx27+zFsT6+BgTcXOtu7dWy350J1G28ydoCwKB+oxwsRPx2e7aeQnmd3YJbQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [glibc] '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.53': resolution: {integrity: sha512-bpIGX+ov9PhJYV+wHNXl9rzq4F0QvILiURn0y0oepbQx+7stmQsKA0DhPGwmhfvF856wq+gbM8L92SAa/CBcLg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [glibc] + + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.12': + resolution: {integrity: sha512-/I5AS4cIroLpslsmzXfwbe5OmWvSsrFuEw3mwvbQ1kDxJ822hFHIx+vsN/TAzNVyepI/j/GSzrtCIwQPeKCLIg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.3': resolution: {integrity: sha512-kWXkoxxarYISBJ4bLNf5vFkEbb4JvccOwxWDxuK9yee8lg5XA7OpvlTptfRuwEvYcOZf+7VS69Uenpmpyo5Bjw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [glibc] '@rolldown/binding-linux-arm64-musl@1.0.0-beta.45': resolution: {integrity: sha512-tdy8ThO/fPp40B81v0YK3QC+KODOmzJzSUOO37DinQxzlTJ026gqUSOM8tzlVixRbQJltgVDCTYF8HNPRErQTA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [musl] '@rolldown/binding-linux-arm64-musl@1.0.0-beta.52': resolution: {integrity: sha512-ENLmSQCWqSA/+YN45V2FqTIemg7QspaiTjlm327eUAMeOLdqmSOVVyrQexJGNTQ5M8sDYCgVAig2Kk01Ggmqaw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [musl] '@rolldown/binding-linux-arm64-musl@1.0.0-beta.53': resolution: {integrity: sha512-bGe5EBB8FVjHBR1mOLOPEFg1Lp3//7geqWkU5NIhxe+yH0W8FVrQ6WRYOap4SUTKdklD/dC4qPLREkMMQ855FA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [musl] + + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.12': + resolution: {integrity: sha512-V6/wZztnBqlx5hJQqNWwFdxIKN0m38p8Jas+VoSfgH54HSj9tKTt1dZvG6JRHcjh6D7TvrJPWFGaY9UBVOaWPw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] '@rolldown/binding-linux-arm64-musl@1.0.0-rc.3': resolution: {integrity: sha512-Z03/wrqau9Bicfgb3Dbs6SYTHliELk2PM2LpG2nFd+cGupTMF5kanLEcj2vuuJLLhptNyS61rtk7SOZ+lPsTUA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [musl] + + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.12': + resolution: {integrity: sha512-AP3E9BpcUYliZCxa3w5Kwj9OtEVDYK6sVoUzy4vTOJsjPOgdaJZKFmN4oOlX0Wp0RPV2ETfmIra9x1xuayFB7g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + + '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.12': + resolution: {integrity: sha512-nWwpvUSPkoFmZo0kQazZYOrT7J5DGOJ/+QHHzjvNlooDZED8oH82Yg67HvehPPLAg5fUff7TfWFHQS8IV1n3og==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] '@rolldown/binding-linux-x64-gnu@1.0.0-beta.45': resolution: {integrity: sha512-lS082ROBWdmOyVY/0YB3JmsiClaWoxvC+dA8/rbhyB9VLkvVEaihLEOr4CYmrMse151C4+S6hCw6oa1iewox7g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - libc: [glibc] '@rolldown/binding-linux-x64-gnu@1.0.0-beta.52': resolution: {integrity: sha512-klahlb2EIFltSUubn/VLjuc3qxp1E7th8ukayPfdkcKvvYcQ5rJztgx8JsJSuAKVzKtNTqUGOhy4On71BuyV8g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - libc: [glibc] '@rolldown/binding-linux-x64-gnu@1.0.0-beta.53': resolution: {integrity: sha512-qL+63WKVQs1CMvFedlPt0U9PiEKJOAL/bsHMKUDS6Vp2Q+YAv/QLPu8rcvkfIMvQ0FPU2WL0aX4eWwF6e/GAnA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - libc: [glibc] + + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.12': + resolution: {integrity: sha512-RNrafz5bcwRy+O9e6P8Z/OCAJW/A+qtBczIqVYwTs14pf4iV1/+eKEjdOUta93q2TsT/FI0XYDP3TCky38LMAg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] '@rolldown/binding-linux-x64-gnu@1.0.0-rc.3': resolution: {integrity: sha512-iSXXZsQp08CSilff/DCTFZHSVEpEwdicV3W8idHyrByrcsRDVh9sGC3sev6d8BygSGj3vt8GvUKBPCoyMA4tgQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - libc: [glibc] '@rolldown/binding-linux-x64-musl@1.0.0-beta.45': resolution: {integrity: sha512-Hi73aYY0cBkr1/SvNQqH8Cd+rSV6S9RB5izCv0ySBcRnd/Wfn5plguUoGYwBnhHgFbh6cPw9m2dUVBR6BG1gxA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - libc: [musl] '@rolldown/binding-linux-x64-musl@1.0.0-beta.52': resolution: {integrity: sha512-UuA+JqQIgqtkgGN2c/AQ5wi8M6mJHrahz/wciENPTeI6zEIbbLGoth5XN+sQe2pJDejEVofN9aOAp0kaazwnVg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - libc: [musl] '@rolldown/binding-linux-x64-musl@1.0.0-beta.53': resolution: {integrity: sha512-VGl9JIGjoJh3H8Mb+7xnVqODajBmrdOOb9lxWXdcmxyI+zjB2sux69br0hZJDTyLJfvBoYm439zPACYbCjGRmw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - libc: [musl] + + '@rolldown/binding-linux-x64-musl@1.0.0-rc.12': + resolution: {integrity: sha512-Jpw/0iwoKWx3LJ2rc1yjFrj+T7iHZn2JDg1Yny1ma0luviFS4mhAIcd1LFNxK3EYu3DHWCps0ydXQ5i/rrJ2ig==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] '@rolldown/binding-linux-x64-musl@1.0.0-rc.3': resolution: {integrity: sha512-qaj+MFudtdCv9xZo9znFvkgoajLdc+vwf0Kz5N44g+LU5XMe+IsACgn3UG7uTRlCCvhMAGXm1XlpEA5bZBrOcw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - libc: [musl] '@rolldown/binding-openharmony-arm64@1.0.0-beta.45': resolution: {integrity: sha512-fljEqbO7RHHogNDxYtTzr+GNjlfOx21RUyGmF+NrkebZ8emYYiIqzPxsaMZuRx0rgZmVmliOzEp86/CQFDKhJQ==} @@ -6312,6 +6330,12 @@ packages: cpu: [arm64] os: [openharmony] + '@rolldown/binding-openharmony-arm64@1.0.0-rc.12': + resolution: {integrity: sha512-vRugONE4yMfVn0+7lUKdKvN4D5YusEiPilaoO2sgUWpCvrncvWgPMzK00ZFFJuiPgLwgFNP5eSiUlv2tfc+lpA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + '@rolldown/binding-openharmony-arm64@1.0.0-rc.3': resolution: {integrity: sha512-U662UnMETyjT65gFmG9ma+XziENrs7BBnENi/27swZPYagubfHRirXHG2oMl+pEax2WvO7Kb9gHZmMakpYqBHQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -6333,6 +6357,11 @@ packages: engines: {node: '>=14.0.0'} cpu: [wasm32] + '@rolldown/binding-wasm32-wasi@1.0.0-rc.12': + resolution: {integrity: sha512-ykGiLr/6kkiHc0XnBfmFJuCjr5ZYKKofkx+chJWDjitX+KsJuAmrzWhwyOMSHzPhzOHOy7u9HlFoa5MoAOJ/Zg==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + '@rolldown/binding-wasm32-wasi@1.0.0-rc.3': resolution: {integrity: sha512-gekrQ3Q2HiC1T5njGyuUJoGpK/l6B/TNXKed3fZXNf9YRTJn3L5MOZsFBn4bN2+UX+8+7hgdlTcEsexX988G4g==} engines: {node: '>=14.0.0'} @@ -6356,6 +6385,12 @@ packages: cpu: [arm64] os: [win32] + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.12': + resolution: {integrity: sha512-5eOND4duWkwx1AzCxadcOrNeighiLwMInEADT0YM7xeEOOFcovWZCq8dadXgcRHSf3Ulh1kFo/qvzoFiCLOL1Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.3': resolution: {integrity: sha512-85y5JifyMgs8m5K2XzR/VDsapKbiFiohl7s5lEj7nmNGO0pkTXE7q6TQScei96BNAsoK7JC3pA7ukA8WRHVJpg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -6392,6 +6427,12 @@ packages: cpu: [x64] os: [win32] + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.12': + resolution: {integrity: sha512-PyqoipaswDLAZtot351MLhrlrh6lcZPo2LSYE+VDxbVk24LVKAGOuE4hb8xZQmrPAuEtTZW8E6D2zc5EUZX4Lw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.3': resolution: {integrity: sha512-a4VUQZH7LxGbUJ3qJ/TzQG8HxdHvf+jOnqf7B7oFx1TEBm+j2KNL2zr5SQ7wHkNAcaPevF6gf9tQnVBnC4mD+A==} engines: {node: ^20.19.0 || >=22.12.0} @@ -6410,6 +6451,9 @@ packages: '@rolldown/pluginutils@1.0.0-beta.53': resolution: {integrity: sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==} + '@rolldown/pluginutils@1.0.0-rc.12': + resolution: {integrity: sha512-HHMwmarRKvoFsJorqYlFeFRzXZqCt2ETQlEDOb9aqssrnVBB1/+xgTGtuTrIk5vzLNX1MjMtTf7W9z3tsSbrxw==} + '@rolldown/pluginutils@1.0.0-rc.3': resolution: {integrity: sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==} @@ -6456,85 +6500,71 @@ packages: resolution: {integrity: sha512-Rn3n+FUk2J5VWx+ywrG/HGPTD9jXNbicRtTM11e/uorplArnXZYsVifnPPqNNP5BsO3roI4n8332ukpY/zN7rQ==} cpu: [arm] os: [linux] - libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.55.1': resolution: {integrity: sha512-grPNWydeKtc1aEdrJDWk4opD7nFtQbMmV7769hiAaYyUKCT1faPRm2av8CX1YJsZ4TLAZcg9gTR1KvEzoLjXkg==} cpu: [arm] os: [linux] - libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.55.1': resolution: {integrity: sha512-a59mwd1k6x8tXKcUxSyISiquLwB5pX+fJW9TkWU46lCqD/GRDe9uDN31jrMmVP3feI3mhAdvcCClhV8V5MhJFQ==} cpu: [arm64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.55.1': resolution: {integrity: sha512-puS1MEgWX5GsHSoiAsF0TYrpomdvkaXm0CofIMG5uVkP6IBV+ZO9xhC5YEN49nsgYo1DuuMquF9+7EDBVYu4uA==} cpu: [arm64] os: [linux] - libc: [musl] '@rollup/rollup-linux-loong64-gnu@4.55.1': resolution: {integrity: sha512-r3Wv40in+lTsULSb6nnoudVbARdOwb2u5fpeoOAZjFLznp6tDU8kd+GTHmJoqZ9lt6/Sys33KdIHUaQihFcu7g==} cpu: [loong64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-loong64-musl@4.55.1': resolution: {integrity: sha512-MR8c0+UxAlB22Fq4R+aQSPBayvYa3+9DrwG/i1TKQXFYEaoW3B5b/rkSRIypcZDdWjWnpcvxbNaAJDcSbJU3Lw==} cpu: [loong64] os: [linux] - libc: [musl] '@rollup/rollup-linux-ppc64-gnu@4.55.1': resolution: {integrity: sha512-3KhoECe1BRlSYpMTeVrD4sh2Pw2xgt4jzNSZIIPLFEsnQn9gAnZagW9+VqDqAHgm1Xc77LzJOo2LdigS5qZ+gw==} cpu: [ppc64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-ppc64-musl@4.55.1': resolution: {integrity: sha512-ziR1OuZx0vdYZZ30vueNZTg73alF59DicYrPViG0NEgDVN8/Jl87zkAPu4u6VjZST2llgEUjaiNl9JM6HH1Vdw==} cpu: [ppc64] os: [linux] - libc: [musl] '@rollup/rollup-linux-riscv64-gnu@4.55.1': resolution: {integrity: sha512-uW0Y12ih2XJRERZ4jAfKamTyIHVMPQnTZcQjme2HMVDAHY4amf5u414OqNYC+x+LzRdRcnIG1YodLrrtA8xsxw==} cpu: [riscv64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.55.1': resolution: {integrity: sha512-u9yZ0jUkOED1BFrqu3BwMQoixvGHGZ+JhJNkNKY/hyoEgOwlqKb62qu+7UjbPSHYjiVy8kKJHvXKv5coH4wDeg==} cpu: [riscv64] os: [linux] - libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.55.1': resolution: {integrity: sha512-/0PenBCmqM4ZUd0190j7J0UsQ/1nsi735iPRakO8iPciE7BQ495Y6msPzaOmvx0/pn+eJVVlZrNrSh4WSYLxNg==} cpu: [s390x] os: [linux] - libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.45.1': resolution: {integrity: sha512-+E/lYl6qu1zqgPEnTrs4WysQtvc/Sh4fC2nByfFExqgYrqkKWp1tWIbe+ELhixnenSpBbLXNi6vbEEJ8M7fiHw==} cpu: [x64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.55.1': resolution: {integrity: sha512-a8G4wiQxQG2BAvo+gU6XrReRRqj+pLS2NGXKm8io19goR+K8lw269eTrPkSdDTALwMmJp4th2Uh0D8J9bEV1vg==} cpu: [x64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-x64-musl@4.55.1': resolution: {integrity: sha512-bD+zjpFrMpP/hqkfEcnjXWHMw5BIghGisOKPj+2NaNDuVT+8Ds4mPf3XcPHuat1tz89WRL+1wbcxKY3WSbiT7w==} cpu: [x64] os: [linux] - libc: [musl] '@rollup/rollup-openbsd-x64@4.55.1': resolution: {integrity: sha512-eLXw0dOiqE4QmvikfQ6yjgkg/xDM+MdU9YJuP4ySTibXU0oAvnEWXt7UDJmD4UkYialMfOGFPJnIHSe/kdzPxg==} @@ -7024,28 +7054,24 @@ packages: engines: {node: '>=10'} cpu: [arm64] os: [linux] - libc: [glibc] '@swc/core-linux-arm64-musl@1.15.8': resolution: {integrity: sha512-koiCqL09EwOP1S2RShCI7NbsQuG6r2brTqUYE7pV7kZm9O17wZ0LSz22m6gVibpwEnw8jI3IE1yYsQTVpluALw==} engines: {node: '>=10'} cpu: [arm64] os: [linux] - libc: [musl] '@swc/core-linux-x64-gnu@1.15.8': resolution: {integrity: sha512-4p6lOMU3bC+Vd5ARtKJ/FxpIC5G8v3XLoPEZ5s7mLR8h7411HWC/LmTXDHcrSXRC55zvAVia1eldy6zDLz8iFQ==} engines: {node: '>=10'} cpu: [x64] os: [linux] - libc: [glibc] '@swc/core-linux-x64-musl@1.15.8': resolution: {integrity: sha512-z3XBnbrZAL+6xDGAhJoN4lOueIxC/8rGrJ9tg+fEaeqLEuAtHSW2QHDHxDwkxZMjuF/pZ6MUTjHjbp8wLbuRLA==} engines: {node: '>=10'} cpu: [x64] os: [linux] - libc: [musl] '@swc/core-win32-arm64-msvc@1.15.8': resolution: {integrity: sha512-djQPJ9Rh9vP8GTS/Df3hcc6XP6xnG5c8qsngWId/BLA9oX6C7UzCPAn74BG/wGb9a6j4w3RINuoaieJB3t+7iQ==} @@ -7132,28 +7158,24 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [glibc] '@tailwindcss/oxide-linux-arm64-musl@4.1.18': resolution: {integrity: sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [musl] '@tailwindcss/oxide-linux-x64-gnu@4.1.18': resolution: {integrity: sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [glibc] '@tailwindcss/oxide-linux-x64-musl@4.1.18': resolution: {integrity: sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [musl] '@tailwindcss/oxide-wasm32-wasi@4.1.18': resolution: {integrity: sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==} @@ -11614,57 +11636,107 @@ packages: cpu: [arm64] os: [android] + lightningcss-android-arm64@1.32.0: + resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + lightningcss-darwin-arm64@1.30.2: resolution: {integrity: sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [darwin] + lightningcss-darwin-arm64@1.32.0: + resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + lightningcss-darwin-x64@1.30.2: resolution: {integrity: sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [darwin] + lightningcss-darwin-x64@1.32.0: + resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + lightningcss-freebsd-x64@1.30.2: resolution: {integrity: sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [freebsd] + lightningcss-freebsd-x64@1.32.0: + resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + lightningcss-linux-arm-gnueabihf@1.30.2: resolution: {integrity: sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==} engines: {node: '>= 12.0.0'} cpu: [arm] os: [linux] + lightningcss-linux-arm-gnueabihf@1.32.0: + resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + lightningcss-linux-arm64-gnu@1.30.2: resolution: {integrity: sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - libc: [glibc] + + lightningcss-linux-arm64-gnu@1.32.0: + resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] lightningcss-linux-arm64-musl@1.30.2: resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - libc: [musl] + + lightningcss-linux-arm64-musl@1.32.0: + resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] lightningcss-linux-x64-gnu@1.30.2: resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - libc: [glibc] + + lightningcss-linux-x64-gnu@1.32.0: + resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] lightningcss-linux-x64-musl@1.30.2: resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - libc: [musl] + + lightningcss-linux-x64-musl@1.32.0: + resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] lightningcss-win32-arm64-msvc@1.30.2: resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==} @@ -11672,16 +11744,32 @@ packages: cpu: [arm64] os: [win32] + lightningcss-win32-arm64-msvc@1.32.0: + resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + lightningcss-win32-x64-msvc@1.30.2: resolution: {integrity: sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [win32] + lightningcss-win32-x64-msvc@1.32.0: + resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + lightningcss@1.30.2: resolution: {integrity: sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==} engines: {node: '>= 12.0.0'} + lightningcss@1.32.0: + resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} + engines: {node: '>= 12.0.0'} + lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} @@ -12899,6 +12987,10 @@ packages: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} + pify@2.3.0: resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} engines: {node: '>=0.10.0'} @@ -12976,6 +13068,10 @@ packages: resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} + postcss@8.5.8: + resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==} + engines: {node: ^10 || ^12 || >=14} + postject@1.0.0-alpha.6: resolution: {integrity: sha512-b9Eb8h2eVqNE8edvKdwqkrY6O7kAwmI8kcnBv1NScolYJbo59XUF0noFq+lxbC1yN20bmC0WBEbDC5H/7ASb0A==} engines: {node: '>=14.0.0'} @@ -13962,6 +14058,11 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} hasBin: true + rolldown@1.0.0-rc.12: + resolution: {integrity: sha512-yP4USLIMYrwpPHEFB5JGH1uxhcslv6/hL0OyvTuY+3qlOSJvZ7ntYnoWpehBxufkgN0cvXxppuTu5hHa/zPh+A==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + rolldown@1.0.0-rc.3: resolution: {integrity: sha512-Po/YZECDOqVXjIXrtC5h++a5NLvKAQNrd9ggrIG3sbDfGO5BqTUsrI6l8zdniKRp3r5Tp/2JTrXqx4GIguFCMw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -14542,6 +14643,7 @@ packages: tar@6.2.1: resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} engines: {node: '>=10'} + deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me tar@7.5.9: resolution: {integrity: sha512-BTLcK0xsDh2+PUe9F6c2TlRp4zOOBMTkoQHQIWSIzI0R7KG46uEwq4OPk2W7bZcprBMsuaeFsqwYr7pjh6CuHg==} @@ -15218,6 +15320,49 @@ packages: engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true + vite@8.0.5: + resolution: {integrity: sha512-nmu43Qvq9UopTRfMx2jOYW5l16pb3iDC1JH6yMuPkpVbzK0k+L7dfsEDH4jRgYFmsg0sTAqkojoZgzLMlwHsCQ==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + '@vitejs/devtools': ^0.1.0 + esbuild: ^0.25.0 + jiti: '>=1.21.0' + less: ^4.0.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + '@vitejs/devtools': + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + vitest@3.2.4: resolution: {integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} @@ -19133,11 +19278,11 @@ snapshots: transitivePeerDependencies: - debug - '@joshwooding/vite-plugin-react-docgen-typescript@0.7.0(rolldown-vite@7.3.0(@types/node@24.10.4)(esbuild@0.25.12)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2))(typescript@5.8.3)': + '@joshwooding/vite-plugin-react-docgen-typescript@0.7.0(typescript@5.8.3)(vite@8.0.5(@types/node@24.10.4)(esbuild@0.25.12)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2))': dependencies: glob: 13.0.6 react-docgen-typescript: 2.4.0(typescript@5.8.3) - vite: rolldown-vite@7.3.0(@types/node@24.10.4)(esbuild@0.25.12)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2) + vite: 8.0.5(@types/node@24.10.4)(esbuild@0.25.12)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2) optionalDependencies: typescript: 5.8.3 @@ -19896,6 +20041,8 @@ snapshots: '@oxc-project/types@0.112.0': {} + '@oxc-project/types@0.122.0': {} + '@oxc-project/types@0.95.0': {} '@oxc-project/types@0.99.0': {} @@ -21459,6 +21606,9 @@ snapshots: '@rolldown/binding-android-arm64@1.0.0-beta.53': optional: true + '@rolldown/binding-android-arm64@1.0.0-rc.12': + optional: true + '@rolldown/binding-android-arm64@1.0.0-rc.3': optional: true @@ -21471,6 +21621,9 @@ snapshots: '@rolldown/binding-darwin-arm64@1.0.0-beta.53': optional: true + '@rolldown/binding-darwin-arm64@1.0.0-rc.12': + optional: true + '@rolldown/binding-darwin-arm64@1.0.0-rc.3': optional: true @@ -21483,6 +21636,9 @@ snapshots: '@rolldown/binding-darwin-x64@1.0.0-beta.53': optional: true + '@rolldown/binding-darwin-x64@1.0.0-rc.12': + optional: true + '@rolldown/binding-darwin-x64@1.0.0-rc.3': optional: true @@ -21495,6 +21651,9 @@ snapshots: '@rolldown/binding-freebsd-x64@1.0.0-beta.53': optional: true + '@rolldown/binding-freebsd-x64@1.0.0-rc.12': + optional: true + '@rolldown/binding-freebsd-x64@1.0.0-rc.3': optional: true @@ -21507,6 +21666,9 @@ snapshots: '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.53': optional: true + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.12': + optional: true + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.3': optional: true @@ -21519,6 +21681,9 @@ snapshots: '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.53': optional: true + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.12': + optional: true + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.3': optional: true @@ -21531,9 +21696,18 @@ snapshots: '@rolldown/binding-linux-arm64-musl@1.0.0-beta.53': optional: true + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.12': + optional: true + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.3': optional: true + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.12': + optional: true + + '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.12': + optional: true + '@rolldown/binding-linux-x64-gnu@1.0.0-beta.45': optional: true @@ -21543,6 +21717,9 @@ snapshots: '@rolldown/binding-linux-x64-gnu@1.0.0-beta.53': optional: true + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.12': + optional: true + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.3': optional: true @@ -21555,6 +21732,9 @@ snapshots: '@rolldown/binding-linux-x64-musl@1.0.0-beta.53': optional: true + '@rolldown/binding-linux-x64-musl@1.0.0-rc.12': + optional: true + '@rolldown/binding-linux-x64-musl@1.0.0-rc.3': optional: true @@ -21567,6 +21747,9 @@ snapshots: '@rolldown/binding-openharmony-arm64@1.0.0-beta.53': optional: true + '@rolldown/binding-openharmony-arm64@1.0.0-rc.12': + optional: true + '@rolldown/binding-openharmony-arm64@1.0.0-rc.3': optional: true @@ -21585,6 +21768,11 @@ snapshots: '@napi-rs/wasm-runtime': 1.1.1 optional: true + '@rolldown/binding-wasm32-wasi@1.0.0-rc.12': + dependencies: + '@napi-rs/wasm-runtime': 1.1.1 + optional: true + '@rolldown/binding-wasm32-wasi@1.0.0-rc.3': dependencies: '@napi-rs/wasm-runtime': 1.1.1 @@ -21599,6 +21787,9 @@ snapshots: '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.53': optional: true + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.12': + optional: true + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.3': optional: true @@ -21617,6 +21808,9 @@ snapshots: '@rolldown/binding-win32-x64-msvc@1.0.0-beta.53': optional: true + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.12': + optional: true + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.3': optional: true @@ -21628,6 +21822,8 @@ snapshots: '@rolldown/pluginutils@1.0.0-beta.53': {} + '@rolldown/pluginutils@1.0.0-rc.12': {} + '@rolldown/pluginutils@1.0.0-rc.3': {} '@rollup/pluginutils@5.3.0(rollup@4.55.1)': @@ -22129,10 +22325,10 @@ snapshots: '@standard-schema/utils@0.3.0': {} - '@storybook/addon-docs@10.3.4(@types/react@19.2.7)(esbuild@0.25.12)(rolldown-vite@7.3.0(@types/node@24.10.4)(esbuild@0.25.12)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2))(rollup@4.55.1)(storybook@10.3.4(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))': + '@storybook/addon-docs@10.3.4(@types/react@19.2.7)(esbuild@0.25.12)(rollup@4.55.1)(storybook@10.3.4(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(vite@8.0.5(@types/node@24.10.4)(esbuild@0.25.12)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2))': dependencies: '@mdx-js/react': 3.1.1(@types/react@19.2.7)(react@19.2.3) - '@storybook/csf-plugin': 10.3.4(esbuild@0.25.12)(rolldown-vite@7.3.0(@types/node@24.10.4)(esbuild@0.25.12)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2))(rollup@4.55.1)(storybook@10.3.4(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)) + '@storybook/csf-plugin': 10.3.4(esbuild@0.25.12)(rollup@4.55.1)(storybook@10.3.4(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(vite@8.0.5(@types/node@24.10.4)(esbuild@0.25.12)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2)) '@storybook/icons': 2.0.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@storybook/react-dom-shim': 10.3.4(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@10.3.4(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)) react: 19.2.3 @@ -22151,25 +22347,25 @@ snapshots: storybook: 10.3.4(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) ts-dedent: 2.2.0 - '@storybook/builder-vite@10.3.4(esbuild@0.25.12)(rolldown-vite@7.3.0(@types/node@24.10.4)(esbuild@0.25.12)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2))(rollup@4.55.1)(storybook@10.3.4(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))': + '@storybook/builder-vite@10.3.4(esbuild@0.25.12)(rollup@4.55.1)(storybook@10.3.4(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(vite@8.0.5(@types/node@24.10.4)(esbuild@0.25.12)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2))': dependencies: - '@storybook/csf-plugin': 10.3.4(esbuild@0.25.12)(rolldown-vite@7.3.0(@types/node@24.10.4)(esbuild@0.25.12)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2))(rollup@4.55.1)(storybook@10.3.4(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)) + '@storybook/csf-plugin': 10.3.4(esbuild@0.25.12)(rollup@4.55.1)(storybook@10.3.4(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(vite@8.0.5(@types/node@24.10.4)(esbuild@0.25.12)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2)) storybook: 10.3.4(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) ts-dedent: 2.2.0 - vite: rolldown-vite@7.3.0(@types/node@24.10.4)(esbuild@0.25.12)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2) + vite: 8.0.5(@types/node@24.10.4)(esbuild@0.25.12)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2) transitivePeerDependencies: - esbuild - rollup - webpack - '@storybook/csf-plugin@10.3.4(esbuild@0.25.12)(rolldown-vite@7.3.0(@types/node@24.10.4)(esbuild@0.25.12)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2))(rollup@4.55.1)(storybook@10.3.4(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))': + '@storybook/csf-plugin@10.3.4(esbuild@0.25.12)(rollup@4.55.1)(storybook@10.3.4(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(vite@8.0.5(@types/node@24.10.4)(esbuild@0.25.12)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2))': dependencies: storybook: 10.3.4(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) unplugin: 2.3.11 optionalDependencies: esbuild: 0.25.12 rollup: 4.55.1 - vite: rolldown-vite@7.3.0(@types/node@24.10.4)(esbuild@0.25.12)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2) + vite: 8.0.5(@types/node@24.10.4)(esbuild@0.25.12)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2) '@storybook/global@5.0.0': {} @@ -22184,11 +22380,11 @@ snapshots: react-dom: 19.2.3(react@19.2.3) storybook: 10.3.4(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - '@storybook/react-vite@10.3.4(esbuild@0.25.12)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rolldown-vite@7.3.0(@types/node@24.10.4)(esbuild@0.25.12)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2))(rollup@4.55.1)(storybook@10.3.4(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(typescript@5.8.3)': + '@storybook/react-vite@10.3.4(esbuild@0.25.12)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rollup@4.55.1)(storybook@10.3.4(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(typescript@5.8.3)(vite@8.0.5(@types/node@24.10.4)(esbuild@0.25.12)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2))': dependencies: - '@joshwooding/vite-plugin-react-docgen-typescript': 0.7.0(rolldown-vite@7.3.0(@types/node@24.10.4)(esbuild@0.25.12)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2))(typescript@5.8.3) + '@joshwooding/vite-plugin-react-docgen-typescript': 0.7.0(typescript@5.8.3)(vite@8.0.5(@types/node@24.10.4)(esbuild@0.25.12)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2)) '@rollup/pluginutils': 5.3.0(rollup@4.55.1) - '@storybook/builder-vite': 10.3.4(esbuild@0.25.12)(rolldown-vite@7.3.0(@types/node@24.10.4)(esbuild@0.25.12)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2))(rollup@4.55.1)(storybook@10.3.4(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)) + '@storybook/builder-vite': 10.3.4(esbuild@0.25.12)(rollup@4.55.1)(storybook@10.3.4(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(vite@8.0.5(@types/node@24.10.4)(esbuild@0.25.12)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2)) '@storybook/react': 10.3.4(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(storybook@10.3.4(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(typescript@5.8.3) empathic: 2.0.0 magic-string: 0.30.21 @@ -22198,7 +22394,7 @@ snapshots: resolve: 1.22.11 storybook: 10.3.4(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) tsconfig-paths: 4.2.0 - vite: rolldown-vite@7.3.0(@types/node@24.10.4)(esbuild@0.25.12)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2) + vite: 8.0.5(@types/node@24.10.4)(esbuild@0.25.12)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2) transitivePeerDependencies: - esbuild - rollup @@ -23959,14 +24155,14 @@ snapshots: msw: 2.12.7(@types/node@24.10.4)(typescript@5.8.3) vite: rolldown-vite@7.3.0(@types/node@24.10.4)(esbuild@0.25.12)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2) - '@vitest/mocker@4.1.2(msw@2.12.7(@types/node@24.10.4)(typescript@5.9.3))(rolldown-vite@7.3.0(@types/node@24.10.4)(esbuild@0.25.12)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2))': + '@vitest/mocker@4.1.2(msw@2.12.7(@types/node@24.10.4)(typescript@5.9.3))(vite@8.0.5(@types/node@24.10.4)(esbuild@0.25.12)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2))': dependencies: '@vitest/spy': 4.1.2 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: msw: 2.12.7(@types/node@24.10.4)(typescript@5.9.3) - vite: rolldown-vite@7.3.0(@types/node@24.10.4)(esbuild@0.25.12)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2) + vite: 8.0.5(@types/node@24.10.4)(esbuild@0.25.12)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2) '@vitest/pretty-format@3.2.4': dependencies: @@ -27924,36 +28120,69 @@ snapshots: lightningcss-android-arm64@1.30.2: optional: true + lightningcss-android-arm64@1.32.0: + optional: true + lightningcss-darwin-arm64@1.30.2: optional: true + lightningcss-darwin-arm64@1.32.0: + optional: true + lightningcss-darwin-x64@1.30.2: optional: true + lightningcss-darwin-x64@1.32.0: + optional: true + lightningcss-freebsd-x64@1.30.2: optional: true + lightningcss-freebsd-x64@1.32.0: + optional: true + lightningcss-linux-arm-gnueabihf@1.30.2: optional: true + lightningcss-linux-arm-gnueabihf@1.32.0: + optional: true + lightningcss-linux-arm64-gnu@1.30.2: optional: true + lightningcss-linux-arm64-gnu@1.32.0: + optional: true + lightningcss-linux-arm64-musl@1.30.2: optional: true + lightningcss-linux-arm64-musl@1.32.0: + optional: true + lightningcss-linux-x64-gnu@1.30.2: optional: true + lightningcss-linux-x64-gnu@1.32.0: + optional: true + lightningcss-linux-x64-musl@1.30.2: optional: true + lightningcss-linux-x64-musl@1.32.0: + optional: true + lightningcss-win32-arm64-msvc@1.30.2: optional: true + lightningcss-win32-arm64-msvc@1.32.0: + optional: true + lightningcss-win32-x64-msvc@1.30.2: optional: true + lightningcss-win32-x64-msvc@1.32.0: + optional: true + lightningcss@1.30.2: dependencies: detect-libc: 2.1.2 @@ -27970,6 +28199,22 @@ snapshots: lightningcss-win32-arm64-msvc: 1.30.2 lightningcss-win32-x64-msvc: 1.30.2 + lightningcss@1.32.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.32.0 + lightningcss-darwin-arm64: 1.32.0 + lightningcss-darwin-x64: 1.32.0 + lightningcss-freebsd-x64: 1.32.0 + lightningcss-linux-arm-gnueabihf: 1.32.0 + lightningcss-linux-arm64-gnu: 1.32.0 + lightningcss-linux-arm64-musl: 1.32.0 + lightningcss-linux-x64-gnu: 1.32.0 + lightningcss-linux-x64-musl: 1.32.0 + lightningcss-win32-arm64-msvc: 1.32.0 + lightningcss-win32-x64-msvc: 1.32.0 + lines-and-columns@1.2.4: {} linguist-languages@8.2.0: {} @@ -29504,6 +29749,8 @@ snapshots: picomatch@4.0.3: {} + picomatch@4.0.4: {} + pify@2.3.0: {} pify@3.0.0: {} @@ -29580,6 +29827,12 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + postcss@8.5.8: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + postject@1.0.0-alpha.6: dependencies: commander: 9.5.0 @@ -30893,6 +31146,27 @@ snapshots: '@rolldown/binding-win32-arm64-msvc': 1.0.0-beta.53 '@rolldown/binding-win32-x64-msvc': 1.0.0-beta.53 + rolldown@1.0.0-rc.12: + dependencies: + '@oxc-project/types': 0.122.0 + '@rolldown/pluginutils': 1.0.0-rc.12 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.0.0-rc.12 + '@rolldown/binding-darwin-arm64': 1.0.0-rc.12 + '@rolldown/binding-darwin-x64': 1.0.0-rc.12 + '@rolldown/binding-freebsd-x64': 1.0.0-rc.12 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.12 + '@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.12 + '@rolldown/binding-linux-arm64-musl': 1.0.0-rc.12 + '@rolldown/binding-linux-ppc64-gnu': 1.0.0-rc.12 + '@rolldown/binding-linux-s390x-gnu': 1.0.0-rc.12 + '@rolldown/binding-linux-x64-gnu': 1.0.0-rc.12 + '@rolldown/binding-linux-x64-musl': 1.0.0-rc.12 + '@rolldown/binding-openharmony-arm64': 1.0.0-rc.12 + '@rolldown/binding-wasm32-wasi': 1.0.0-rc.12 + '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.12 + '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.12 + rolldown@1.0.0-rc.3: dependencies: '@oxc-project/types': 0.112.0 @@ -32352,6 +32626,21 @@ snapshots: - tsx - yaml + vite@8.0.5(@types/node@24.10.4)(esbuild@0.25.12)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2): + dependencies: + lightningcss: 1.32.0 + picomatch: 4.0.4 + postcss: 8.5.8 + rolldown: 1.0.0-rc.12 + tinyglobby: 0.2.15 + optionalDependencies: + '@types/node': 24.10.4 + esbuild: 0.25.12 + fsevents: 2.3.3 + jiti: 2.6.1 + tsx: 4.21.0 + yaml: 2.8.2 + vitest@3.2.4(@types/debug@4.1.13)(@types/node@24.10.4)(@vitest/browser@3.2.4)(@vitest/ui@3.2.4)(esbuild@0.25.12)(jiti@2.6.1)(jsdom@26.1.0)(msw@2.12.7(@types/node@24.10.4)(typescript@5.8.3))(tsx@4.21.0)(yaml@2.8.2): dependencies: '@types/chai': 5.2.3 @@ -32397,10 +32686,10 @@ snapshots: - tsx - yaml - vitest@4.1.2(@opentelemetry/api@1.9.0)(@types/node@24.10.4)(@vitest/ui@3.2.4(vitest@3.2.4))(jsdom@26.1.0)(msw@2.12.7(@types/node@24.10.4)(typescript@5.9.3))(rolldown-vite@7.3.0(@types/node@24.10.4)(esbuild@0.25.12)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2)): + vitest@4.1.2(@opentelemetry/api@1.9.0)(@types/node@24.10.4)(@vitest/ui@3.2.4(vitest@3.2.4))(jsdom@26.1.0)(msw@2.12.7(@types/node@24.10.4)(typescript@5.9.3))(vite@8.0.5(@types/node@24.10.4)(esbuild@0.25.12)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2)): dependencies: '@vitest/expect': 4.1.2 - '@vitest/mocker': 4.1.2(msw@2.12.7(@types/node@24.10.4)(typescript@5.9.3))(rolldown-vite@7.3.0(@types/node@24.10.4)(esbuild@0.25.12)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2)) + '@vitest/mocker': 4.1.2(msw@2.12.7(@types/node@24.10.4)(typescript@5.9.3))(vite@8.0.5(@types/node@24.10.4)(esbuild@0.25.12)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2)) '@vitest/pretty-format': 4.1.2 '@vitest/runner': 4.1.2 '@vitest/snapshot': 4.1.2 @@ -32417,7 +32706,7 @@ snapshots: tinyexec: 1.0.2 tinyglobby: 0.2.15 tinyrainbow: 3.1.0 - vite: rolldown-vite@7.3.0(@types/node@24.10.4)(esbuild@0.25.12)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2) + vite: 8.0.5(@types/node@24.10.4)(esbuild@0.25.12)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2) why-is-node-running: 2.3.0 optionalDependencies: '@opentelemetry/api': 1.9.0 diff --git a/src/main/data/api/handlers/models.ts b/src/main/data/api/handlers/models.ts index 7a74fb75da2..ed845cbf79c 100644 --- a/src/main/data/api/handlers/models.ts +++ b/src/main/data/api/handlers/models.ts @@ -7,7 +7,7 @@ */ import { modelService } from '@data/services/ModelService' -import { catalogService } from '@data/services/ProviderCatalogService' +import { registryService } from '@data/services/ProviderRegistryService' import type { ApiHandler, ApiMethods } from '@shared/data/api/apiTypes' import type { ModelSchemas } from '@shared/data/api/schemas/models' @@ -36,7 +36,7 @@ export const modelHandlers: { '/models/resolve': { POST: async ({ body }) => { - return await catalogService.resolveModels(body.providerId, body.models) + return await registryService.resolveModels(body.providerId, body.models) } }, diff --git a/src/main/data/api/handlers/providers.ts b/src/main/data/api/handlers/providers.ts index 2ed9bf83173..b7164dc6621 100644 --- a/src/main/data/api/handlers/providers.ts +++ b/src/main/data/api/handlers/providers.ts @@ -10,7 +10,7 @@ */ import { userProviderInsertSchema } from '@data/db/schemas/userProvider' -import { catalogService } from '@data/services/ProviderCatalogService' +import { registryService } from '@data/services/ProviderRegistryService' import { providerService } from '@data/services/ProviderService' import type { ApiHandler, ApiMethods } from '@shared/data/api/apiTypes' import type { CreateProviderDto, UpdateProviderDto } from '@shared/data/api/schemas/providers' @@ -84,9 +84,9 @@ export const providerHandlers: { } }, - '/providers/:providerId/catalog-models': { + '/providers/:providerId/registry-models': { GET: async ({ params }) => { - return catalogService.getCatalogModelsByProvider(params.providerId) + return registryService.getRegistryModelsByProvider(params.providerId) } }, diff --git a/src/main/data/db/schemas/userModel.ts b/src/main/data/db/schemas/userModel.ts index d1c3c5c2f39..cfcd9a16ab7 100644 --- a/src/main/data/db/schemas/userModel.ts +++ b/src/main/data/db/schemas/userModel.ts @@ -2,7 +2,7 @@ * User Model table schema * * Stores all user models with fully resolved configurations. - * Capabilities and settings are resolved once at add-time (from catalog), + * Capabilities and settings are resolved once at add-time (from registry), * so no runtime merge is needed. * * - presetModelId: traceability marker (which preset this came from, if any) @@ -28,17 +28,17 @@ const { createInsertSchema, createSelectSchema } = createSchemaFactory({ zodInst import { createUpdateTimestamps } from './_columnHelpers' // ═══════════════════════════════════════════════════════════════════════════════ -// Catalog Enrichable Fields +// Registry Enrichable Fields // ═══════════════════════════════════════════════════════════════════════════════ /** - * Fields that can be auto-populated by catalog enrichment. + * Fields that can be auto-populated by registry enrichment. * Used by `userOverrides` to track which fields the user has explicitly modified, - * so that catalog updates don't overwrite user customizations. + * so that registry updates don't overwrite user customizations. * - * The `isCatalogEnrichableField` guard ensures runtime safety. + * The `isRegistryEnrichableField` guard ensures runtime safety. */ -export const CATALOG_ENRICHABLE_FIELDS = [ +export const REGISTRY_ENRICHABLE_FIELDS = [ 'name', 'description', 'capabilities', @@ -53,13 +53,13 @@ export const CATALOG_ENRICHABLE_FIELDS = [ 'pricing' ] as const -export type CatalogEnrichableField = (typeof CATALOG_ENRICHABLE_FIELDS)[number] +export type RegistryEnrichableField = (typeof REGISTRY_ENRICHABLE_FIELDS)[number] -const CATALOG_ENRICHABLE_SET: ReadonlySet = new Set(CATALOG_ENRICHABLE_FIELDS) +const REGISTRY_ENRICHABLE_SET: ReadonlySet = new Set(REGISTRY_ENRICHABLE_FIELDS) -/** Check if a field name is a catalog-enrichable field */ -export function isCatalogEnrichableField(field: string): field is CatalogEnrichableField { - return CATALOG_ENRICHABLE_SET.has(field) +/** Check if a field name is a registry-enrichable field */ +export function isRegistryEnrichableField(field: string): field is RegistryEnrichableField { + return REGISTRY_ENRICHABLE_SET.has(field) } // ═══════════════════════════════════════════════════════════════════════════════ @@ -137,9 +137,9 @@ export const userModelTable = sqliteTable( /** * List of field names the user has explicitly modified. - * Catalog enrichment skips these fields to preserve user customizations. + * Registry enrichment skips these fields to preserve user customizations. */ - userOverrides: text({ mode: 'json' }).$type(), + userOverrides: text({ mode: 'json' }).$type(), ...createUpdateTimestamps }, diff --git a/src/main/data/migration/v2/migrators/mappings/ProviderModelMappings.ts b/src/main/data/migration/v2/migrators/mappings/ProviderModelMappings.ts index 18945f61952..fdc6650dd24 100644 --- a/src/main/data/migration/v2/migrators/mappings/ProviderModelMappings.ts +++ b/src/main/data/migration/v2/migrators/mappings/ProviderModelMappings.ts @@ -8,7 +8,7 @@ import { MODEL_CAPABILITY, type ModelCapability, normalizeModelId -} from '@cherrystudio/provider-catalog' +} from '@cherrystudio/provider-registry' import type { NewUserModel } from '@data/db/schemas/userModel' import type { NewUserProvider } from '@data/db/schemas/userProvider' import type { RuntimeModelPricing } from '@shared/data/types/model' diff --git a/src/main/data/services/ModelService.ts b/src/main/data/services/ModelService.ts index 6f11f3e6743..3d92171958f 100644 --- a/src/main/data/services/ModelService.ts +++ b/src/main/data/services/ModelService.ts @@ -4,11 +4,11 @@ * Provides business logic for: * - Model CRUD operations * - Row to Model conversion - * - Catalog import support + * - Registry import support */ import type { NewUserModel, UserModel } from '@data/db/schemas/userModel' -import { isCatalogEnrichableField, userModelTable } from '@data/db/schemas/userModel' +import { isRegistryEnrichableField, userModelTable } from '@data/db/schemas/userModel' import { loggerService } from '@logger' import { application } from '@main/core/application' import { DataApiErrorFactory } from '@shared/data/api' @@ -18,7 +18,7 @@ import { createUniqueModelId } from '@shared/data/types/model' import { mergeModelConfig } from '@shared/data/utils/modelMerger' import { and, eq, inArray, type SQL } from 'drizzle-orm' -import { catalogService } from './ProviderCatalogService' +import { registryService } from './ProviderRegistryService' const logger = loggerService.withContext('DataApi:ModelService') @@ -118,20 +118,20 @@ export class ModelService { /** * Create a new model * - * Automatically enriches from catalog preset data when a match is found. - * DTO values take priority over catalog (user > catalogOverride > preset). + * Automatically enriches from registry preset data when a match is found. + * DTO values take priority over registry (user > registryOverride > preset). */ async create(dto: CreateModelDto): Promise { const db = application.get('DbService').getDb() - // Look up catalog data for auto-enrichment - const { presetModel, catalogOverride, reasoningFormatTypes, defaultChatEndpoint } = - await catalogService.lookupModel(dto.providerId, dto.modelId) + // Look up registry data for auto-enrichment + const { presetModel, registryOverride, reasoningFormatTypes, defaultChatEndpoint } = + await registryService.lookupModel(dto.providerId, dto.modelId) let values: NewUserModel if (presetModel) { - // Catalog match found — merge DTO with preset data + // Registry match found — merge DTO with preset data const userRow = { providerId: dto.providerId, modelId: dto.modelId, @@ -151,7 +151,7 @@ export class ModelService { const merged = mergeModelConfig( userRow, - catalogOverride, + registryOverride, presetModel, dto.providerId, reasoningFormatTypes, @@ -177,13 +177,13 @@ export class ModelService { pricing: merged.pricing ?? null } - logger.info('Created model with catalog enrichment', { + logger.info('Created model with registry enrichment', { providerId: dto.providerId, modelId: dto.modelId, presetModelId: presetModel.id }) } else { - // No catalog match — store as custom model + // No registry match — store as custom model values = { providerId: dto.providerId, modelId: dto.modelId, @@ -203,7 +203,7 @@ export class ModelService { pricing: dto.pricing ?? null } - logger.info('Created custom model (no catalog match)', { + logger.info('Created custom model (no registry match)', { providerId: dto.providerId, modelId: dto.modelId }) @@ -249,8 +249,8 @@ export class ModelService { if (dto.sortOrder !== undefined) updates.sortOrder = dto.sortOrder if (dto.notes !== undefined) updates.notes = dto.notes - // Track which catalog-enrichable fields the user explicitly changed - const changedEnrichableFields = Object.keys(dto).filter(isCatalogEnrichableField) + // Track which registry-enrichable fields the user explicitly changed + const changedEnrichableFields = Object.keys(dto).filter(isRegistryEnrichableField) if (changedEnrichableFields.length > 0) { const existingOverrides = existing.userOverrides ?? [] updates.userOverrides = [...new Set([...existingOverrides, ...changedEnrichableFields])] @@ -284,7 +284,7 @@ export class ModelService { } /** - * Batch upsert models for a provider (used by CatalogService). + * Batch upsert models for a provider (used by RegistryService). * Inserts new models, updates existing ones. * Respects `userOverrides`: fields the user has explicitly modified are not overwritten. */ diff --git a/src/main/data/services/ProviderCatalogService.ts b/src/main/data/services/ProviderRegistryService.ts similarity index 72% rename from src/main/data/services/ProviderCatalogService.ts rename to src/main/data/services/ProviderRegistryService.ts index f0e27bfcb29..aa15363b2a3 100644 --- a/src/main/data/services/ProviderCatalogService.ts +++ b/src/main/data/services/ProviderRegistryService.ts @@ -1,8 +1,8 @@ /** - * Catalog Service - imports catalog data into SQLite + * Registry Service - imports registry data into SQLite * * Responsible for: - * - Reading catalog protobuf files (models.pb, provider-models.pb, providers.pb) + * - Reading registry protobuf files (models.pb, provider-models.pb, providers.pb) * - Merging configurations using mergeModelConfig/mergeProviderConfig * - Writing resolved data to user_model / user_provider tables * @@ -11,13 +11,13 @@ import { join } from 'node:path' -import type { ProtoModelConfig, ProtoProviderConfig, ProtoProviderModelOverride } from '@cherrystudio/provider-catalog' +import type { ProtoModelConfig, ProtoProviderConfig, ProtoProviderModelOverride } from '@cherrystudio/provider-registry' import { EndpointType, - readModelCatalog, - readProviderCatalog, - readProviderModelCatalog -} from '@cherrystudio/provider-catalog' + readModelRegistry, + readProviderModelRegistry, + readProviderRegistry +} from '@cherrystudio/provider-registry' import type { NewUserModel } from '@data/db/schemas/userModel' import { userModelTable } from '@data/db/schemas/userModel' import type { NewUserProvider } from '@data/db/schemas/userProvider' @@ -33,7 +33,7 @@ import { eq, isNotNull } from 'drizzle-orm' import { modelService } from './ModelService' import { providerService } from './ProviderService' -const logger = loggerService.withContext('DataApi:CatalogService') +const logger = loggerService.withContext('DataApi:RegistryService') /** Map proto ProviderReasoningFormat oneof case to runtime type string */ const CASE_TO_TYPE: Record = { @@ -89,47 +89,47 @@ function buildEndpointConfigsFromProto(p: ProtoProviderConfig): Partial 0 ? configs : null } -export class CatalogService { - private static instance: CatalogService +export class RegistryService { + private static instance: RegistryService - private catalogModels: ProtoModelConfig[] | null = null - private catalogProviderModels: ProtoProviderModelOverride[] | null = null - private catalogProviders: ProtoProviderConfig[] | null = null + private registryModels: ProtoModelConfig[] | null = null + private registryProviderModels: ProtoProviderModelOverride[] | null = null + private registryProviders: ProtoProviderConfig[] | null = null private constructor() {} - public static getInstance(): CatalogService { - if (!CatalogService.instance) { - CatalogService.instance = new CatalogService() + public static getInstance(): RegistryService { + if (!RegistryService.instance) { + RegistryService.instance = new RegistryService() } - return CatalogService.instance + return RegistryService.instance } /** - * Get the path to catalog data directory + * Get the path to registry data directory */ - private getCatalogDataPath(): string { + private getRegistryDataPath(): string { if (isDev) { - return join(__dirname, '..', '..', 'packages', 'provider-catalog', 'data') + return join(__dirname, '..', '..', 'packages', 'provider-registry', 'data') } - return join(process.resourcesPath, 'packages', 'provider-catalog', 'data') + return join(process.resourcesPath, 'packages', 'provider-registry', 'data') } /** - * Load and cache catalog models from models.pb + * Load and cache registry models from models.pb */ - private loadCatalogModels(): ProtoModelConfig[] { - if (this.catalogModels) return this.catalogModels + private loadRegistryModels(): ProtoModelConfig[] { + if (this.registryModels) return this.registryModels try { - const dataPath = this.getCatalogDataPath() - const data = readModelCatalog(join(dataPath, 'models.pb')) + const dataPath = this.getRegistryDataPath() + const data = readModelRegistry(join(dataPath, 'models.pb')) const models = data.models ?? [] - this.catalogModels = models - logger.info('Loaded catalog models', { count: models.length }) + this.registryModels = models + logger.info('Loaded registry models', { count: models.length }) return models } catch (error) { - logger.warn('Failed to load catalog models.pb', { error }) + logger.warn('Failed to load registry models.pb', { error }) return [] } } @@ -138,47 +138,47 @@ export class CatalogService { * Load and cache provider-model overrides from provider-models.pb */ private loadProviderModels(): ProtoProviderModelOverride[] { - if (this.catalogProviderModels) return this.catalogProviderModels + if (this.registryProviderModels) return this.registryProviderModels try { - const dataPath = this.getCatalogDataPath() - const data = readProviderModelCatalog(join(dataPath, 'provider-models.pb')) + const dataPath = this.getRegistryDataPath() + const data = readProviderModelRegistry(join(dataPath, 'provider-models.pb')) const overrides = data.overrides ?? [] - this.catalogProviderModels = overrides - logger.info('Loaded catalog provider-models', { count: overrides.length }) + this.registryProviderModels = overrides + logger.info('Loaded registry provider-models', { count: overrides.length }) return overrides } catch (error) { - logger.warn('Failed to load catalog provider-models.pb', { error }) + logger.warn('Failed to load registry provider-models.pb', { error }) return [] } } /** - * Load and cache catalog providers from providers.pb + * Load and cache registry providers from providers.pb */ - private loadCatalogProviders(): ProtoProviderConfig[] { - if (this.catalogProviders) return this.catalogProviders + private loadRegistryProviders(): ProtoProviderConfig[] { + if (this.registryProviders) return this.registryProviders try { - const dataPath = this.getCatalogDataPath() - const data = readProviderCatalog(join(dataPath, 'providers.pb')) + const dataPath = this.getRegistryDataPath() + const data = readProviderRegistry(join(dataPath, 'providers.pb')) const providers = data.providers ?? [] - this.catalogProviders = providers + this.registryProviders = providers return providers } catch (error) { - logger.warn('Failed to load catalog providers.pb', { error }) + logger.warn('Failed to load registry providers.pb', { error }) return [] } } /** - * Get provider reasoning config from catalog data. + * Get provider reasoning config from registry data. */ - private getCatalogReasoningConfig(providerId: string): { + private getRegistryReasoningConfig(providerId: string): { defaultChatEndpoint?: EndpointType reasoningFormatTypes?: Partial> } { - const providers = this.loadCatalogProviders() + const providers = this.loadRegistryProviders() const provider = providers.find((p) => p.id === providerId) const endpointConfigs = provider ? buildEndpointConfigsFromProto(provider) : null @@ -196,7 +196,7 @@ export class CatalogService { reasoningFormatTypes?: Partial> }> { const db = application.get('DbService').getDb() - const catalogConfig = this.getCatalogReasoningConfig(providerId) + const registryConfig = this.getRegistryReasoningConfig(providerId) const [provider] = await db .select({ defaultChatEndpoint: userProviderTable.defaultChatEndpoint, @@ -207,9 +207,9 @@ export class CatalogService { .limit(1) if (provider) { - const defaultChatEndpoint = provider.defaultChatEndpoint ?? catalogConfig.defaultChatEndpoint + const defaultChatEndpoint = provider.defaultChatEndpoint ?? registryConfig.defaultChatEndpoint const reasoningFormatTypes = - extractReasoningFormatTypes(provider.endpointConfigs) ?? catalogConfig.reasoningFormatTypes + extractReasoningFormatTypes(provider.endpointConfigs) ?? registryConfig.reasoningFormatTypes return { defaultChatEndpoint, @@ -217,18 +217,18 @@ export class CatalogService { } } - return catalogConfig + return registryConfig } /** * Initialize models for a specific provider * - * Reads catalog data, merges configurations, and writes to SQLite. + * Reads registry data, merges configurations, and writes to SQLite. * * @param providerId - The provider ID to initialize models for */ async initializeProvider(providerId: string): Promise { - const catalogModels = this.loadCatalogModels() + const registryModels = this.loadRegistryModels() const providerModels = this.loadProviderModels() const { defaultChatEndpoint, reasoningFormatTypes } = await this.getEffectiveReasoningConfig(providerId) @@ -236,13 +236,13 @@ export class CatalogService { const overrides = providerModels.filter((pm) => pm.providerId === providerId) if (overrides.length === 0) { - logger.info('No catalog overrides found for provider', { providerId }) + logger.info('No registry overrides found for provider', { providerId }) return [] } - // Build a map of catalog models by ID for fast lookup + // Build a map of registry models by ID for fast lookup const modelMap = new Map() - for (const model of catalogModels) { + for (const model of registryModels) { modelMap.set(model.id, model) } @@ -261,7 +261,7 @@ export class CatalogService { continue } - // Merge: no user override (null), catalog override, preset model + // Merge: no user override (null), registry override, preset model const merged = mergeModelConfig(null, override, baseModel, providerId, reasoningFormatTypes, defaultChatEndpoint) mergedModels.push(merged) @@ -290,7 +290,7 @@ export class CatalogService { // Batch upsert to database await modelService.batchUpsert(dbRows) - logger.info('Initialized provider models from catalog', { + logger.info('Initialized provider models from registry', { providerId, count: mergedModels.length }) @@ -299,12 +299,12 @@ export class CatalogService { } /** - * Get catalog preset models for a provider (read-only, no DB writes). + * Get registry preset models for a provider (read-only, no DB writes). */ - getCatalogModelsByProvider(providerId: string): Model[] { - const catalogModels = this.loadCatalogModels() + getRegistryModelsByProvider(providerId: string): Model[] { + const registryModels = this.loadRegistryModels() const providerModels = this.loadProviderModels() - const { defaultChatEndpoint, reasoningFormatTypes } = this.getCatalogReasoningConfig(providerId) + const { defaultChatEndpoint, reasoningFormatTypes } = this.getRegistryReasoningConfig(providerId) const overrides = providerModels.filter((pm) => pm.providerId === providerId) if (overrides.length === 0) { @@ -312,7 +312,7 @@ export class CatalogService { } const modelMap = new Map() - for (const model of catalogModels) { + for (const model of registryModels) { modelMap.set(model.id, model) } @@ -331,17 +331,17 @@ export class CatalogService { } /** - * Initialize preset providers from catalog into SQLite. + * Initialize preset providers from registry into SQLite. * * Reads providers.pb, maps fields to NewUserProvider, and batch upserts. * Also seeds the cherryai provider which is not in providers.pb. */ async initializePresetProviders(): Promise { - const dataPath = this.getCatalogDataPath() - let rawProviders: ReturnType['providers'] = [] + const dataPath = this.getRegistryDataPath() + let rawProviders: ReturnType['providers'] = [] try { - const data = readProviderCatalog(join(dataPath, 'providers.pb')) + const data = readProviderRegistry(join(dataPath, 'providers.pb')) rawProviders = data.providers } catch (error) { logger.warn('Failed to load providers.pb for provider import', { error }) @@ -349,16 +349,16 @@ export class CatalogService { } const dbRows: NewUserProvider[] = rawProviders.map((p) => { - // Map catalog metadata.website to runtime websites field - const catalogWebsite = p.metadata?.website + // Map registry metadata.website to runtime websites field + const registryWebsite = p.metadata?.website const websites = - catalogWebsite && - (catalogWebsite.official || catalogWebsite.docs || catalogWebsite.apiKey || catalogWebsite.models) + registryWebsite && + (registryWebsite.official || registryWebsite.docs || registryWebsite.apiKey || registryWebsite.models) ? { - official: catalogWebsite.official || undefined, - docs: catalogWebsite.docs || undefined, - apiKey: catalogWebsite.apiKey || undefined, - models: catalogWebsite.models || undefined + official: registryWebsite.official || undefined, + docs: registryWebsite.docs || undefined, + apiKey: registryWebsite.apiKey || undefined, + models: registryWebsite.models || undefined } : null @@ -400,14 +400,14 @@ export class CatalogService { await providerService.batchUpsert(dbRows) - logger.info('Initialized preset providers from catalog', { count: dbRows.length }) + logger.info('Initialized preset providers from registry', { count: dbRows.length }) } /** - * Initialize all preset providers from catalog + * Initialize all preset providers from registry * - * Called during app startup and after migration to seed the database with catalog data. - * Seeds provider configurations and enriches existing user models with catalog data. + * Called during app startup and after migration to seed the database with registry data. + * Seeds provider configurations and enriches existing user models with registry data. */ async initializeAllPresetProviders(): Promise { await this.initializePresetProviders() @@ -417,30 +417,30 @@ export class CatalogService { } /** - * Enrich existing user models with catalog data + * Enrich existing user models with registry data * - * For each user model that has a presetModelId, looks up the catalog model + * For each user model that has a presetModelId, looks up the registry model * and updates capabilities, modalities, contextWindow, maxOutputTokens, * reasoning, pricing, etc. * * This bridges the gap between: - * - Migration: inserts user models with null catalog fields - * - Catalog: has rich model metadata (capabilities, limits, pricing) + * - Migration: inserts user models with null registry fields + * - Registry: has rich model metadata (capabilities, limits, pricing) * - * Uses presetModelId to match user models to catalog models. + * Uses presetModelId to match user models to registry models. */ async enrichExistingModels(): Promise { - const catalogModels = this.loadCatalogModels() + const registryModels = this.loadRegistryModels() const providerModels = this.loadProviderModels() - if (catalogModels.length === 0) { - logger.warn('No catalog models loaded, skipping model enrichment') + if (registryModels.length === 0) { + logger.warn('No registry models loaded, skipping model enrichment') return } // Build lookup maps const modelMap = new Map() - for (const m of catalogModels) { + for (const m of registryModels) { modelMap.set(m.id, m) } @@ -484,14 +484,14 @@ export class CatalogService { } const providerOverrides = overridesByProvider.get(row.providerId) - const catalogOverride = providerOverrides?.get(presetModelId) ?? null + const registryOverride = providerOverrides?.get(presetModelId) ?? null const providerConfig = providerConfigMap.get(row.providerId) - const catalogReasoningConfig = this.getCatalogReasoningConfig(row.providerId) - const defaultChatEndpoint = providerConfig?.defaultChatEndpoint ?? catalogReasoningConfig.defaultChatEndpoint + const registryReasoningConfig = this.getRegistryReasoningConfig(row.providerId) + const defaultChatEndpoint = providerConfig?.defaultChatEndpoint ?? registryReasoningConfig.defaultChatEndpoint const reasoningFormatTypes = - extractReasoningFormatTypes(providerConfig?.endpointConfigs) ?? catalogReasoningConfig.reasoningFormatTypes + extractReasoningFormatTypes(providerConfig?.endpointConfigs) ?? registryReasoningConfig.reasoningFormatTypes - // Merge catalog data with user data + // Merge registry data with user data const merged = mergeModelConfig( { providerId: row.providerId, @@ -511,7 +511,7 @@ export class CatalogService { isEnabled: row.isEnabled, isHidden: row.isHidden }, - catalogOverride, + registryOverride, presetModel, row.providerId, reasoningFormatTypes, @@ -547,12 +547,12 @@ export class CatalogService { total: userModels.length, enriched: updateRows.length, skipped: skippedCount, - catalogSize: catalogModels.length + registrySize: registryModels.length }) } /** - * Look up catalog data for a single model + * Look up registry data for a single model * * Returns the preset base model and provider-level override (if any). * Used by ModelService.create to auto-enrich models at save time. @@ -562,26 +562,26 @@ export class CatalogService { modelId: string ): Promise<{ presetModel: ProtoModelConfig | null - catalogOverride: ProtoProviderModelOverride | null + registryOverride: ProtoProviderModelOverride | null defaultChatEndpoint?: EndpointType reasoningFormatTypes?: Partial> }> { - const catalogModels = this.loadCatalogModels() + const registryModels = this.loadRegistryModels() const providerModels = this.loadProviderModels() - const presetModel = catalogModels.find((m) => m.id === modelId) ?? null - const catalogOverride = providerModels.find((pm) => pm.providerId === providerId && pm.modelId === modelId) ?? null + const presetModel = registryModels.find((m) => m.id === modelId) ?? null + const registryOverride = providerModels.find((pm) => pm.providerId === providerId && pm.modelId === modelId) ?? null const reasoningConfig = await this.getEffectiveReasoningConfig(providerId) - return { presetModel, catalogOverride, ...reasoningConfig } + return { presetModel, registryOverride, ...reasoningConfig } } /** - * Resolve raw model entries against catalog data + * Resolve raw model entries against registry data * - * For each raw entry, looks up catalog preset + provider override + * For each raw entry, looks up registry preset + provider override * and produces an enriched Model via mergeModelConfig. - * Models not found in catalog are returned with minimal data. + * Models not found in registry are returned with minimal data. * * Used by the renderer to display enriched models in ManageModelsPopup * before the user adds them. @@ -596,13 +596,13 @@ export class CatalogService { endpointTypes?: number[] }> ): Promise { - const catalogModels = this.loadCatalogModels() + const registryModels = this.loadRegistryModels() const providerModels = this.loadProviderModels() const { defaultChatEndpoint, reasoningFormatTypes } = await this.getEffectiveReasoningConfig(providerId) // Build lookup maps const modelMap = new Map() - for (const m of catalogModels) { + for (const m of registryModels) { modelMap.set(m.id, m) } const overrideMap = new Map() @@ -620,7 +620,7 @@ export class CatalogService { seen.add(raw.modelId) const presetModel = modelMap.get(raw.modelId) ?? null - const catalogOverride = overrideMap.get(raw.modelId) ?? null + const registryOverride = overrideMap.get(raw.modelId) ?? null // Build a minimal user row from the raw entry const userRow = { @@ -635,11 +635,11 @@ export class CatalogService { try { if (presetModel) { - // Catalog match found — merge with preset data + // Registry match found — merge with preset data results.push( mergeModelConfig( userRow, - catalogOverride, + registryOverride, presetModel, providerId, reasoningFormatTypes, @@ -647,7 +647,7 @@ export class CatalogService { ) ) } else { - // No catalog match — return as custom model (no presetModelId) + // No registry match — return as custom model (no presetModelId) results.push(mergeModelConfig({ ...userRow, presetModelId: null }, null, null, providerId)) } } catch (error) { @@ -659,13 +659,13 @@ export class CatalogService { } /** - * Clear cached catalog data + * Clear cached registry data */ clearCache(): void { - this.catalogModels = null - this.catalogProviderModels = null - this.catalogProviders = null + this.registryModels = null + this.registryProviderModels = null + this.registryProviders = null } } -export const catalogService = CatalogService.getInstance() +export const registryService = RegistryService.getInstance() diff --git a/src/main/data/services/ProviderService.ts b/src/main/data/services/ProviderService.ts index df84b814d85..1aaf9bfafff 100644 --- a/src/main/data/services/ProviderService.ts +++ b/src/main/data/services/ProviderService.ts @@ -173,7 +173,7 @@ export class ProviderService { } /** - * Batch upsert providers (used by CatalogService for preset providers) + * Batch upsert providers (used by RegistryService for preset providers) * Inserts new providers, updates only preset fields on existing ones. * Does NOT overwrite user-customized fields (apiKeys, isEnabled, sortOrder, authConfig). */ diff --git a/src/main/index.ts b/src/main/index.ts index 01ef6b6f89e..c4819161c26 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -255,11 +255,11 @@ const startApp = async () => { // (DbService, PreferenceService, CacheService, DataApiService are now ready) await bootstrapPromise - // Sync preset catalog data on every startup so that provider websites/baseUrls + // Sync preset registry data on every startup so that provider websites/baseUrls // and model capabilities/modalities/contextWindow/pricing stay up-to-date - // when the catalog protobuf files are updated between app versions. - const { catalogService } = await import('@data/services/ProviderCatalogService') - await catalogService.initializeAllPresetProviders() + // when the registry protobuf files are updated between app versions. + const { registryService } = await import('@data/services/ProviderRegistryService') + await registryService.initializeAllPresetProviders() // Record current version for tracking // A preparation for v2 data refactoring diff --git a/src/renderer/src/aiCore/services/listModels.ts b/src/renderer/src/aiCore/services/listModels.ts index 099ef9f0e09..a0a1999375e 100644 --- a/src/renderer/src/aiCore/services/listModels.ts +++ b/src/renderer/src/aiCore/services/listModels.ts @@ -200,7 +200,7 @@ const githubFetcher: ModelFetcher = { abortSignal: signal }).catch(() => ({ data: [] as { id: string; owned_by?: string }[] })) ]) - const catalogModels = catalogResponse.map((m) => + const registryModels = catalogResponse.map((m) => toModel(m.id, provider, { name: m.name || m.id, description: pickPreferredString([m.summary, m.description]), @@ -208,7 +208,7 @@ const githubFetcher: ModelFetcher = { }) ) const v1Models = v1Response.data.map((m) => toModel(m.id, provider, { owned_by: m.owned_by })) - return dedup([...catalogModels, ...v1Models], (m) => m.id) + return dedup([...registryModels, ...v1Models], (m) => m.id) } } diff --git a/tsconfig.json b/tsconfig.json index d234949d364..ff180420fc0 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -17,7 +17,7 @@ "paths": { "@renderer/*": ["src/renderer/src/*"], "@shared/*": ["packages/shared/*"], - "@cherrystudio/provider-catalog": ["packages/provider-catalog/src/index.ts"] + "@cherrystudio/provider-registry": ["packages/provider-registry/src/index.ts"] } } } diff --git a/tsconfig.node.json b/tsconfig.node.json index 61541d35053..8d3957a8dec 100644 --- a/tsconfig.node.json +++ b/tsconfig.node.json @@ -6,7 +6,7 @@ "incremental": true, "moduleResolution": "bundler", "paths": { - "@cherrystudio/provider-catalog": ["./packages/provider-catalog/src/index.ts"], + "@cherrystudio/provider-registry": ["./packages/provider-registry/src/index.ts"], "@data/*": ["./src/main/data/*"], "@logger": ["./src/main/services/LoggerService"], "@main/*": ["./src/main/*"], @@ -30,7 +30,7 @@ "src/renderer/src/services/traceApi.ts", "src/renderer/src/types/*", "packages/mcp-trace/**/*", - "packages/provider-catalog/**/*", + "packages/provider-registry/**/*", "packages/shared/**/*", "tests/__mocks__/**/*" ] diff --git a/tsconfig.web.json b/tsconfig.web.json index 38d1c1335d9..67fa887f969 100644 --- a/tsconfig.web.json +++ b/tsconfig.web.json @@ -7,7 +7,7 @@ "jsx": "react-jsx", "moduleResolution": "bundler", "paths": { - "@cherrystudio/provider-catalog": ["./packages/provider-catalog/src/index.ts"], + "@cherrystudio/provider-registry": ["./packages/provider-registry/src/index.ts"], "@cherrystudio/ai-core": ["./packages/aiCore/src/index.ts"], "@cherrystudio/ai-core/*": ["./packages/aiCore/src/*"], "@cherrystudio/ai-core/built-in/plugins": ["./packages/aiCore/src/core/plugins/built-in/index.ts"], @@ -39,7 +39,7 @@ "packages/ai-sdk-provider/**/*", "packages/extension-table-plus/**/*", "packages/mcp-trace/**/*", - "packages/provider-catalog/**/*", + "packages/provider-registry/**/*", "packages/shared/**/*", "packages/ui/**/*" ], From 02a0d54804b5bc2e3d1d867ec8df57f5183e16a6 Mon Sep 17 00:00:00 2001 From: suyao Date: Mon, 6 Apr 2026 23:09:30 +0800 Subject: [PATCH 19/29] refactor: remove rateLimit from ModelLimits and clean up proto reserved MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit rateLimit depends on the user's API plan, not the model itself — not appropriate for preset registry data. Also remove inherited reserved fields since registry.v1 has no legacy data to maintain compatibility with. Co-Authored-By: Claude Opus 4.6 Signed-off-by: suyao --- docs/en/references/data/provider-registry.md | 3 +-- packages/provider-registry/proto/v1/provider.proto | 4 ---- packages/provider-registry/proto/v1/provider_models.proto | 1 - .../provider-registry/src/gen/v1/provider_models_pb.ts | 7 +------ packages/provider-registry/src/gen/v1/provider_pb.ts | 2 +- packages/provider-registry/src/schemas/provider-models.ts | 3 +-- 6 files changed, 4 insertions(+), 16 deletions(-) diff --git a/docs/en/references/data/provider-registry.md b/docs/en/references/data/provider-registry.md index 1622c7fd58e..d0790a8750d 100644 --- a/docs/en/references/data/provider-registry.md +++ b/docs/en/references/data/provider-registry.md @@ -191,8 +191,7 @@ const ProviderModelOverrideSchema = z.object({ limits: z.object({ context_window: z.number().optional(), max_output_tokens: z.number().optional(), - max_input_tokens: z.number().optional(), - rate_limit: z.number().optional() // Requests per minute + max_input_tokens: z.number().optional() }).optional(), pricing: ModelPricingSchema.partial().optional(), diff --git a/packages/provider-registry/proto/v1/provider.proto b/packages/provider-registry/proto/v1/provider.proto index e1ee591f1db..2ce8bbca8fe 100644 --- a/packages/provider-registry/proto/v1/provider.proto +++ b/packages/provider-registry/proto/v1/provider.proto @@ -210,10 +210,6 @@ message ProviderConfig { optional ApiFeatures api_features = 6; optional ProviderMetadata metadata = 8; - - // Reserved field numbers from removed fields - reserved 4, 7, 9; - reserved "base_urls", "models_api_urls", "reasoning_format"; } // Top-level container diff --git a/packages/provider-registry/proto/v1/provider_models.proto b/packages/provider-registry/proto/v1/provider_models.proto index 2204a9e1ae2..603f5551b04 100644 --- a/packages/provider-registry/proto/v1/provider_models.proto +++ b/packages/provider-registry/proto/v1/provider_models.proto @@ -22,7 +22,6 @@ message ModelLimits { optional uint32 context_window = 1; optional uint32 max_output_tokens = 2; optional uint32 max_input_tokens = 3; - optional uint32 rate_limit = 4; } // ═══════════════════════════════════════════════════════════════════════════════ diff --git a/packages/provider-registry/src/gen/v1/provider_models_pb.ts b/packages/provider-registry/src/gen/v1/provider_models_pb.ts index ab0bbc51222..863501ec0b0 100644 --- a/packages/provider-registry/src/gen/v1/provider_models_pb.ts +++ b/packages/provider-registry/src/gen/v1/provider_models_pb.ts @@ -16,7 +16,7 @@ import type { Message } from '@bufbuild/protobuf' export const file_v1_provider_models: GenFile = /*@__PURE__*/ fileDesc( - 'Chh2MS9wcm92aWRlcl9tb2RlbHMucHJvdG8SC3JlZ2lzdHJ5LnYxIpoBChJDYXBhYmlsaXR5T3ZlcnJpZGUSKQoDYWRkGAEgAygOMhwucmVnaXN0cnkudjEuTW9kZWxDYXBhYmlsaXR5EiwKBnJlbW92ZRgCIAMoDjIcLnJlZ2lzdHJ5LnYxLk1vZGVsQ2FwYWJpbGl0eRIrCgVmb3JjZRgDIAMoDjIcLnJlZ2lzdHJ5LnYxLk1vZGVsQ2FwYWJpbGl0eSLPAQoLTW9kZWxMaW1pdHMSGwoOY29udGV4dF93aW5kb3cYASABKA1IAIgBARIeChFtYXhfb3V0cHV0X3Rva2VucxgCIAEoDUgBiAEBEh0KEG1heF9pbnB1dF90b2tlbnMYAyABKA1IAogBARIXCgpyYXRlX2xpbWl0GAQgASgNSAOIAQFCEQoPX2NvbnRleHRfd2luZG93QhQKEl9tYXhfb3V0cHV0X3Rva2Vuc0ITChFfbWF4X2lucHV0X3Rva2Vuc0INCgtfcmF0ZV9saW1pdCKOBgoVUHJvdmlkZXJNb2RlbE92ZXJyaWRlEhMKC3Byb3ZpZGVyX2lkGAEgASgJEhAKCG1vZGVsX2lkGAIgASgJEhkKDGFwaV9tb2RlbF9pZBgDIAEoCUgAiAEBEhoKDW1vZGVsX3ZhcmlhbnQYBCABKAlIAYgBARI6CgxjYXBhYmlsaXRpZXMYBSABKAsyHy5yZWdpc3RyeS52MS5DYXBhYmlsaXR5T3ZlcnJpZGVIAogBARItCgZsaW1pdHMYBiABKAsyGC5yZWdpc3RyeS52MS5Nb2RlbExpbWl0c0gDiAEBEi8KB3ByaWNpbmcYByABKAsyGS5yZWdpc3RyeS52MS5Nb2RlbFByaWNpbmdIBIgBARI1CglyZWFzb25pbmcYCCABKAsyHS5yZWdpc3RyeS52MS5SZWFzb25pbmdTdXBwb3J0SAWIAQESPQoRcGFyYW1ldGVyX3N1cHBvcnQYCSABKAsyHS5yZWdpc3RyeS52MS5QYXJhbWV0ZXJTdXBwb3J0SAaIAQESMQoOZW5kcG9pbnRfdHlwZXMYCiADKA4yGS5yZWdpc3RyeS52MS5FbmRwb2ludFR5cGUSLwoQaW5wdXRfbW9kYWxpdGllcxgLIAMoDjIVLnJlZ2lzdHJ5LnYxLk1vZGFsaXR5EjAKEW91dHB1dF9tb2RhbGl0aWVzGAwgAygOMhUucmVnaXN0cnkudjEuTW9kYWxpdHkSFQoIZGlzYWJsZWQYDSABKAhIB4gBARIZCgxyZXBsYWNlX3dpdGgYDiABKAlICIgBARITCgZyZWFzb24YDyABKAlICYgBARIQCghwcmlvcml0eRgQIAEoDUIPCg1fYXBpX21vZGVsX2lkQhAKDl9tb2RlbF92YXJpYW50Qg8KDV9jYXBhYmlsaXRpZXNCCQoHX2xpbWl0c0IKCghfcHJpY2luZ0IMCgpfcmVhc29uaW5nQhQKEl9wYXJhbWV0ZXJfc3VwcG9ydEILCglfZGlzYWJsZWRCDwoNX3JlcGxhY2Vfd2l0aEIJCgdfcmVhc29uIl8KFVByb3ZpZGVyTW9kZWxSZWdpc3RyeRIPCgd2ZXJzaW9uGAEgASgJEjUKCW92ZXJyaWRlcxgCIAMoCzIiLnJlZ2lzdHJ5LnYxLlByb3ZpZGVyTW9kZWxPdmVycmlkZWIGcHJvdG8z', + 'Chh2MS9wcm92aWRlcl9tb2RlbHMucHJvdG8SC3JlZ2lzdHJ5LnYxIpoBChJDYXBhYmlsaXR5T3ZlcnJpZGUSKQoDYWRkGAEgAygOMhwucmVnaXN0cnkudjEuTW9kZWxDYXBhYmlsaXR5EiwKBnJlbW92ZRgCIAMoDjIcLnJlZ2lzdHJ5LnYxLk1vZGVsQ2FwYWJpbGl0eRIrCgVmb3JjZRgDIAMoDjIcLnJlZ2lzdHJ5LnYxLk1vZGVsQ2FwYWJpbGl0eSKnAQoLTW9kZWxMaW1pdHMSGwoOY29udGV4dF93aW5kb3cYASABKA1IAIgBARIeChFtYXhfb3V0cHV0X3Rva2VucxgCIAEoDUgBiAEBEh0KEG1heF9pbnB1dF90b2tlbnMYAyABKA1IAogBAUIRCg9fY29udGV4dF93aW5kb3dCFAoSX21heF9vdXRwdXRfdG9rZW5zQhMKEV9tYXhfaW5wdXRfdG9rZW5zIo4GChVQcm92aWRlck1vZGVsT3ZlcnJpZGUSEwoLcHJvdmlkZXJfaWQYASABKAkSEAoIbW9kZWxfaWQYAiABKAkSGQoMYXBpX21vZGVsX2lkGAMgASgJSACIAQESGgoNbW9kZWxfdmFyaWFudBgEIAEoCUgBiAEBEjoKDGNhcGFiaWxpdGllcxgFIAEoCzIfLnJlZ2lzdHJ5LnYxLkNhcGFiaWxpdHlPdmVycmlkZUgCiAEBEi0KBmxpbWl0cxgGIAEoCzIYLnJlZ2lzdHJ5LnYxLk1vZGVsTGltaXRzSAOIAQESLwoHcHJpY2luZxgHIAEoCzIZLnJlZ2lzdHJ5LnYxLk1vZGVsUHJpY2luZ0gEiAEBEjUKCXJlYXNvbmluZxgIIAEoCzIdLnJlZ2lzdHJ5LnYxLlJlYXNvbmluZ1N1cHBvcnRIBYgBARI9ChFwYXJhbWV0ZXJfc3VwcG9ydBgJIAEoCzIdLnJlZ2lzdHJ5LnYxLlBhcmFtZXRlclN1cHBvcnRIBogBARIxCg5lbmRwb2ludF90eXBlcxgKIAMoDjIZLnJlZ2lzdHJ5LnYxLkVuZHBvaW50VHlwZRIvChBpbnB1dF9tb2RhbGl0aWVzGAsgAygOMhUucmVnaXN0cnkudjEuTW9kYWxpdHkSMAoRb3V0cHV0X21vZGFsaXRpZXMYDCADKA4yFS5yZWdpc3RyeS52MS5Nb2RhbGl0eRIVCghkaXNhYmxlZBgNIAEoCEgHiAEBEhkKDHJlcGxhY2Vfd2l0aBgOIAEoCUgIiAEBEhMKBnJlYXNvbhgPIAEoCUgJiAEBEhAKCHByaW9yaXR5GBAgASgNQg8KDV9hcGlfbW9kZWxfaWRCEAoOX21vZGVsX3ZhcmlhbnRCDwoNX2NhcGFiaWxpdGllc0IJCgdfbGltaXRzQgoKCF9wcmljaW5nQgwKCl9yZWFzb25pbmdCFAoSX3BhcmFtZXRlcl9zdXBwb3J0QgsKCV9kaXNhYmxlZEIPCg1fcmVwbGFjZV93aXRoQgkKB19yZWFzb24iXwoVUHJvdmlkZXJNb2RlbFJlZ2lzdHJ5Eg8KB3ZlcnNpb24YASABKAkSNQoJb3ZlcnJpZGVzGAIgAygLMiIucmVnaXN0cnkudjEuUHJvdmlkZXJNb2RlbE92ZXJyaWRlYgZwcm90bzM', [file_v1_common, file_v1_model] ) @@ -66,11 +66,6 @@ export type ModelLimits = Message<'registry.v1.ModelLimits'> & { * @generated from field: optional uint32 max_input_tokens = 3; */ maxInputTokens?: number - - /** - * @generated from field: optional uint32 rate_limit = 4; - */ - rateLimit?: number } /** diff --git a/packages/provider-registry/src/gen/v1/provider_pb.ts b/packages/provider-registry/src/gen/v1/provider_pb.ts index a8245985139..a2e3c514d97 100644 --- a/packages/provider-registry/src/gen/v1/provider_pb.ts +++ b/packages/provider-registry/src/gen/v1/provider_pb.ts @@ -14,7 +14,7 @@ import type { Message } from '@bufbuild/protobuf' export const file_v1_provider: GenFile = /*@__PURE__*/ fileDesc( - 'ChF2MS9wcm92aWRlci5wcm90bxILcmVnaXN0cnkudjEinwIKC0FwaUZlYXR1cmVzEhoKDWFycmF5X2NvbnRlbnQYASABKAhIAIgBARIbCg5zdHJlYW1fb3B0aW9ucxgCIAEoCEgBiAEBEhsKDmRldmVsb3Blcl9yb2xlGAMgASgISAKIAQESGQoMc2VydmljZV90aWVyGAQgASgISAOIAQESFgoJdmVyYm9zaXR5GAUgASgISASIAQESHAoPZW5hYmxlX3RoaW5raW5nGAYgASgISAWIAQFCEAoOX2FycmF5X2NvbnRlbnRCEQoPX3N0cmVhbV9vcHRpb25zQhEKD19kZXZlbG9wZXJfcm9sZUIPCg1fc2VydmljZV90aWVyQgwKCl92ZXJib3NpdHlCEgoQX2VuYWJsZV90aGlua2luZyJzChlPcGVuQUlDaGF0UmVhc29uaW5nRm9ybWF0EkEKEHJlYXNvbmluZ19lZmZvcnQYASABKA4yIi5yZWdpc3RyeS52MS5PcGVuQUlSZWFzb25pbmdFZmZvcnRIAIgBAUITChFfcmVhc29uaW5nX2VmZm9ydCKpAQoeT3BlbkFJUmVzcG9uc2VzUmVhc29uaW5nRm9ybWF0EjcKBmVmZm9ydBgBIAEoDjIiLnJlZ2lzdHJ5LnYxLk9wZW5BSVJlYXNvbmluZ0VmZm9ydEgAiAEBEjcKB3N1bW1hcnkYAiABKA4yIS5yZWdpc3RyeS52MS5SZXNwb25zZXNTdW1tYXJ5TW9kZUgBiAEBQgkKB19lZmZvcnRCCgoIX3N1bW1hcnkizwEKGEFudGhyb3BpY1JlYXNvbmluZ0Zvcm1hdBI1CgR0eXBlGAEgASgOMiIucmVnaXN0cnkudjEuQW50aHJvcGljVGhpbmtpbmdUeXBlSACIAQESGgoNYnVkZ2V0X3Rva2VucxgCIAEoDUgBiAEBEjoKBmVmZm9ydBgDIAEoDjIlLnJlZ2lzdHJ5LnYxLkFudGhyb3BpY1JlYXNvbmluZ0VmZm9ydEgCiAEBQgcKBV90eXBlQhAKDl9idWRnZXRfdG9rZW5zQgkKB19lZmZvcnQifAoUR2VtaW5pVGhpbmtpbmdDb25maWcSHQoQaW5jbHVkZV90aG91Z2h0cxgBIAEoCEgAiAEBEhwKD3RoaW5raW5nX2J1ZGdldBgCIAEoDUgBiAEBQhMKEV9pbmNsdWRlX3Rob3VnaHRzQhIKEF90aGlua2luZ19idWRnZXQimwEKFUdlbWluaVJlYXNvbmluZ0Zvcm1hdBI8Cg90aGlua2luZ19jb25maWcYASABKAsyIS5yZWdpc3RyeS52MS5HZW1pbmlUaGlua2luZ0NvbmZpZ0gAEjoKDnRoaW5raW5nX2xldmVsGAIgASgOMiAucmVnaXN0cnkudjEuR2VtaW5pVGhpbmtpbmdMZXZlbEgAQggKBmNvbmZpZyKpAQoZT3BlblJvdXRlclJlYXNvbmluZ0Zvcm1hdBI3CgZlZmZvcnQYASABKA4yIi5yZWdpc3RyeS52MS5PcGVuQUlSZWFzb25pbmdFZmZvcnRIAIgBARIXCgptYXhfdG9rZW5zGAIgASgNSAGIAQESFAoHZXhjbHVkZRgDIAEoCEgCiAEBQgkKB19lZmZvcnRCDQoLX21heF90b2tlbnNCCgoIX2V4Y2x1ZGUigwEKHUVuYWJsZVRoaW5raW5nUmVhc29uaW5nRm9ybWF0EhwKD2VuYWJsZV90aGlua2luZxgBIAEoCEgAiAEBEhwKD3RoaW5raW5nX2J1ZGdldBgCIAEoDUgBiAEBQhIKEF9lbmFibGVfdGhpbmtpbmdCEgoQX3RoaW5raW5nX2J1ZGdldCJmChtUaGlua2luZ1R5cGVSZWFzb25pbmdGb3JtYXQSNQoNdGhpbmtpbmdfdHlwZRgBIAEoDjIZLnJlZ2lzdHJ5LnYxLlRoaW5raW5nVHlwZUgAiAEBQhAKDl90aGlua2luZ190eXBlIoQBChhEYXNoc2NvcGVSZWFzb25pbmdGb3JtYXQSHAoPZW5hYmxlX3RoaW5raW5nGAEgASgISACIAQESHwoSaW5jcmVtZW50YWxfb3V0cHV0GAIgASgISAGIAQFCEgoQX2VuYWJsZV90aGlua2luZ0IVChNfaW5jcmVtZW50YWxfb3V0cHV0InEKGVNlbGZIb3N0ZWRSZWFzb25pbmdGb3JtYXQSHAoPZW5hYmxlX3RoaW5raW5nGAEgASgISACIAQESFQoIdGhpbmtpbmcYAiABKAhIAYgBAUISChBfZW5hYmxlX3RoaW5raW5nQgsKCV90aGlua2luZyLgBAoXUHJvdmlkZXJSZWFzb25pbmdGb3JtYXQSPQoLb3BlbmFpX2NoYXQYASABKAsyJi5yZWdpc3RyeS52MS5PcGVuQUlDaGF0UmVhc29uaW5nRm9ybWF0SAASRwoQb3BlbmFpX3Jlc3BvbnNlcxgCIAEoCzIrLnJlZ2lzdHJ5LnYxLk9wZW5BSVJlc3BvbnNlc1JlYXNvbmluZ0Zvcm1hdEgAEjoKCWFudGhyb3BpYxgDIAEoCzIlLnJlZ2lzdHJ5LnYxLkFudGhyb3BpY1JlYXNvbmluZ0Zvcm1hdEgAEjQKBmdlbWluaRgEIAEoCzIiLnJlZ2lzdHJ5LnYxLkdlbWluaVJlYXNvbmluZ0Zvcm1hdEgAEjwKCm9wZW5yb3V0ZXIYBSABKAsyJi5yZWdpc3RyeS52MS5PcGVuUm91dGVyUmVhc29uaW5nRm9ybWF0SAASRQoPZW5hYmxlX3RoaW5raW5nGAYgASgLMioucmVnaXN0cnkudjEuRW5hYmxlVGhpbmtpbmdSZWFzb25pbmdGb3JtYXRIABJBCg10aGlua2luZ190eXBlGAcgASgLMigucmVnaXN0cnkudjEuVGhpbmtpbmdUeXBlUmVhc29uaW5nRm9ybWF0SAASOgoJZGFzaHNjb3BlGAggASgLMiUucmVnaXN0cnkudjEuRGFzaHNjb3BlUmVhc29uaW5nRm9ybWF0SAASPQoLc2VsZl9ob3N0ZWQYCSABKAsyJi5yZWdpc3RyeS52MS5TZWxmSG9zdGVkUmVhc29uaW5nRm9ybWF0SABCCAoGZm9ybWF0IpMBCg9Qcm92aWRlcldlYnNpdGUSFQoIb2ZmaWNpYWwYASABKAlIAIgBARIRCgRkb2NzGAIgASgJSAGIAQESFAoHYXBpX2tleRgDIAEoCUgCiAEBEhMKBm1vZGVscxgEIAEoCUgDiAEBQgsKCV9vZmZpY2lhbEIHCgVfZG9jc0IKCghfYXBpX2tleUIJCgdfbW9kZWxzInsKDU1vZGVsc0FwaVVybHMSFAoHZGVmYXVsdBgBIAEoCUgAiAEBEhYKCWVtYmVkZGluZxgCIAEoCUgBiAEBEhUKCHJlcmFua2VyGAMgASgJSAKIAQFCCgoIX2RlZmF1bHRCDAoKX2VtYmVkZGluZ0ILCglfcmVyYW5rZXIiUgoQUHJvdmlkZXJNZXRhZGF0YRIyCgd3ZWJzaXRlGAEgASgLMhwucmVnaXN0cnkudjEuUHJvdmlkZXJXZWJzaXRlSACIAQFCCgoIX3dlYnNpdGUi3AEKDkVuZHBvaW50Q29uZmlnEhUKCGJhc2VfdXJsGAEgASgJSACIAQESOAoPbW9kZWxzX2FwaV91cmxzGAIgASgLMhoucmVnaXN0cnkudjEuTW9kZWxzQXBpVXJsc0gBiAEBEkMKEHJlYXNvbmluZ19mb3JtYXQYAyABKAsyJC5yZWdpc3RyeS52MS5Qcm92aWRlclJlYXNvbmluZ0Zvcm1hdEgCiAEBQgsKCV9iYXNlX3VybEISChBfbW9kZWxzX2FwaV91cmxzQhMKEV9yZWFzb25pbmdfZm9ybWF0IpcECg5Qcm92aWRlckNvbmZpZxIKCgJpZBgBIAEoCRIMCgRuYW1lGAIgASgJEhgKC2Rlc2NyaXB0aW9uGAMgASgJSACIAQESSgoQZW5kcG9pbnRfY29uZmlncxgKIAMoCzIwLnJlZ2lzdHJ5LnYxLlByb3ZpZGVyQ29uZmlnLkVuZHBvaW50Q29uZmlnc0VudHJ5Ej0KFWRlZmF1bHRfY2hhdF9lbmRwb2ludBgFIAEoDjIZLnJlZ2lzdHJ5LnYxLkVuZHBvaW50VHlwZUgBiAEBEjMKDGFwaV9mZWF0dXJlcxgGIAEoCzIYLnJlZ2lzdHJ5LnYxLkFwaUZlYXR1cmVzSAKIAQESNAoIbWV0YWRhdGEYCCABKAsyHS5yZWdpc3RyeS52MS5Qcm92aWRlck1ldGFkYXRhSAOIAQEaUwoURW5kcG9pbnRDb25maWdzRW50cnkSCwoDa2V5GAEgASgFEioKBXZhbHVlGAIgASgLMhsucmVnaXN0cnkudjEuRW5kcG9pbnRDb25maWc6AjgBQg4KDF9kZXNjcmlwdGlvbkIYChZfZGVmYXVsdF9jaGF0X2VuZHBvaW50Qg8KDV9hcGlfZmVhdHVyZXNCCwoJX21ldGFkYXRhSgQIBBAFSgQIBxAISgQICRAKUgliYXNlX3VybHNSD21vZGVsc19hcGlfdXJsc1IQcmVhc29uaW5nX2Zvcm1hdCJTChBQcm92aWRlclJlZ2lzdHJ5Eg8KB3ZlcnNpb24YASABKAkSLgoJcHJvdmlkZXJzGAIgAygLMhsucmVnaXN0cnkudjEuUHJvdmlkZXJDb25maWcqqAEKFFJlc3BvbnNlc1N1bW1hcnlNb2RlEiYKIlJFU1BPTlNFU19TVU1NQVJZX01PREVfVU5TUEVDSUZJRUQQABIfChtSRVNQT05TRVNfU1VNTUFSWV9NT0RFX0FVVE8QARIiCh5SRVNQT05TRVNfU1VNTUFSWV9NT0RFX0NPTkNJU0UQAhIjCh9SRVNQT05TRVNfU1VNTUFSWV9NT0RFX0RFVEFJTEVEEAMqsQEKFUFudGhyb3BpY1RoaW5raW5nVHlwZRInCiNBTlRIUk9QSUNfVEhJTktJTkdfVFlQRV9VTlNQRUNJRklFRBAAEiMKH0FOVEhST1BJQ19USElOS0lOR19UWVBFX0VOQUJMRUQQARIkCiBBTlRIUk9QSUNfVEhJTktJTkdfVFlQRV9ESVNBQkxFRBACEiQKIEFOVEhST1BJQ19USElOS0lOR19UWVBFX0FEQVBUSVZFEAMqwAEKE0dlbWluaVRoaW5raW5nTGV2ZWwSJQohR0VNSU5JX1RISU5LSU5HX0xFVkVMX1VOU1BFQ0lGSUVEEAASIQodR0VNSU5JX1RISU5LSU5HX0xFVkVMX01JTklNQUwQARIdChlHRU1JTklfVEhJTktJTkdfTEVWRUxfTE9XEAISIAocR0VNSU5JX1RISU5LSU5HX0xFVkVMX01FRElVTRADEh4KGkdFTUlOSV9USElOS0lOR19MRVZFTF9ISUdIEAQqfAoMVGhpbmtpbmdUeXBlEh0KGVRISU5LSU5HX1RZUEVfVU5TUEVDSUZJRUQQABIZChVUSElOS0lOR19UWVBFX0VOQUJMRUQQARIaChZUSElOS0lOR19UWVBFX0RJU0FCTEVEEAISFgoSVEhJTktJTkdfVFlQRV9BVVRPEANiBnByb3RvMw', + 'ChF2MS9wcm92aWRlci5wcm90bxILcmVnaXN0cnkudjEinwIKC0FwaUZlYXR1cmVzEhoKDWFycmF5X2NvbnRlbnQYASABKAhIAIgBARIbCg5zdHJlYW1fb3B0aW9ucxgCIAEoCEgBiAEBEhsKDmRldmVsb3Blcl9yb2xlGAMgASgISAKIAQESGQoMc2VydmljZV90aWVyGAQgASgISAOIAQESFgoJdmVyYm9zaXR5GAUgASgISASIAQESHAoPZW5hYmxlX3RoaW5raW5nGAYgASgISAWIAQFCEAoOX2FycmF5X2NvbnRlbnRCEQoPX3N0cmVhbV9vcHRpb25zQhEKD19kZXZlbG9wZXJfcm9sZUIPCg1fc2VydmljZV90aWVyQgwKCl92ZXJib3NpdHlCEgoQX2VuYWJsZV90aGlua2luZyJzChlPcGVuQUlDaGF0UmVhc29uaW5nRm9ybWF0EkEKEHJlYXNvbmluZ19lZmZvcnQYASABKA4yIi5yZWdpc3RyeS52MS5PcGVuQUlSZWFzb25pbmdFZmZvcnRIAIgBAUITChFfcmVhc29uaW5nX2VmZm9ydCKpAQoeT3BlbkFJUmVzcG9uc2VzUmVhc29uaW5nRm9ybWF0EjcKBmVmZm9ydBgBIAEoDjIiLnJlZ2lzdHJ5LnYxLk9wZW5BSVJlYXNvbmluZ0VmZm9ydEgAiAEBEjcKB3N1bW1hcnkYAiABKA4yIS5yZWdpc3RyeS52MS5SZXNwb25zZXNTdW1tYXJ5TW9kZUgBiAEBQgkKB19lZmZvcnRCCgoIX3N1bW1hcnkizwEKGEFudGhyb3BpY1JlYXNvbmluZ0Zvcm1hdBI1CgR0eXBlGAEgASgOMiIucmVnaXN0cnkudjEuQW50aHJvcGljVGhpbmtpbmdUeXBlSACIAQESGgoNYnVkZ2V0X3Rva2VucxgCIAEoDUgBiAEBEjoKBmVmZm9ydBgDIAEoDjIlLnJlZ2lzdHJ5LnYxLkFudGhyb3BpY1JlYXNvbmluZ0VmZm9ydEgCiAEBQgcKBV90eXBlQhAKDl9idWRnZXRfdG9rZW5zQgkKB19lZmZvcnQifAoUR2VtaW5pVGhpbmtpbmdDb25maWcSHQoQaW5jbHVkZV90aG91Z2h0cxgBIAEoCEgAiAEBEhwKD3RoaW5raW5nX2J1ZGdldBgCIAEoDUgBiAEBQhMKEV9pbmNsdWRlX3Rob3VnaHRzQhIKEF90aGlua2luZ19idWRnZXQimwEKFUdlbWluaVJlYXNvbmluZ0Zvcm1hdBI8Cg90aGlua2luZ19jb25maWcYASABKAsyIS5yZWdpc3RyeS52MS5HZW1pbmlUaGlua2luZ0NvbmZpZ0gAEjoKDnRoaW5raW5nX2xldmVsGAIgASgOMiAucmVnaXN0cnkudjEuR2VtaW5pVGhpbmtpbmdMZXZlbEgAQggKBmNvbmZpZyKpAQoZT3BlblJvdXRlclJlYXNvbmluZ0Zvcm1hdBI3CgZlZmZvcnQYASABKA4yIi5yZWdpc3RyeS52MS5PcGVuQUlSZWFzb25pbmdFZmZvcnRIAIgBARIXCgptYXhfdG9rZW5zGAIgASgNSAGIAQESFAoHZXhjbHVkZRgDIAEoCEgCiAEBQgkKB19lZmZvcnRCDQoLX21heF90b2tlbnNCCgoIX2V4Y2x1ZGUigwEKHUVuYWJsZVRoaW5raW5nUmVhc29uaW5nRm9ybWF0EhwKD2VuYWJsZV90aGlua2luZxgBIAEoCEgAiAEBEhwKD3RoaW5raW5nX2J1ZGdldBgCIAEoDUgBiAEBQhIKEF9lbmFibGVfdGhpbmtpbmdCEgoQX3RoaW5raW5nX2J1ZGdldCJmChtUaGlua2luZ1R5cGVSZWFzb25pbmdGb3JtYXQSNQoNdGhpbmtpbmdfdHlwZRgBIAEoDjIZLnJlZ2lzdHJ5LnYxLlRoaW5raW5nVHlwZUgAiAEBQhAKDl90aGlua2luZ190eXBlIoQBChhEYXNoc2NvcGVSZWFzb25pbmdGb3JtYXQSHAoPZW5hYmxlX3RoaW5raW5nGAEgASgISACIAQESHwoSaW5jcmVtZW50YWxfb3V0cHV0GAIgASgISAGIAQFCEgoQX2VuYWJsZV90aGlua2luZ0IVChNfaW5jcmVtZW50YWxfb3V0cHV0InEKGVNlbGZIb3N0ZWRSZWFzb25pbmdGb3JtYXQSHAoPZW5hYmxlX3RoaW5raW5nGAEgASgISACIAQESFQoIdGhpbmtpbmcYAiABKAhIAYgBAUISChBfZW5hYmxlX3RoaW5raW5nQgsKCV90aGlua2luZyLgBAoXUHJvdmlkZXJSZWFzb25pbmdGb3JtYXQSPQoLb3BlbmFpX2NoYXQYASABKAsyJi5yZWdpc3RyeS52MS5PcGVuQUlDaGF0UmVhc29uaW5nRm9ybWF0SAASRwoQb3BlbmFpX3Jlc3BvbnNlcxgCIAEoCzIrLnJlZ2lzdHJ5LnYxLk9wZW5BSVJlc3BvbnNlc1JlYXNvbmluZ0Zvcm1hdEgAEjoKCWFudGhyb3BpYxgDIAEoCzIlLnJlZ2lzdHJ5LnYxLkFudGhyb3BpY1JlYXNvbmluZ0Zvcm1hdEgAEjQKBmdlbWluaRgEIAEoCzIiLnJlZ2lzdHJ5LnYxLkdlbWluaVJlYXNvbmluZ0Zvcm1hdEgAEjwKCm9wZW5yb3V0ZXIYBSABKAsyJi5yZWdpc3RyeS52MS5PcGVuUm91dGVyUmVhc29uaW5nRm9ybWF0SAASRQoPZW5hYmxlX3RoaW5raW5nGAYgASgLMioucmVnaXN0cnkudjEuRW5hYmxlVGhpbmtpbmdSZWFzb25pbmdGb3JtYXRIABJBCg10aGlua2luZ190eXBlGAcgASgLMigucmVnaXN0cnkudjEuVGhpbmtpbmdUeXBlUmVhc29uaW5nRm9ybWF0SAASOgoJZGFzaHNjb3BlGAggASgLMiUucmVnaXN0cnkudjEuRGFzaHNjb3BlUmVhc29uaW5nRm9ybWF0SAASPQoLc2VsZl9ob3N0ZWQYCSABKAsyJi5yZWdpc3RyeS52MS5TZWxmSG9zdGVkUmVhc29uaW5nRm9ybWF0SABCCAoGZm9ybWF0IpMBCg9Qcm92aWRlcldlYnNpdGUSFQoIb2ZmaWNpYWwYASABKAlIAIgBARIRCgRkb2NzGAIgASgJSAGIAQESFAoHYXBpX2tleRgDIAEoCUgCiAEBEhMKBm1vZGVscxgEIAEoCUgDiAEBQgsKCV9vZmZpY2lhbEIHCgVfZG9jc0IKCghfYXBpX2tleUIJCgdfbW9kZWxzInsKDU1vZGVsc0FwaVVybHMSFAoHZGVmYXVsdBgBIAEoCUgAiAEBEhYKCWVtYmVkZGluZxgCIAEoCUgBiAEBEhUKCHJlcmFua2VyGAMgASgJSAKIAQFCCgoIX2RlZmF1bHRCDAoKX2VtYmVkZGluZ0ILCglfcmVyYW5rZXIiUgoQUHJvdmlkZXJNZXRhZGF0YRIyCgd3ZWJzaXRlGAEgASgLMhwucmVnaXN0cnkudjEuUHJvdmlkZXJXZWJzaXRlSACIAQFCCgoIX3dlYnNpdGUi3AEKDkVuZHBvaW50Q29uZmlnEhUKCGJhc2VfdXJsGAEgASgJSACIAQESOAoPbW9kZWxzX2FwaV91cmxzGAIgASgLMhoucmVnaXN0cnkudjEuTW9kZWxzQXBpVXJsc0gBiAEBEkMKEHJlYXNvbmluZ19mb3JtYXQYAyABKAsyJC5yZWdpc3RyeS52MS5Qcm92aWRlclJlYXNvbmluZ0Zvcm1hdEgCiAEBQgsKCV9iYXNlX3VybEISChBfbW9kZWxzX2FwaV91cmxzQhMKEV9yZWFzb25pbmdfZm9ybWF0ItcDCg5Qcm92aWRlckNvbmZpZxIKCgJpZBgBIAEoCRIMCgRuYW1lGAIgASgJEhgKC2Rlc2NyaXB0aW9uGAMgASgJSACIAQESSgoQZW5kcG9pbnRfY29uZmlncxgKIAMoCzIwLnJlZ2lzdHJ5LnYxLlByb3ZpZGVyQ29uZmlnLkVuZHBvaW50Q29uZmlnc0VudHJ5Ej0KFWRlZmF1bHRfY2hhdF9lbmRwb2ludBgFIAEoDjIZLnJlZ2lzdHJ5LnYxLkVuZHBvaW50VHlwZUgBiAEBEjMKDGFwaV9mZWF0dXJlcxgGIAEoCzIYLnJlZ2lzdHJ5LnYxLkFwaUZlYXR1cmVzSAKIAQESNAoIbWV0YWRhdGEYCCABKAsyHS5yZWdpc3RyeS52MS5Qcm92aWRlck1ldGFkYXRhSAOIAQEaUwoURW5kcG9pbnRDb25maWdzRW50cnkSCwoDa2V5GAEgASgFEioKBXZhbHVlGAIgASgLMhsucmVnaXN0cnkudjEuRW5kcG9pbnRDb25maWc6AjgBQg4KDF9kZXNjcmlwdGlvbkIYChZfZGVmYXVsdF9jaGF0X2VuZHBvaW50Qg8KDV9hcGlfZmVhdHVyZXNCCwoJX21ldGFkYXRhIlMKEFByb3ZpZGVyUmVnaXN0cnkSDwoHdmVyc2lvbhgBIAEoCRIuCglwcm92aWRlcnMYAiADKAsyGy5yZWdpc3RyeS52MS5Qcm92aWRlckNvbmZpZyqoAQoUUmVzcG9uc2VzU3VtbWFyeU1vZGUSJgoiUkVTUE9OU0VTX1NVTU1BUllfTU9ERV9VTlNQRUNJRklFRBAAEh8KG1JFU1BPTlNFU19TVU1NQVJZX01PREVfQVVUTxABEiIKHlJFU1BPTlNFU19TVU1NQVJZX01PREVfQ09OQ0lTRRACEiMKH1JFU1BPTlNFU19TVU1NQVJZX01PREVfREVUQUlMRUQQAyqxAQoVQW50aHJvcGljVGhpbmtpbmdUeXBlEicKI0FOVEhST1BJQ19USElOS0lOR19UWVBFX1VOU1BFQ0lGSUVEEAASIwofQU5USFJPUElDX1RISU5LSU5HX1RZUEVfRU5BQkxFRBABEiQKIEFOVEhST1BJQ19USElOS0lOR19UWVBFX0RJU0FCTEVEEAISJAogQU5USFJPUElDX1RISU5LSU5HX1RZUEVfQURBUFRJVkUQAyrAAQoTR2VtaW5pVGhpbmtpbmdMZXZlbBIlCiFHRU1JTklfVEhJTktJTkdfTEVWRUxfVU5TUEVDSUZJRUQQABIhCh1HRU1JTklfVEhJTktJTkdfTEVWRUxfTUlOSU1BTBABEh0KGUdFTUlOSV9USElOS0lOR19MRVZFTF9MT1cQAhIgChxHRU1JTklfVEhJTktJTkdfTEVWRUxfTUVESVVNEAMSHgoaR0VNSU5JX1RISU5LSU5HX0xFVkVMX0hJR0gQBCp8CgxUaGlua2luZ1R5cGUSHQoZVEhJTktJTkdfVFlQRV9VTlNQRUNJRklFRBAAEhkKFVRISU5LSU5HX1RZUEVfRU5BQkxFRBABEhoKFlRISU5LSU5HX1RZUEVfRElTQUJMRUQQAhIWChJUSElOS0lOR19UWVBFX0FVVE8QA2IGcHJvdG8z', [file_v1_common] ) diff --git a/packages/provider-registry/src/schemas/provider-models.ts b/packages/provider-registry/src/schemas/provider-models.ts index 4f413ac6573..30467532f7e 100644 --- a/packages/provider-registry/src/schemas/provider-models.ts +++ b/packages/provider-registry/src/schemas/provider-models.ts @@ -52,8 +52,7 @@ export const ProviderModelOverrideSchema = z.object({ .object({ contextWindow: z.number().optional(), maxOutputTokens: z.number().optional(), - maxInputTokens: z.number().optional(), - rateLimit: z.number().optional() // requests per minute + maxInputTokens: z.number().optional() }) .optional(), pricing: ModelPricingSchema.partial().optional(), From 5dfee6d282f0e1b8c06aa346bc8ad956f2bb1b70 Mon Sep 17 00:00:00 2001 From: suyao Date: Tue, 7 Apr 2026 00:28:51 +0800 Subject: [PATCH 20/29] refactor: migrate ProviderRegistryService to lifecycle and improve API design - Convert RegistryService to lifecycle-managed ProviderRegistryService (@Injectable, @ServicePhase, @DependsOn, BaseService) - Register in serviceRegistry.ts; remove manual init from index.ts - Replace singleton imports with application.get('ProviderRegistryService') - Move /models/resolve to POST /providers/:providerId/registry-models (RESTful: same resource path, different HTTP methods) - Rename ResolveModelsDto to EnrichModelsDto (providerId from URL params) Co-Authored-By: Claude Opus 4.6 Signed-off-by: suyao --- packages/shared/data/api/schemas/models.ts | 20 ++---------- packages/shared/data/api/schemas/providers.ts | 12 ++++++- src/main/core/application/serviceRegistry.ts | 2 ++ src/main/data/api/handlers/models.ts | 7 ---- src/main/data/api/handlers/providers.ts | 8 +++-- src/main/data/services/ModelService.ts | 7 ++-- .../data/services/ProviderRegistryService.ts | 32 ++++++++++--------- src/main/index.ts | 6 ---- 8 files changed, 42 insertions(+), 52 deletions(-) diff --git a/packages/shared/data/api/schemas/models.ts b/packages/shared/data/api/schemas/models.ts index a9b4c525f23..81c44d233ce 100644 --- a/packages/shared/data/api/schemas/models.ts +++ b/packages/shared/data/api/schemas/models.ts @@ -77,10 +77,8 @@ const UpdateModelDtoSchema = CreateModelDtoSchema.omit({ }) export type UpdateModelDto = z.infer -/** DTO for resolving raw model entries against catalog presets */ -const ResolveModelsDtoSchema = z.object({ - /** Provider ID */ - providerId: z.string(), +/** DTO for enriching raw model entries against registry presets */ +const EnrichModelsDtoSchema = z.object({ /** Raw model entries from SDK */ models: z.array( z.object({ @@ -92,7 +90,7 @@ const ResolveModelsDtoSchema = z.object({ }) ) }) -export type ResolveModelsDto = z.infer +export type EnrichModelsDto = z.infer /** * Model API Schema definitions @@ -116,18 +114,6 @@ export interface ModelSchemas { } } - /** - * Resolve raw SDK model entries against catalog presets - * Returns enriched Model[] with capabilities, pricing, etc. from catalog - * @example POST /models/resolve { "providerId": "openai", "models": [{ "modelId": "gpt-4o" }] } - */ - '/models/resolve': { - POST: { - body: ResolveModelsDto - response: Model[] - } - } - /** * Individual model endpoint (keyed by providerId + modelId) * @example GET /models/openai/gpt-5 diff --git a/packages/shared/data/api/schemas/providers.ts b/packages/shared/data/api/schemas/providers.ts index 4ed955d8b1b..267e48c305a 100644 --- a/packages/shared/data/api/schemas/providers.ts +++ b/packages/shared/data/api/schemas/providers.ts @@ -15,6 +15,7 @@ import type { Provider, ProviderSettings } from '../../types/provider' +import type { EnrichModelsDto } from './models' export interface ListProvidersQuery { /** Filter by enabled status */ @@ -134,14 +135,23 @@ export interface ProviderSchemas { } /** - * Get all registry preset models for a provider (read-only, no DB writes) + * Registry models for a provider + * GET: Get all registry preset models (read-only, no DB writes) + * POST: Enrich raw SDK model entries against registry presets * @example GET /providers/openai/registry-models + * @example POST /providers/openai/registry-models { "models": [{ "modelId": "gpt-4o" }] } */ '/providers/:providerId/registry-models': { GET: { params: { providerId: string } response: Model[] } + /** Enrich raw model entries with registry capabilities, pricing, etc. */ + POST: { + params: { providerId: string } + body: EnrichModelsDto + response: Model[] + } } /** diff --git a/src/main/core/application/serviceRegistry.ts b/src/main/core/application/serviceRegistry.ts index 9134b3e70d0..5246a97db9c 100644 --- a/src/main/core/application/serviceRegistry.ts +++ b/src/main/core/application/serviceRegistry.ts @@ -2,6 +2,7 @@ import { CacheService } from '@data/CacheService' import { DataApiService } from '@data/DataApiService' import { DbService } from '@data/db/DbService' import { PreferenceService } from '@data/PreferenceService' +import { ProviderRegistryService } from '@data/services/ProviderRegistryService' import { AgentBootstrapService } from '@main/services/AgentBootstrapService' import { AnalyticsService } from '@main/services/AnalyticsService' import { ApiServerService } from '@main/services/ApiServerService' @@ -76,6 +77,7 @@ export const services = { MCPService, OpenClawService, SearchService, + ProviderRegistryService, AgentBootstrapService, ApiServerService, AppUpdaterService diff --git a/src/main/data/api/handlers/models.ts b/src/main/data/api/handlers/models.ts index ed845cbf79c..a547437d59a 100644 --- a/src/main/data/api/handlers/models.ts +++ b/src/main/data/api/handlers/models.ts @@ -7,7 +7,6 @@ */ import { modelService } from '@data/services/ModelService' -import { registryService } from '@data/services/ProviderRegistryService' import type { ApiHandler, ApiMethods } from '@shared/data/api/apiTypes' import type { ModelSchemas } from '@shared/data/api/schemas/models' @@ -34,12 +33,6 @@ export const modelHandlers: { } }, - '/models/resolve': { - POST: async ({ body }) => { - return await registryService.resolveModels(body.providerId, body.models) - } - }, - '/models/:providerId/:modelId': { GET: async ({ params }) => { return await modelService.getByKey(params.providerId, params.modelId) diff --git a/src/main/data/api/handlers/providers.ts b/src/main/data/api/handlers/providers.ts index b7164dc6621..3fff96d5239 100644 --- a/src/main/data/api/handlers/providers.ts +++ b/src/main/data/api/handlers/providers.ts @@ -10,8 +10,8 @@ */ import { userProviderInsertSchema } from '@data/db/schemas/userProvider' -import { registryService } from '@data/services/ProviderRegistryService' import { providerService } from '@data/services/ProviderService' +import { application } from '@main/core/application' import type { ApiHandler, ApiMethods } from '@shared/data/api/apiTypes' import type { CreateProviderDto, UpdateProviderDto } from '@shared/data/api/schemas/providers' import type { ProviderSchemas } from '@shared/data/api/schemas/providers' @@ -86,7 +86,11 @@ export const providerHandlers: { '/providers/:providerId/registry-models': { GET: async ({ params }) => { - return registryService.getRegistryModelsByProvider(params.providerId) + return application.get('ProviderRegistryService').getRegistryModelsByProvider(params.providerId) + }, + + POST: async ({ params, body }) => { + return await application.get('ProviderRegistryService').resolveModels(params.providerId, body.models) } }, diff --git a/src/main/data/services/ModelService.ts b/src/main/data/services/ModelService.ts index 3d92171958f..d4f8e70c5b8 100644 --- a/src/main/data/services/ModelService.ts +++ b/src/main/data/services/ModelService.ts @@ -18,8 +18,6 @@ import { createUniqueModelId } from '@shared/data/types/model' import { mergeModelConfig } from '@shared/data/utils/modelMerger' import { and, eq, inArray, type SQL } from 'drizzle-orm' -import { registryService } from './ProviderRegistryService' - const logger = loggerService.withContext('DataApi:ModelService') /** @@ -125,8 +123,9 @@ export class ModelService { const db = application.get('DbService').getDb() // Look up registry data for auto-enrichment - const { presetModel, registryOverride, reasoningFormatTypes, defaultChatEndpoint } = - await registryService.lookupModel(dto.providerId, dto.modelId) + const { presetModel, registryOverride, reasoningFormatTypes, defaultChatEndpoint } = await application + .get('ProviderRegistryService') + .lookupModel(dto.providerId, dto.modelId) let values: NewUserModel diff --git a/src/main/data/services/ProviderRegistryService.ts b/src/main/data/services/ProviderRegistryService.ts index aa15363b2a3..d56e6a0c152 100644 --- a/src/main/data/services/ProviderRegistryService.ts +++ b/src/main/data/services/ProviderRegistryService.ts @@ -6,7 +6,7 @@ * - Merging configurations using mergeModelConfig/mergeProviderConfig * - Writing resolved data to user_model / user_provider tables * - * Called during app initialization or when a user adds a preset provider. + * Managed by the lifecycle system. Seeds preset data during onInit. */ import { join } from 'node:path' @@ -25,6 +25,8 @@ import { userProviderTable } from '@data/db/schemas/userProvider' import { loggerService } from '@logger' import { isDev } from '@main/constant' import { application } from '@main/core/application' +import { BaseService, DependsOn, Injectable, ServicePhase } from '@main/core/lifecycle' +import { Phase } from '@main/core/lifecycle' import type { Model } from '@shared/data/types/model' import type { EndpointConfig, ReasoningFormatType } from '@shared/data/types/provider' import { extractReasoningFormatTypes, mergeModelConfig } from '@shared/data/utils/modelMerger' @@ -33,7 +35,7 @@ import { eq, isNotNull } from 'drizzle-orm' import { modelService } from './ModelService' import { providerService } from './ProviderService' -const logger = loggerService.withContext('DataApi:RegistryService') +const logger = loggerService.withContext('DataApi:ProviderRegistryService') /** Map proto ProviderReasoningFormat oneof case to runtime type string */ const CASE_TO_TYPE: Record = { @@ -89,20 +91,23 @@ function buildEndpointConfigsFromProto(p: ProtoProviderConfig): Partial 0 ? configs : null } -export class RegistryService { - private static instance: RegistryService - +@Injectable('ProviderRegistryService') +@ServicePhase(Phase.BeforeReady) +@DependsOn(['DbService']) +export class ProviderRegistryService extends BaseService { private registryModels: ProtoModelConfig[] | null = null private registryProviderModels: ProtoProviderModelOverride[] | null = null private registryProviders: ProtoProviderConfig[] | null = null - private constructor() {} + protected async onInit(): Promise { + // Sync preset registry data on every startup so that provider websites/baseUrls + // and model capabilities/modalities/contextWindow/pricing stay up-to-date + // when the registry protobuf files are updated between app versions. + await this.initializeAllPresetProviders() + } - public static getInstance(): RegistryService { - if (!RegistryService.instance) { - RegistryService.instance = new RegistryService() - } - return RegistryService.instance + protected onDestroy(): void { + this.clearCache() } /** @@ -406,10 +411,9 @@ export class RegistryService { /** * Initialize all preset providers from registry * - * Called during app startup and after migration to seed the database with registry data. * Seeds provider configurations and enriches existing user models with registry data. */ - async initializeAllPresetProviders(): Promise { + private async initializeAllPresetProviders(): Promise { await this.initializePresetProviders() await this.enrichExistingModels() @@ -667,5 +671,3 @@ export class RegistryService { this.registryProviders = null } } - -export const registryService = RegistryService.getInstance() diff --git a/src/main/index.ts b/src/main/index.ts index c4819161c26..c417a885e47 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -255,12 +255,6 @@ const startApp = async () => { // (DbService, PreferenceService, CacheService, DataApiService are now ready) await bootstrapPromise - // Sync preset registry data on every startup so that provider websites/baseUrls - // and model capabilities/modalities/contextWindow/pricing stay up-to-date - // when the registry protobuf files are updated between app versions. - const { registryService } = await import('@data/services/ProviderRegistryService') - await registryService.initializeAllPresetProviders() - // Record current version for tracking // A preparation for v2 data refactoring versionService.recordCurrentVersion() From 582c5947a6a5a7bd2c771fc5146983e4922c96a9 Mon Sep 17 00:00:00 2001 From: jidan745le <420511176@qq.com> Date: Tue, 7 Apr 2026 13:05:40 +0800 Subject: [PATCH 21/29] refactor(renderer): adapt provider settings to endpointConfigs data model MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Migrate renderer consumer code to match the new consolidated endpointConfigs schema (replaces separate baseUrls, modelsApiUrls, reasoningFormatTypes fields) and catalog→registry rename. - Replace provider.baseUrls?.[ep] with endpointConfigs?.[ep]?.baseUrl in ProviderSetting, CherryINSettings, DMXAPISettings, ManageModelsPopup - Rename replaceBaseUrlDomain → replaceEndpointConfigDomain (preserves other EndpointConfig fields like reasoningFormatType) - Update v1ProviderShim to read baseUrl from endpointConfigs - Rename useProviderCatalogModels → useProviderRegistryModels, update endpoint from catalog-models to registry-models - Replace removed /models/resolve with /providers/:id/registry-models POST - Update tests to reflect all of the above Signed-off-by: jidan745le <420511176@qq.com> --- .../data/hooks/__tests__/useProviders.test.ts | 12 ++--- src/renderer/src/data/hooks/useProviders.ts | 4 +- .../ProviderSettings/CherryINSettings.tsx | 15 ++++--- .../ProviderSettings/DMXAPISettings.tsx | 13 +++--- .../ModelList/ManageModelsPopup.tsx | 12 +++-- .../ProviderSettings/ProviderSetting.tsx | 40 +++++++++++++---- .../src/utils/__tests__/provider.v2.test.ts | 44 ++++++++++++------- src/renderer/src/utils/provider.v2.ts | 32 ++++++++------ src/renderer/src/utils/v1ProviderShim.ts | 4 +- 9 files changed, 108 insertions(+), 68 deletions(-) diff --git a/src/renderer/src/data/hooks/__tests__/useProviders.test.ts b/src/renderer/src/data/hooks/__tests__/useProviders.test.ts index 5b0bc035ca7..1cd348180f5 100644 --- a/src/renderer/src/data/hooks/__tests__/useProviders.test.ts +++ b/src/renderer/src/data/hooks/__tests__/useProviders.test.ts @@ -7,8 +7,8 @@ import { useProvider, useProviderApiKeys, useProviderAuthConfig, - useProviderCatalogModels, useProviderMutations, + useProviderRegistryModels, useProviders } from '../useProviders' @@ -363,15 +363,15 @@ describe('useProviderApiKeys', () => { }) }) -describe('useProviderCatalogModels', () => { +describe('useProviderRegistryModels', () => { beforeEach(() => { vi.clearAllMocks() }) - it('should query catalog models for a provider', () => { + it('should query registry models for a provider', () => { const mockModels = [{ id: 'gpt-4o', name: 'GPT-4o', providerId: 'openai' }] mockUseQuery.mockImplementation((path: string) => ({ - data: path.includes('catalog-models') ? mockModels : undefined, + data: path.includes('registry-models') ? mockModels : undefined, isLoading: false, isRefreshing: false, error: undefined, @@ -379,10 +379,10 @@ describe('useProviderCatalogModels', () => { mutate: vi.fn() })) - const { result } = renderHook(() => useProviderCatalogModels('openai')) + const { result } = renderHook(() => useProviderRegistryModels('openai')) expect(result.current.data).toEqual(mockModels) expect(result.current.isLoading).toBe(false) - expect(mockUseQuery).toHaveBeenCalledWith('/providers/openai/catalog-models') + expect(mockUseQuery).toHaveBeenCalledWith('/providers/openai/registry-models') }) }) diff --git a/src/renderer/src/data/hooks/useProviders.ts b/src/renderer/src/data/hooks/useProviders.ts index 277e2ab288a..3f110a6caaa 100644 --- a/src/renderer/src/data/hooks/useProviders.ts +++ b/src/renderer/src/data/hooks/useProviders.ts @@ -129,8 +129,8 @@ export function useProviderApiKeys(providerId: string) { } } -export function useProviderCatalogModels(providerId: string) { - return useQuery(`/providers/${providerId}/catalog-models` as const) as { +export function useProviderRegistryModels(providerId: string) { + return useQuery(`/providers/${providerId}/registry-models` as const) as { data: Model[] | undefined isLoading: boolean [k: string]: any diff --git a/src/renderer/src/pages/settings/ProviderSettings/CherryINSettings.tsx b/src/renderer/src/pages/settings/ProviderSettings/CherryINSettings.tsx index e4d51895ff9..ebc35dea8d2 100644 --- a/src/renderer/src/pages/settings/ProviderSettings/CherryINSettings.tsx +++ b/src/renderer/src/pages/settings/ProviderSettings/CherryINSettings.tsx @@ -1,5 +1,5 @@ import { useProvider } from '@renderer/data/hooks/useProviders' -import { replaceBaseUrlDomain } from '@renderer/utils/provider.v2' +import { replaceEndpointConfigDomain } from '@renderer/utils/provider.v2' import { Select } from 'antd' import type { FC } from 'react' import { useCallback, useMemo } from 'react' @@ -32,15 +32,16 @@ const CherryINSettings: FC = ({ providerId }) => { const { t } = useTranslation() const currentDomain = useMemo(() => { - if (!provider?.baseUrls) return API_HOST_OPTIONS[0].value - const firstUrl = Object.values(provider.baseUrls)[0] + if (!provider?.endpointConfigs) return API_HOST_OPTIONS[0].value + const firstConfig = Object.values(provider.endpointConfigs)[0] + const firstUrl = firstConfig?.baseUrl if (!firstUrl) return API_HOST_OPTIONS[0].value try { return new URL(firstUrl).hostname } catch { return API_HOST_OPTIONS[0].value } - }, [provider?.baseUrls]) + }, [provider?.endpointConfigs]) const getCurrentHost = useMemo(() => { const matched = API_HOST_OPTIONS.find((option) => currentDomain.includes(option.value)) @@ -49,10 +50,10 @@ const CherryINSettings: FC = ({ providerId }) => { const handleHostChange = useCallback( async (value: string) => { - const newBaseUrls = replaceBaseUrlDomain(provider?.baseUrls, value) - await updateProvider({ baseUrls: newBaseUrls }) + const newEndpointConfigs = replaceEndpointConfigDomain(provider?.endpointConfigs, value) + await updateProvider({ endpointConfigs: newEndpointConfigs }) }, - [provider?.baseUrls, updateProvider] + [provider?.endpointConfigs, updateProvider] ) const options = useMemo( diff --git a/src/renderer/src/pages/settings/ProviderSettings/DMXAPISettings.tsx b/src/renderer/src/pages/settings/ProviderSettings/DMXAPISettings.tsx index 7928d248d89..7e884b6c33a 100644 --- a/src/renderer/src/pages/settings/ProviderSettings/DMXAPISettings.tsx +++ b/src/renderer/src/pages/settings/ProviderSettings/DMXAPISettings.tsx @@ -1,6 +1,6 @@ import { Dmxapi } from '@cherrystudio/ui/icons' import { useProvider } from '@renderer/data/hooks/useProviders' -import { replaceBaseUrlDomain } from '@renderer/utils/provider.v2' +import { replaceEndpointConfigDomain } from '@renderer/utils/provider.v2' import type { RadioChangeEvent } from 'antd' import { Radio, Space } from 'antd' import type { FC } from 'react' @@ -44,8 +44,9 @@ const DMXAPISettings: FC = ({ providerId }) => { ] const getCurrentPlatform = (): PlatformDomain => { - if (!provider?.baseUrls) return PlatformDomain.OFFICIAL - const firstUrl = Object.values(provider.baseUrls)[0] + if (!provider?.endpointConfigs) return PlatformDomain.OFFICIAL + const firstConfig = Object.values(provider.endpointConfigs)[0] + const firstUrl = firstConfig?.baseUrl if (!firstUrl) return PlatformDomain.OFFICIAL if (firstUrl.includes('DMXAPI.com') || firstUrl.includes('dmxapi.com')) { return firstUrl.includes('ssvip') ? PlatformDomain.OVERSEA : PlatformDomain.INTERNATIONAL @@ -59,10 +60,10 @@ const DMXAPISettings: FC = ({ providerId }) => { async (e: RadioChangeEvent) => { const domain = e.target.value as PlatformDomain setSelectedPlatform(domain) - const newBaseUrls = replaceBaseUrlDomain(provider?.baseUrls, domain) - await updateProvider({ baseUrls: newBaseUrls }) + const newEndpointConfigs = replaceEndpointConfigDomain(provider?.endpointConfigs, domain) + await updateProvider({ endpointConfigs: newEndpointConfigs }) }, - [provider?.baseUrls, updateProvider] + [provider?.endpointConfigs, updateProvider] ) return ( diff --git a/src/renderer/src/pages/settings/ProviderSettings/ModelList/ManageModelsPopup.tsx b/src/renderer/src/pages/settings/ProviderSettings/ModelList/ManageModelsPopup.tsx index 78d0328c23c..1084e88668e 100644 --- a/src/renderer/src/pages/settings/ProviderSettings/ModelList/ManageModelsPopup.tsx +++ b/src/renderer/src/pages/settings/ProviderSettings/ModelList/ManageModelsPopup.tsx @@ -1,7 +1,7 @@ import { Button, Flex, RowFlex, Tooltip } from '@cherrystudio/ui' import { dataApiService } from '@data/DataApiService' import { useModelMutations, useModels } from '@data/hooks/useModels' -import { useProvider, useProviderCatalogModels } from '@data/hooks/useProviders' +import { useProvider, useProviderRegistryModels } from '@data/hooks/useProviders' import { loggerService } from '@logger' import { LoadingIcon } from '@renderer/components/Icons' import { TopView } from '@renderer/components/TopView' @@ -49,7 +49,7 @@ const PopupContainer: React.FC = ({ providerId, resolve }) => { const [open, setOpen] = useState(true) const { provider } = useProvider(providerId) const { models: existingModels } = useModels({ providerId }) - const { data: catalogModels = [] } = useProviderCatalogModels(providerId) + const { data: catalogModels = [] } = useProviderRegistryModels(providerId) const { createModel, deleteModel } = useModelMutations() const existingModelIds = useMemo(() => new Set(existingModels.map((m) => m.id)), [existingModels]) const [listModels, setListModels] = useState([]) @@ -200,9 +200,8 @@ const PopupContainer: React.FC = ({ providerId, resolve }) => { setLoadingModels(true) try { // Bridge v2 Provider → v1 shape for fetchModels (reads apiHost/apiKey) - // defaultChatEndpoint comes as "1.0" (float string), baseUrls keys are "1" (int string) const endpointKey = Math.floor(Number(prov.defaultChatEndpoint ?? 1)) - const apiHost = prov.baseUrls?.[endpointKey] ?? '' + const apiHost = prov.endpointConfigs?.[endpointKey]?.baseUrl ?? '' // Fetch key imperatively — useQuery may not have resolved yet at mount time let apiKey = '' try { @@ -218,11 +217,10 @@ const PopupContainer: React.FC = ({ providerId, resolve }) => { } const fetched = await fetchModels(v1Shim as any) const filteredModels = fetched.filter((model: any) => !isEmpty(model.name)) - // Resolve fetched models against catalog via POST /models/resolve + // Enrich fetched models against registry via POST /providers/:providerId/registry-models try { - const resolved = await dataApiService.post('/models/resolve' as const, { + const resolved = await dataApiService.post(`/providers/${providerId}/registry-models` as const, { body: { - providerId, models: filteredModels.map((m: any) => ({ modelId: m.id, name: m.name, diff --git a/src/renderer/src/pages/settings/ProviderSettings/ProviderSetting.tsx b/src/renderer/src/pages/settings/ProviderSettings/ProviderSetting.tsx index f9142ac9e07..321bc307279 100644 --- a/src/renderer/src/pages/settings/ProviderSettings/ProviderSetting.tsx +++ b/src/renderer/src/pages/settings/ProviderSettings/ProviderSetting.tsx @@ -128,8 +128,8 @@ const ProviderSettingContent: FC = ({ provider, providerId, isOnbo // Derive v1-like fields from v2 Provider const primaryEndpoint = provider.defaultChatEndpoint ?? EndpointType.OPENAI_CHAT_COMPLETIONS - const providerApiHost = provider.baseUrls?.[primaryEndpoint] ?? '' - const providerAnthropicHost = provider.baseUrls?.[EndpointType.ANTHROPIC_MESSAGES] + const providerApiHost = provider.endpointConfigs?.[primaryEndpoint]?.baseUrl ?? '' + const providerAnthropicHost = provider.endpointConfigs?.[EndpointType.ANTHROPIC_MESSAGES]?.baseUrl const providerApiVersion = provider.settings?.apiVersion ?? '' const providerApiKey = apiKeysData?.keys?.map((k) => k.key).join(',') ?? '' @@ -231,7 +231,12 @@ const ProviderSettingContent: FC = ({ provider, providerId, isOnbo return } if (isVertexProvider(provider) || apiHost.trim()) { - patchProvider({ baseUrls: { ...provider.baseUrls, [primaryEndpoint]: apiHost } }) + patchProvider({ + endpointConfigs: { + ...provider.endpointConfigs, + [primaryEndpoint]: { ...provider.endpointConfigs?.[primaryEndpoint], baseUrl: apiHost } + } + }) } else { setApiHost(providerApiHost) } @@ -242,12 +247,18 @@ const ProviderSettingContent: FC = ({ provider, providerId, isOnbo if (trimmedHost) { patchProvider({ - baseUrls: { ...provider.baseUrls, [EndpointType.ANTHROPIC_MESSAGES]: trimmedHost } + endpointConfigs: { + ...provider.endpointConfigs, + [EndpointType.ANTHROPIC_MESSAGES]: { + ...provider.endpointConfigs?.[EndpointType.ANTHROPIC_MESSAGES], + baseUrl: trimmedHost + } + } }) setAnthropicHost(trimmedHost) } else { - const { [EndpointType.ANTHROPIC_MESSAGES]: _, ...restUrls } = provider.baseUrls ?? {} - patchProvider({ baseUrls: restUrls }) + const { [EndpointType.ANTHROPIC_MESSAGES]: _, ...restConfigs } = provider.endpointConfigs ?? {} + patchProvider({ endpointConfigs: restConfigs }) setAnthropicHost(undefined) } } @@ -338,8 +349,13 @@ const ProviderSettingContent: FC = ({ provider, providerId, isOnbo const onReset = useCallback(() => { setApiHost(configuredApiHost) - patchProvider({ baseUrls: { ...provider?.baseUrls, [primaryEndpoint]: configuredApiHost } }) - }, [configuredApiHost, patchProvider, provider?.baseUrls, primaryEndpoint]) + patchProvider({ + endpointConfigs: { + ...provider?.endpointConfigs, + [primaryEndpoint]: { ...provider?.endpointConfigs?.[primaryEndpoint], baseUrl: configuredApiHost } + } + }) + }, [configuredApiHost, patchProvider, provider?.endpointConfigs, primaryEndpoint]) const isApiHostResettable = useMemo(() => { return !isEmpty(configuredApiHost) && apiHost !== configuredApiHost @@ -483,7 +499,13 @@ const ProviderSettingContent: FC = ({ provider, providerId, isOnbo checked={provider.isEnabled} key={provider.id} onCheckedChange={(enabled) => { - patchProvider({ isEnabled: enabled, baseUrls: { ...provider.baseUrls, [primaryEndpoint]: apiHost } }) + patchProvider({ + isEnabled: enabled, + endpointConfigs: { + ...provider.endpointConfigs, + [primaryEndpoint]: { ...provider.endpointConfigs?.[primaryEndpoint], baseUrl: apiHost } + } + }) if (enabled) { moveProviderToTop() } diff --git a/src/renderer/src/utils/__tests__/provider.v2.test.ts b/src/renderer/src/utils/__tests__/provider.v2.test.ts index 7dfb4484788..df4ee4e405a 100644 --- a/src/renderer/src/utils/__tests__/provider.v2.test.ts +++ b/src/renderer/src/utils/__tests__/provider.v2.test.ts @@ -26,7 +26,7 @@ import { isSystemProvider, isVertexProvider, matchKeywordsInProvider, - replaceBaseUrlDomain + replaceEndpointConfigDomain } from '../provider.v2' /** Helper to create a minimal v2 Provider for testing */ @@ -147,10 +147,10 @@ describe('provider.v2 - Composite identity checks', () => { expect(isAnthropicSupportedProvider(p)).toBe(true) }) - it('isAnthropicSupportedProvider: true when baseUrls has ANTHROPIC_MESSAGES key', () => { + it('isAnthropicSupportedProvider: true when endpointConfigs has ANTHROPIC_MESSAGES baseUrl', () => { const p = makeProvider({ defaultChatEndpoint: EndpointType.OPENAI_CHAT_COMPLETIONS, - baseUrls: { [EndpointType.ANTHROPIC_MESSAGES]: 'https://api.example.com' } + endpointConfigs: { [EndpointType.ANTHROPIC_MESSAGES]: { baseUrl: 'https://api.example.com' } } }) expect(isAnthropicSupportedProvider(p)).toBe(true) }) @@ -264,25 +264,37 @@ describe('provider.v2 - API Key helpers', () => { }) }) -describe('provider.v2 - Base URL helpers', () => { - it('replaceBaseUrlDomain: replaces domain in all URLs while preserving paths', () => { - const result = replaceBaseUrlDomain({ 1: 'https://old.com/v1', 3: 'https://old.com/anthropic' }, 'new.com') - expect(result[1]).toBe('https://new.com/v1') - expect(result[3]).toBe('https://new.com/anthropic') +describe('provider.v2 - Endpoint config helpers', () => { + it('replaceEndpointConfigDomain: replaces domain in all baseUrls while preserving paths', () => { + const result = replaceEndpointConfigDomain( + { 1: { baseUrl: 'https://old.com/v1' }, 3: { baseUrl: 'https://old.com/anthropic' } }, + 'new.com' + ) + expect(result[1]?.baseUrl).toBe('https://new.com/v1') + expect(result[3]?.baseUrl).toBe('https://new.com/anthropic') + }) + + it('replaceEndpointConfigDomain: returns empty object for undefined input', () => { + expect(replaceEndpointConfigDomain(undefined, 'new.com')).toEqual({}) }) - it('replaceBaseUrlDomain: returns empty object for undefined input', () => { - expect(replaceBaseUrlDomain(undefined, 'new.com')).toEqual({}) + it('replaceEndpointConfigDomain: preserves invalid URLs unchanged', () => { + const result = replaceEndpointConfigDomain({ 1: { baseUrl: 'not-a-url' } }, 'new.com') + expect(result[1]?.baseUrl).toBe('not-a-url') }) - it('replaceBaseUrlDomain: preserves invalid URLs unchanged', () => { - const result = replaceBaseUrlDomain({ 1: 'not-a-url' }, 'new.com') - expect(result[1]).toBe('not-a-url') + it('replaceEndpointConfigDomain: handles URLs with ports and paths', () => { + const result = replaceEndpointConfigDomain({ 6: { baseUrl: 'http://localhost:11434/api' } }, '192.168.1.100') + expect(result[6]?.baseUrl).toBe('http://192.168.1.100:11434/api') }) - it('replaceBaseUrlDomain: handles URLs with ports and paths', () => { - const result = replaceBaseUrlDomain({ 6: 'http://localhost:11434/api' }, '192.168.1.100') - expect(result[6]).toBe('http://192.168.1.100:11434/api') + it('replaceEndpointConfigDomain: preserves other EndpointConfig fields', () => { + const result = replaceEndpointConfigDomain( + { 1: { baseUrl: 'https://old.com/v1', reasoningFormatType: 'openai-chat' } }, + 'new.com' + ) + expect(result[1]?.baseUrl).toBe('https://new.com/v1') + expect(result[1]?.reasoningFormatType).toBe('openai-chat') }) }) diff --git a/src/renderer/src/utils/provider.v2.ts b/src/renderer/src/utils/provider.v2.ts index be29efbdcd9..4fae4572008 100644 --- a/src/renderer/src/utils/provider.v2.ts +++ b/src/renderer/src/utils/provider.v2.ts @@ -1,6 +1,6 @@ import { getProviderLabel } from '@renderer/i18n/label' import { EndpointType } from '@shared/data/types/model' -import type { Provider } from '@shared/data/types/provider' +import type { EndpointConfig, Provider } from '@shared/data/types/provider' // ─── Protocol-level: check defaultChatEndpoint ─────────────────────────────── @@ -70,7 +70,7 @@ export function isOpenAICompatibleProvider(provider: Provider): boolean { export function isAnthropicSupportedProvider(provider: Provider): boolean { return ( provider.defaultChatEndpoint === EndpointType.ANTHROPIC_MESSAGES || - provider.baseUrls?.[EndpointType.ANTHROPIC_MESSAGES] != null + provider.endpointConfigs?.[EndpointType.ANTHROPIC_MESSAGES]?.baseUrl != null ) } @@ -129,23 +129,29 @@ export function hasApiKeys(provider: Provider): boolean { // ─── Base URL helpers ─────────────────────────────────────────────────────── /** - * Replace the domain (host) in all baseUrls while preserving URL paths. + * Replace the domain (host) in all endpointConfigs baseUrls while preserving URL paths + * and other EndpointConfig fields (reasoningFormatType, modelsApiUrls). * Used by CherryIN/DMXAPI domain switching. */ -export function replaceBaseUrlDomain( - baseUrls: Partial> | undefined, +export function replaceEndpointConfigDomain( + endpointConfigs: Partial> | undefined, newDomain: string -): Partial> { - if (!baseUrls) return {} - const result: Partial> = {} - for (const [key, url] of Object.entries(baseUrls)) { - if (!url) continue +): Partial> { + if (!endpointConfigs) return {} + const result: Partial> = {} + for (const [key, config] of Object.entries(endpointConfigs)) { + if (!config) continue + const baseUrl = config.baseUrl + if (!baseUrl) { + result[Number(key)] = config + continue + } try { - const parsed = new URL(url) + const parsed = new URL(baseUrl) parsed.hostname = newDomain - result[Number(key)] = parsed.toString().replace(/\/$/, '') + result[Number(key)] = { ...config, baseUrl: parsed.toString().replace(/\/$/, '') } } catch { - result[Number(key)] = url + result[Number(key)] = config } } return result diff --git a/src/renderer/src/utils/v1ProviderShim.ts b/src/renderer/src/utils/v1ProviderShim.ts index 7e82b371d4d..84ec260848c 100644 --- a/src/renderer/src/utils/v1ProviderShim.ts +++ b/src/renderer/src/utils/v1ProviderShim.ts @@ -16,7 +16,7 @@ export interface V1ShimOptions { function defaultChatBaseUrl(v2: V2Provider): string { const ep = v2.defaultChatEndpoint ?? EndpointType.OPENAI_CHAT_COMPLETIONS - return v2.baseUrls?.[ep] ?? '' + return v2.endpointConfigs?.[ep]?.baseUrl ?? '' } function v1ProviderTypeFromV2(v2: V2Provider): ProviderType { @@ -93,7 +93,7 @@ export function toV1ProviderShim(v2Provider: V2Provider, options: V1ShimOptions type: v1ProviderTypeFromV2(v2Provider), apiKey: options.apiKey ?? '', apiHost: options.apiHost ?? defaultChatBaseUrl(v2Provider), - anthropicApiHost: v2Provider.baseUrls?.[EndpointType.ANTHROPIC_MESSAGES], + anthropicApiHost: v2Provider.endpointConfigs?.[EndpointType.ANTHROPIC_MESSAGES]?.baseUrl, models: (options.models ?? []) as unknown as V1Model[], enabled: v2Provider.isEnabled, isSystem: v2Provider.presetProviderId != null, From 48af98a34b23c4fb9931402943e612defe374463 Mon Sep 17 00:00:00 2001 From: suyao Date: Tue, 7 Apr 2026 15:29:02 +0800 Subject: [PATCH 22/29] refactor: replace protobuf with JSON and convert numeric enums to strings - Remove protobuf toolchain (proto files, buf config, generated code, @bufbuild/* dependencies) - Convert registry data from .pb binary to .json files - Replace proto-generated types with Zod-inferred types - Rewrite registry-reader to use JSON.parse + Zod validation - Simplify ProviderRegistryService and modelMerger by removing proto conversion layers (buildEndpointConfigsFromProto, CASE_TO_TYPE, extractReasoningFormatType) - Convert all enums from numeric TypeScript enum to string-valued as-const objects (EndpointType, ModelCapability, Modality, Currency, ReasoningEffort) for debuggability - Regenerate Drizzle migration to match current schema Co-Authored-By: Claude Opus 4.6 Signed-off-by: suyao --- ..._naoko.sql => 0009_medical_giant_girl.sql} | 6 +- .../sqlite-drizzle/meta/0009_snapshot.json | 119 +- migrations/sqlite-drizzle/meta/_journal.json | 4 +- packages/provider-registry/data/models.json | 37631 ++++++++++++++++ packages/provider-registry/data/models.pb | Bin 792407 -> 0 bytes .../data/provider-models.json | 17244 +++++++ .../provider-registry/data/provider-models.pb | Bin 102905 -> 0 bytes .../provider-registry/data/providers.json | 391 + packages/provider-registry/data/providers.pb | Bin 8539 -> 0 bytes packages/provider-registry/package.json | 11 +- packages/provider-registry/proto/buf.gen.yaml | 7 - packages/provider-registry/proto/buf.yaml | 12 - .../provider-registry/proto/v1/common.proto | 111 - .../provider-registry/proto/v1/model.proto | 110 - .../provider-registry/proto/v1/provider.proto | 219 - .../proto/v1/provider_models.proto | 62 - .../provider-registry/src/gen/v1/common_pb.ts | 456 - .../provider-registry/src/gen/v1/model_pb.ts | 387 - .../src/gen/v1/provider_models_pb.ts | 203 - .../src/gen/v1/provider_pb.ts | 707 - packages/provider-registry/src/index.ts | 25 +- .../provider-registry/src/registry-reader.ts | 43 +- .../provider-registry/src/schemas/common.ts | 4 +- .../provider-registry/src/schemas/enums.ts | 154 +- .../provider-registry/src/schemas/model.ts | 8 +- .../provider-registry/src/schemas/provider.ts | 6 +- packages/shared/data/api/schemas/models.ts | 14 +- packages/shared/data/types/model.ts | 15 +- packages/shared/data/types/provider.ts | 4 +- packages/shared/data/utils/modelMerger.ts | 90 +- pnpm-lock.yaml | 140 - src/main/data/db/schemas/userModel.ts | 8 +- .../mappings/ProviderModelMappings.ts | 2 +- src/main/data/services/ModelService.ts | 31 +- .../data/services/ProviderRegistryService.ts | 199 +- 35 files changed, 55580 insertions(+), 2843 deletions(-) rename migrations/sqlite-drizzle/{0009_hard_naoko.sql => 0009_medical_giant_girl.sql} (93%) create mode 100644 packages/provider-registry/data/models.json delete mode 100644 packages/provider-registry/data/models.pb create mode 100644 packages/provider-registry/data/provider-models.json delete mode 100644 packages/provider-registry/data/provider-models.pb create mode 100644 packages/provider-registry/data/providers.json delete mode 100644 packages/provider-registry/data/providers.pb delete mode 100644 packages/provider-registry/proto/buf.gen.yaml delete mode 100644 packages/provider-registry/proto/buf.yaml delete mode 100644 packages/provider-registry/proto/v1/common.proto delete mode 100644 packages/provider-registry/proto/v1/model.proto delete mode 100644 packages/provider-registry/proto/v1/provider.proto delete mode 100644 packages/provider-registry/proto/v1/provider_models.proto delete mode 100644 packages/provider-registry/src/gen/v1/common_pb.ts delete mode 100644 packages/provider-registry/src/gen/v1/model_pb.ts delete mode 100644 packages/provider-registry/src/gen/v1/provider_models_pb.ts delete mode 100644 packages/provider-registry/src/gen/v1/provider_pb.ts diff --git a/migrations/sqlite-drizzle/0009_hard_naoko.sql b/migrations/sqlite-drizzle/0009_medical_giant_girl.sql similarity index 93% rename from migrations/sqlite-drizzle/0009_hard_naoko.sql rename to migrations/sqlite-drizzle/0009_medical_giant_girl.sql index 11d4f6d503c..8fbd5fb98f5 100644 --- a/migrations/sqlite-drizzle/0009_hard_naoko.sql +++ b/migrations/sqlite-drizzle/0009_medical_giant_girl.sql @@ -34,14 +34,12 @@ CREATE TABLE `user_provider` ( `provider_id` text PRIMARY KEY NOT NULL, `preset_provider_id` text, `name` text NOT NULL, - `base_urls` text, - `models_api_urls` text, + `endpoint_configs` text, `default_chat_endpoint` text, `api_keys` text DEFAULT '[]', `auth_config` text, `api_features` text, `provider_settings` text, - `reasoning_format_types` text, `websites` text, `is_enabled` integer DEFAULT true, `sort_order` integer DEFAULT 0, @@ -50,4 +48,4 @@ CREATE TABLE `user_provider` ( ); --> statement-breakpoint CREATE INDEX `user_provider_preset_idx` ON `user_provider` (`preset_provider_id`);--> statement-breakpoint -CREATE INDEX `user_provider_enabled_sort_idx` ON `user_provider` (`is_enabled`,`sort_order`); +CREATE INDEX `user_provider_enabled_sort_idx` ON `user_provider` (`is_enabled`,`sort_order`); \ No newline at end of file diff --git a/migrations/sqlite-drizzle/meta/0009_snapshot.json b/migrations/sqlite-drizzle/meta/0009_snapshot.json index 62dba627978..3a9ee620de1 100644 --- a/migrations/sqlite-drizzle/meta/0009_snapshot.json +++ b/migrations/sqlite-drizzle/meta/0009_snapshot.json @@ -1,6 +1,8 @@ { "version": "6", "dialect": "sqlite", + "id": "06b58aff-1332-4170-be6b-45a53d5e3cda", + "prevId": "4105c4f2-d9df-4048-acab-0049e5a95941", "tables": { "app_state": { "name": "app_state", @@ -967,28 +969,28 @@ "uniqueConstraints": {}, "checkConstraints": {} }, - "tag": { - "name": "tag", + "entity_tag": { + "name": "entity_tag", "columns": { - "id": { - "name": "id", + "entity_type": { + "name": "entity_type", "type": "text", - "primaryKey": true, + "primaryKey": false, "notNull": true, "autoincrement": false }, - "name": { - "name": "name", + "entity_id": { + "name": "entity_id", "type": "text", "primaryKey": false, "notNull": true, "autoincrement": false }, - "color": { - "name": "color", + "tag_id": { + "name": "tag_id", "type": "text", "primaryKey": false, - "notNull": false, + "notNull": true, "autoincrement": false }, "created_at": { @@ -1007,39 +1009,54 @@ } }, "indexes": { - "tag_name_unique": { - "name": "tag_name_unique", - "columns": ["name"], - "isUnique": true + "entity_tag_tag_id_idx": { + "name": "entity_tag_tag_id_idx", + "columns": ["tag_id"], + "isUnique": false + } + }, + "foreignKeys": { + "entity_tag_tag_id_tag_id_fk": { + "name": "entity_tag_tag_id_tag_id_fk", + "tableFrom": "entity_tag", + "tableTo": "tag", + "columnsFrom": ["tag_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "entity_tag_entity_type_entity_id_tag_id_pk": { + "columns": ["entity_type", "entity_id", "tag_id"], + "name": "entity_tag_entity_type_entity_id_tag_id_pk" } }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, "uniqueConstraints": {}, "checkConstraints": {} }, - "entity_tag": { - "name": "entity_tag", + "tag": { + "name": "tag", "columns": { - "entity_type": { - "name": "entity_type", + "id": { + "name": "id", "type": "text", - "primaryKey": false, + "primaryKey": true, "notNull": true, "autoincrement": false }, - "entity_id": { - "name": "entity_id", + "name": { + "name": "name", "type": "text", "primaryKey": false, "notNull": true, "autoincrement": false }, - "tag_id": { - "name": "tag_id", + "color": { + "name": "color", "type": "text", "primaryKey": false, - "notNull": true, + "notNull": false, "autoincrement": false }, "created_at": { @@ -1058,29 +1075,14 @@ } }, "indexes": { - "entity_tag_tag_id_idx": { - "name": "entity_tag_tag_id_idx", - "columns": ["tag_id"], - "isUnique": false - } - }, - "foreignKeys": { - "entity_tag_tag_id_tag_id_fk": { - "name": "entity_tag_tag_id_tag_id_fk", - "tableFrom": "entity_tag", - "tableTo": "tag", - "columnsFrom": ["tag_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "entity_tag_entity_type_entity_id_tag_id_pk": { - "columns": ["entity_type", "entity_id", "tag_id"], - "name": "entity_tag_entity_type_entity_id_tag_id_pk" + "tag_name_unique": { + "name": "tag_name_unique", + "columns": ["name"], + "isUnique": true } }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, "uniqueConstraints": {}, "checkConstraints": {} }, @@ -1608,15 +1610,8 @@ "notNull": true, "autoincrement": false }, - "base_urls": { - "name": "base_urls", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "models_api_urls": { - "name": "models_api_urls", + "endpoint_configs": { + "name": "endpoint_configs", "type": "text", "primaryKey": false, "notNull": false, @@ -1658,13 +1653,6 @@ "notNull": false, "autoincrement": false }, - "reasoning_format_types": { - "name": "reasoning_format_types", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, "websites": { "name": "websites", "type": "text", @@ -1724,12 +1712,11 @@ "views": {}, "enums": {}, "_meta": { + "schemas": {}, "tables": {}, "columns": {} }, "internal": { "indexes": {} - }, - "id": "34f2606c-1455-47e9-831a-0756e742177b", - "prevId": "4105c4f2-d9df-4048-acab-0049e5a95941" + } } diff --git a/migrations/sqlite-drizzle/meta/_journal.json b/migrations/sqlite-drizzle/meta/_journal.json index f1f506ba6af..7f852a14995 100644 --- a/migrations/sqlite-drizzle/meta/_journal.json +++ b/migrations/sqlite-drizzle/meta/_journal.json @@ -67,8 +67,8 @@ { "idx": 9, "version": "6", - "when": 1775384722093, - "tag": "0009_hard_naoko", + "when": 1775543209395, + "tag": "0009_medical_giant_girl", "breakpoints": true } ], diff --git a/packages/provider-registry/data/models.json b/packages/provider-registry/data/models.json new file mode 100644 index 00000000000..76b864bd8a3 --- /dev/null +++ b/packages/provider-registry/data/models.json @@ -0,0 +1,37631 @@ +{ + "version": "2026.03.09", + "models": [ + { + "id": "multilingual-e5-large-instruct", + "name": "E5 Multi-Lingual Large Embeddings 0.6B", + "capabilities": ["embedding"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 512, + "maxOutputTokens": 512, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.12 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.12 + } + }, + "family": "text-embedding", + "ownedBy": "evroc", + "openWeights": true + }, + { + "id": "kimi-k2-5", + "name": "Kimi K2.5", + "description": "Kimi K2.5 is Moonshot AI's native multimodal model, delivering state-of-the-art visual coding capability and a self-directed agent swarm paradigm. Built on Kimi K2 with continued pretraining over approximately 15T mixed visual and text tokens, it delivers strong performance in general reasoning, visual coding, and agentic tool-calling.", + "capabilities": ["reasoning", "function-call", "image-recognition", "video-recognition"], + "inputModalities": ["text", "image", "video"], + "outputModalities": ["text"], + "contextWindow": 262144, + "maxOutputTokens": 262144, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.47 + }, + "output": { + "currency": "USD", + "perMillionTokens": 5.9 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.1 + } + }, + "reasoning": { + "supportedEfforts": ["none", "auto"] + }, + "family": "kimi", + "ownedBy": "moonshot", + "openWeights": true + }, + { + "id": "llama-3-3-instruct-fp8", + "name": "Llama 3.3 70B", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 32768, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.18 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.18 + } + }, + "family": "llama", + "ownedBy": "meta", + "openWeights": true + }, + { + "id": "kb-whisper-large", + "name": "KB Whisper", + "capabilities": ["audio-recognition", "audio-transcript"], + "inputModalities": ["audio"], + "outputModalities": ["text"], + "contextWindow": 448, + "maxOutputTokens": 448, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.00236 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.00236 + } + }, + "family": "whisper", + "ownedBy": "evroc", + "openWeights": true + }, + { + "id": "phi-4-multimodal-instruct", + "name": "Phi-4 15B", + "description": "Microsoft's latest model", + "capabilities": ["function-call", "image-generation"], + "inputModalities": ["text"], + "outputModalities": ["text", "image"], + "contextWindow": 32000, + "maxOutputTokens": 32000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.24 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.47 + } + }, + "family": "phi", + "ownedBy": "microsoft", + "openWeights": true + }, + { + "id": "whisper-large-v3", + "name": "Whisper 3 Large", + "capabilities": ["audio-recognition", "audio-transcript"], + "inputModalities": ["audio"], + "outputModalities": ["text"], + "contextWindow": 448, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.00236 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.00236 + } + }, + "family": "whisper", + "ownedBy": "openai", + "openWeights": true + }, + { + "id": "gpt-oss", + "name": "GPT OSS 120B", + "description": "gpt-oss-120b is an open-weight, 117B-parameter Mixture-of-Experts (MoE) language model from OpenAI designed for high-reasoning, agentic, and general-purpose production use cases. It activates 5.1B parameters per forward pass and is optimized to run on a single H100 GPU with native MXFP4 quantization. The model supports configurable reasoning depth, full chain-of-thought access, and native tool use, including function calling, browsing, and structured output generation.", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 65536, + "maxOutputTokens": 65536, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.24 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.94 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.005 + } + }, + "reasoning": { + "supportedEfforts": ["low", "medium", "high"] + }, + "family": "gpt-oss", + "ownedBy": "openai", + "openWeights": true + }, + { + "id": "voxtral-small-2507", + "name": "Voxtral Small 24B", + "description": "Voxtral Small is an enhancement of Mistral Small 3, incorporating state-of-the-art audio input capabilities while retaining best-in-class text performance. It excels at speech transcription, translation and audio understanding. Input audio is priced at $100 per million seconds.", + "capabilities": ["audio-recognition"], + "inputModalities": ["audio", "text"], + "outputModalities": ["text"], + "contextWindow": 32000, + "maxOutputTokens": 32000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.00236 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.00236 + } + }, + "family": "voxtral", + "ownedBy": "mistral", + "openWeights": true + }, + { + "id": "magistral-small-2509", + "name": "Magistral Small 1.2 24B", + "capabilities": ["function-call", "reasoning"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 131072, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.59 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2.36 + } + }, + "family": "magistral-small", + "ownedBy": "mistral", + "openWeights": true + }, + { + "id": "devstral-small-2-instruct-2512", + "name": "Devstral Small 2 24B Instruct 2512", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 32768, + "maxOutputTokens": 32768, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.12 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.47 + } + }, + "family": "devstral", + "ownedBy": "mistral", + "openWeights": true + }, + { + "id": "qwen3-embedding", + "name": "Qwen3 Embedding 8B", + "description": "The Qwen3 Embedding model series is the latest proprietary model of the Qwen family, specifically designed for text embedding and ranking tasks. This series inherits the exceptional multilingual capabilities, long-text understanding, and reasoning skills of its foundational model. The Qwen3 Embedding series represents significant advancements in multiple text embedding and ranking tasks, including text retrieval, code retrieval, text classification, text clustering, and bitext mining.", + "capabilities": ["function-call", "embedding", "reasoning"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 40960, + "maxOutputTokens": 40960, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.12 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.12 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 38912 + } + }, + "family": "text-embedding", + "ownedBy": "alibaba", + "openWeights": true + }, + { + "id": "qwen3-vl-a3b-instruct", + "name": "Qwen3 VL 30B", + "description": "Qwen3-VL-30B-A3B-Instruct is a multimodal model that unifies strong text generation with visual understanding for images and videos. Its Instruct variant optimizes instruction-following for general multimodal tasks. It excels in perception of real-world/synthetic categories, 2D/3D spatial grounding, and long-form visual comprehension, achieving competitive multimodal benchmark results. For agentic use, it handles multi-image multi-turn instructions, video timeline alignments, GUI automation, and visual coding from sketches to debugged UI. Text performance matches flagship Qwen3 models, suiting document AI, OCR, UI assistance, spatial tasks, and agent research.", + "capabilities": ["function-call", "image-recognition", "video-recognition"], + "inputModalities": ["text", "image", "video"], + "outputModalities": ["text"], + "contextWindow": 100000, + "maxOutputTokens": 100000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.24 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.94 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": true + }, + { + "id": "qwen3-a3b-instruct-2507-fp8", + "name": "Qwen3 30B 2507", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 64000, + "maxOutputTokens": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.35 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.42 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": true + }, + { + "id": "gemma-3", + "name": "Gemma 3 27B", + "capabilities": ["function-call", "structured-output", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.15 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.3 + } + }, + "family": "gemma", + "ownedBy": "google", + "openWeights": true + }, + { + "id": "qwen3-coder-a3b", + "name": "Qwen3-Coder 30B-A3B", + "capabilities": ["function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 32768, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.07 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.27 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": true + }, + { + "id": "step-2-16k", + "name": "Step 2 (16K)", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 16384, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 5.21 + }, + "output": { + "currency": "USD", + "perMillionTokens": 16.44 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 1.04 + } + }, + "ownedBy": "stepfun", + "openWeights": false + }, + { + "id": "step-1-32k", + "name": "Step 1 (32K)", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 32768, + "maxOutputTokens": 32768, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2.05 + }, + "output": { + "currency": "USD", + "perMillionTokens": 9.59 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.41 + } + }, + "ownedBy": "stepfun", + "openWeights": false + }, + { + "id": "step-3-5-flash", + "name": "Step 3.5 Flash", + "description": "Step 3.5 Flash is StepFun's most capable open-source foundation model. Built on a sparse Mixture of Experts (MoE) architecture, it selectively activates only 11B of its 196B parameters per token. It is a reasoning model that is incredibly speed efficient even at long contexts.", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 256000, + "maxOutputTokens": 256000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.096 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.288 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.019 + } + }, + "family": "step", + "ownedBy": "stepfun", + "openWeights": true + }, + { + "id": "kimi-k2-0905-preview", + "name": "Kimi K2 0905", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 262144, + "maxOutputTokens": 262144, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.6 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2.5 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.15 + } + }, + "family": "kimi", + "ownedBy": "moonshot", + "openWeights": true + }, + { + "id": "kimi-k2", + "name": "Kimi K2 Thinking", + "description": "Kimi K2 Instruct is a large-scale Mixture-of-Experts (MoE) language model developed by Moonshot AI, featuring 1 trillion total parameters with 32 billion active per forward pass. It is optimized for agentic capabilities, including advanced tool use, reasoning, and code synthesis. Kimi K2 excels across a broad range of benchmarks, particularly in coding (LiveCodeBench, SWE-bench), reasoning (ZebraLogic, GPQA), and tool-use (Tau2, AceBench) tasks. It supports long-context inference up to 128K tokens and is designed with a novel training stack that includes the MuonClip optimizer for stable large-scale MoE training.", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 262144, + "maxOutputTokens": 262144, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.6 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2.5 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.15 + } + }, + "family": "kimi-thinking", + "ownedBy": "moonshot", + "openWeights": true + }, + { + "id": "kimi-k2-0711-preview", + "name": "Kimi K2 0711", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 16384, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.6 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2.5 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.15 + } + }, + "family": "kimi", + "ownedBy": "moonshot", + "openWeights": true + }, + { + "id": "kimi-k2-turbo-preview", + "name": "Kimi K2 Turbo", + "description": "The kimi-k2-turbo-preview model is a high-speed version of kimi-k2, with the same model parameters as kimi-k2, but the output speed has been increased from 10 tokens per second to 40 tokens per second.", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 262144, + "maxOutputTokens": 262144, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2.4 + }, + "output": { + "currency": "USD", + "perMillionTokens": 10 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.6 + } + }, + "family": "kimi", + "ownedBy": "moonshot", + "openWeights": true + }, + { + "id": "kimi-k2-thinking-turbo", + "name": "Kimi K2 Thinking Turbo", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 262144, + "maxOutputTokens": 262144, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.15 + }, + "output": { + "currency": "USD", + "perMillionTokens": 8 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.15 + } + }, + "reasoning": { + "supportedEfforts": [] + }, + "family": "kimi-thinking", + "ownedBy": "moonshot", + "openWeights": true + }, + { + "id": "gemini-3-flash-preview", + "name": "Gemini 3 Flash Preview", + "description": "Gemini 3 Flash Preview is a high speed, high value thinking model designed for agentic workflows, multi turn chat, and coding assistance. It delivers near Pro level reasoning and tool use performance with substantially lower latency than larger Gemini variants, making it well suited for interactive development, long running agent loops, and collaborative coding tasks. Compared to Gemini 2.5 Flash, it provides broad quality improvements across reasoning, multimodal understanding, and reliability.\n\nThe model supports a 1M token context window and multimodal inputs including text, images, audio, video, and PDFs, with text output. It includes configurable reasoning via thinking levels (minimal, low, medium, high), structured output, tool use, and automatic context caching. Gemini 3 Flash Preview is optimized for users who want strong reasoning and agentic behavior without the cost or latency of full scale frontier models.", + "capabilities": [ + "reasoning", + "function-call", + "structured-output", + "file-input", + "image-recognition", + "audio-recognition", + "video-recognition", + "web-search" + ], + "inputModalities": ["text", "image", "video", "audio"], + "outputModalities": ["text"], + "contextWindow": 1048576, + "maxOutputTokens": 65536, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 3 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.05 + } + }, + "reasoning": { + "supportedEfforts": ["minimal", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 0, + "max": 24576 + }, + "interleaved": true + }, + "family": "gemini-flash", + "ownedBy": "google", + "openWeights": false + }, + { + "id": "claude-haiku-4-5", + "name": "Claude Haiku 4.5", + "description": "Claude Haiku 4.5 is Anthropic’s fastest and most efficient model, delivering near-frontier intelligence at a fraction of the cost and latency of larger Claude models. Matching Claude Sonnet 4’s performance across reasoning, coding, and computer-use tasks, Haiku 4.5 brings frontier-level capability to real-time and high-volume applications.\n\nIt introduces extended thinking to the Haiku line; enabling controllable reasoning depth, summarized or interleaved thought output, and tool-assisted workflows with full support for coding, bash, web search, and computer-use tools. Scoring >73% on SWE-bench Verified, Haiku 4.5 ranks among the world’s best coding models while maintaining exceptional responsiveness for sub-agents, parallelized execution, and scaled deployment.", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition", "web-search", "computer-use"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1 + }, + "output": { + "currency": "USD", + "perMillionTokens": 5 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.1 + }, + "cacheWrite": { + "currency": "USD", + "perMillionTokens": 1.375 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 64000 + }, + "interleaved": true + }, + "family": "claude-haiku", + "ownedBy": "anthropic", + "openWeights": false + }, + { + "id": "claude-sonnet-4-6", + "name": "Claude Sonnet 4.6", + "description": "Sonnet 4.6 is Anthropic's most capable Sonnet-class model yet, with frontier performance across coding, agents, and professional work. It excels at iterative development, complex codebase navigation, end-to-end project management with memory, polished document creation, and confident computer use for web QA and workflow automation.", + "capabilities": ["function-call", "file-input", "image-recognition", "reasoning", "web-search", "computer-use"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 15 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.3 + }, + "cacheWrite": { + "currency": "USD", + "perMillionTokens": 3.75 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 64000 + }, + "interleaved": true + }, + "family": "claude-sonnet", + "ownedBy": "anthropic", + "openWeights": false + }, + { + "id": "claude-opus-4-6", + "name": "Claude Opus 4.6", + "description": "Opus 4.6 is Anthropic’s strongest model for coding and long-running professional tasks. It is built for agents that operate across entire workflows rather than single prompts, making it especially effective for large codebases, complex refactors, and multi-step debugging that unfolds over time. The model shows deeper contextual understanding, stronger problem decomposition, and greater reliability on hard engineering tasks than prior generations.\n\nBeyond coding, Opus 4.6 excels at sustained knowledge work. It produces near-production-ready documents, plans, and analyses in a single pass, and maintains coherence across very long outputs and extended sessions. This makes it a strong default for tasks that require persistence, judgment, and follow-through, such as technical design, migration planning, and end-to-end project execution.\n\nFor users upgrading from earlier Opus versions, see our [official migration guide here](https://openrouter.ai/docs/guides/guides/model-migrations/claude-4-6-opus)\n", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition", "web-search", "computer-use"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 128000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 25 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.5 + }, + "cacheWrite": { + "currency": "USD", + "perMillionTokens": 6.25 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 32000 + }, + "interleaved": true + }, + "family": "claude-opus", + "ownedBy": "anthropic", + "openWeights": false + }, + { + "id": "claude-opus-4-5", + "name": "Claude Opus 4.5", + "description": "Claude Opus 4.5 is Anthropic’s frontier reasoning model optimized for complex software engineering, agentic workflows, and long-horizon computer use. It offers strong multimodal capabilities, competitive performance across real-world coding and reasoning benchmarks, and improved robustness to prompt injection. The model is designed to operate efficiently across varied effort levels, enabling developers to trade off speed, depth, and token usage depending on task requirements. It comes with a new parameter to control token efficiency, which can be accessed using the OpenRouter Verbosity parameter with low, medium, or high.\n\nOpus 4.5 supports advanced tool use, extended context management, and coordinated multi-agent setups, making it well-suited for autonomous research, debugging, multi-step planning, and spreadsheet/browser manipulation. It delivers substantial gains in structured reasoning, execution reliability, and alignment compared to prior Opus generations, while reducing token overhead and improving performance on long-running tasks.", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition", "web-search", "computer-use"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 25 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.5 + }, + "cacheWrite": { + "currency": "USD", + "perMillionTokens": 6.25 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 64000 + }, + "interleaved": true + }, + "family": "claude-opus", + "ownedBy": "anthropic", + "openWeights": false + }, + { + "id": "gemini-3-pro-preview", + "name": "Gemini 3 Pro Preview", + "description": "Gemini 3 Pro is Google’s flagship frontier model for high-precision multimodal reasoning, combining strong performance across text, image, video, audio, and code with a 1M-token context window. Reasoning Details must be preserved when using multi-turn tool calling, see our docs here: https://openrouter.ai/docs/use-cases/reasoning-tokens#preserving-reasoning-blocks. It delivers state-of-the-art benchmark results in general reasoning, STEM problem solving, factual QA, and multimodal understanding, including leading scores on LMArena, GPQA Diamond, MathArena Apex, MMMU-Pro, and Video-MMMU. Interactions emphasize depth and interpretability: the model is designed to infer intent with minimal prompting and produce direct, insight-focused responses.\n\nBuilt for advanced development and agentic workflows, Gemini 3 Pro provides robust tool-calling, long-horizon planning stability, and strong zero-shot generation for complex UI, visualization, and coding tasks. It excels at agentic coding (SWE-Bench Verified, Terminal-Bench 2.0), multimodal analysis, and structured long-form tasks such as research synthesis, planning, and interactive learning experiences. Suitable applications include autonomous agents, coding assistants, multimodal analytics, scientific reasoning, and high-context information processing.", + "capabilities": [ + "reasoning", + "function-call", + "structured-output", + "file-input", + "image-recognition", + "audio-recognition", + "video-recognition", + "web-search" + ], + "inputModalities": ["text", "image", "video", "audio"], + "outputModalities": ["text"], + "contextWindow": 1000000, + "maxOutputTokens": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 12 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.2 + } + }, + "reasoning": { + "supportedEfforts": ["low", "high"], + "thinkingTokenLimits": { + "min": 128, + "max": 32768 + }, + "interleaved": true + }, + "family": "gemini-pro", + "ownedBy": "google", + "openWeights": false + }, + { + "id": "gemini-2-5-flash", + "name": "Gemini 2.5 Flash", + "description": "Gemini 2.5 Flash is Google's state-of-the-art workhorse model, specifically designed for advanced reasoning, coding, mathematics, and scientific tasks. It includes built-in \"thinking\" capabilities, enabling it to provide responses with greater accuracy and nuanced context handling. \n\nAdditionally, Gemini 2.5 Flash is configurable through the \"max tokens for reasoning\" parameter, as described in the documentation (https://openrouter.ai/docs/use-cases/reasoning-tokens#max-tokens-for-reasoning).", + "capabilities": [ + "reasoning", + "function-call", + "file-input", + "image-recognition", + "audio-recognition", + "video-recognition", + "web-search" + ], + "inputModalities": ["text", "image", "audio", "video"], + "outputModalities": ["text"], + "contextWindow": 1048576, + "maxOutputTokens": 65536, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2.5 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.075 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 0, + "max": 24576 + } + }, + "family": "gemini-flash", + "ownedBy": "google", + "openWeights": false + }, + { + "id": "claude-sonnet-4-5", + "name": "Claude Sonnet 4.5", + "description": "Claude Sonnet 4.5 is Anthropic’s most advanced Sonnet model to date, optimized for real-world agents and coding workflows. It delivers state-of-the-art performance on coding benchmarks such as SWE-bench Verified, with improvements across system design, code security, and specification adherence. The model is designed for extended autonomous operation, maintaining task continuity across sessions and providing fact-based progress tracking.\n\nSonnet 4.5 also introduces stronger agentic capabilities, including improved tool orchestration, speculative parallel execution, and more efficient context and memory management. With enhanced context tracking and awareness of token usage across tool calls, it is particularly well-suited for multi-context and long-running workflows. Use cases span software engineering, cybersecurity, financial analysis, research agents, and other domains requiring sustained reasoning and tool use.", + "capabilities": ["function-call", "file-input", "image-recognition", "reasoning", "web-search", "computer-use"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 15 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.3 + }, + "cacheWrite": { + "currency": "USD", + "perMillionTokens": 4.125 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 64000 + }, + "interleaved": true + }, + "family": "claude-sonnet", + "ownedBy": "anthropic", + "openWeights": false + }, + { + "id": "gpt-5-nano", + "name": "GPT-5 Nano", + "description": "GPT-5-Nano is the smallest and fastest variant in the GPT-5 system, optimized for developer tools, rapid interactions, and ultra-low latency environments. While limited in reasoning depth compared to its larger counterparts, it retains key instruction-following and safety features. It is the successor to GPT-4.1-nano and offers a lightweight option for cost-sensitive or real-time applications.", + "capabilities": [ + "reasoning", + "function-call", + "structured-output", + "file-input", + "image-recognition", + "web-search" + ], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 400000, + "maxOutputTokens": 128000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.05 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.4 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.01 + } + }, + "reasoning": { + "supportedEfforts": ["minimal", "low", "medium", "high"] + }, + "family": "gpt-nano", + "ownedBy": "openai", + "openWeights": false + }, + { + "id": "gpt-4o", + "name": "GPT-4o", + "description": "GPT-4o (\"o\" for \"omni\") is OpenAI's latest AI model, supporting both text and image inputs with text outputs. It maintains the intelligence level of [GPT-4 Turbo](/models/openai/gpt-4-turbo) while being twice as fast and 50% more cost-effective. GPT-4o also offers improved performance in processing non-English languages and enhanced visual capabilities.\n\nFor benchmarking against other models, it was briefly called [\"im-also-a-good-gpt2-chatbot\"](https://twitter.com/LiamFedus/status/1790064963966370209)\n\n#multimodal", + "capabilities": ["function-call", "structured-output", "file-input", "image-recognition", "web-search"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 16384, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2.5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 10 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 1.25 + } + }, + "family": "gpt", + "ownedBy": "openai", + "openWeights": false + }, + { + "id": "gpt-5-mini", + "name": "GPT-5 Mini", + "description": "GPT-5 Mini is a compact version of GPT-5, designed to handle lighter-weight reasoning tasks. It provides the same instruction-following and safety-tuning benefits as GPT-5, but with reduced latency and cost. GPT-5 Mini is the successor to OpenAI's o4-mini model.", + "capabilities": [ + "reasoning", + "function-call", + "structured-output", + "file-input", + "image-recognition", + "web-search" + ], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 400000, + "maxOutputTokens": 128000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.25 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.03 + } + }, + "reasoning": { + "supportedEfforts": ["minimal", "low", "medium", "high"] + }, + "family": "gpt-mini", + "ownedBy": "openai", + "openWeights": false + }, + { + "id": "gemini-2-5-pro", + "name": "Gemini 2.5 Pro", + "description": "Gemini 2.5 Pro is Google’s state-of-the-art AI model designed for advanced reasoning, coding, mathematics, and scientific tasks. It employs “thinking” capabilities, enabling it to reason through responses with enhanced accuracy and nuanced context handling. Gemini 2.5 Pro achieves top-tier performance on multiple benchmarks, including first-place positioning on the LMArena leaderboard, reflecting superior human-preference alignment and complex problem-solving abilities.", + "capabilities": [ + "reasoning", + "function-call", + "file-input", + "image-recognition", + "audio-recognition", + "video-recognition", + "web-search" + ], + "inputModalities": ["text", "image", "audio", "video"], + "outputModalities": ["text"], + "contextWindow": 1048576, + "maxOutputTokens": 65536, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.25 + }, + "output": { + "currency": "USD", + "perMillionTokens": 10 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.31 + } + }, + "reasoning": { + "supportedEfforts": ["low", "medium", "high"], + "thinkingTokenLimits": { + "min": 128, + "max": 32768 + } + }, + "family": "gemini-pro", + "ownedBy": "google", + "openWeights": false + }, + { + "id": "gpt-5", + "name": "GPT-5", + "description": "GPT-5 is OpenAI’s most advanced model, offering major improvements in reasoning, code quality, and user experience. It is optimized for complex tasks that require step-by-step reasoning, instruction following, and accuracy in high-stakes use cases. It supports test-time routing features and advanced prompt understanding, including user-specified intent like \"think hard about this.\" Improvements include reductions in hallucination, sycophancy, and better performance in coding, writing, and health-related tasks.", + "capabilities": [ + "reasoning", + "function-call", + "structured-output", + "file-input", + "image-recognition", + "web-search" + ], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 400000, + "maxOutputTokens": 128000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.25 + }, + "output": { + "currency": "USD", + "perMillionTokens": 10 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.13 + } + }, + "reasoning": { + "supportedEfforts": ["minimal", "low", "medium", "high"] + }, + "family": "gpt", + "ownedBy": "openai", + "openWeights": false + }, + { + "id": "gpt-5-2", + "name": "GPT-5.2", + "description": "GPT-5.2 is the latest frontier-grade model in the GPT-5 series, offering stronger agentic and long context perfomance compared to GPT-5.1. It uses adaptive reasoning to allocate computation dynamically, responding quickly to simple queries while spending more depth on complex tasks.\n\nBuilt for broad task coverage, GPT-5.2 delivers consistent gains across math, coding, sciende, and tool calling workloads, with more coherent long-form answers and improved tool-use reliability.", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition", "web-search"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 400000, + "maxOutputTokens": 128000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.75 + }, + "output": { + "currency": "USD", + "perMillionTokens": 14 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.175 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high", "max"] + }, + "family": "gpt", + "ownedBy": "openai", + "openWeights": false + }, + { + "id": "nova-2-lite-v1", + "name": "Nova 2 Lite", + "description": "Nova 2 Lite is a fast, cost-effective reasoning model for everyday workloads that can process text, images, and videos to generate text. \n\nNova 2 Lite demonstrates standout capabilities in processing documents, extracting information from videos, generating code, providing accurate grounded answers, and automating multi-step agentic workflows.", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition", "video-recognition"], + "inputModalities": ["text", "image", "video"], + "outputModalities": ["text"], + "contextWindow": 1000000, + "maxOutputTokens": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2.5 + } + }, + "family": "nova-lite", + "ownedBy": "amazon", + "openWeights": false + }, + { + "id": "nova-2-pro-v1", + "name": "Nova 2 Pro", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition", "video-recognition"], + "inputModalities": ["text", "image", "video"], + "outputModalities": ["text"], + "contextWindow": 1000000, + "maxOutputTokens": 64000, + "family": "nova-pro", + "ownedBy": "amazon", + "openWeights": false + }, + { + "id": "nexus-coder", + "name": "LucidQuery Nexus Coder", + "capabilities": ["reasoning", "function-call", "file-input"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 250000, + "maxOutputTokens": 60000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 5 + } + }, + "family": "lucid", + "ownedBy": "lucidquery", + "openWeights": false + }, + { + "id": "rf1", + "name": "LucidNova RF1 100B", + "capabilities": ["reasoning", "function-call", "file-input"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 120000, + "maxOutputTokens": 8000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 5 + } + }, + "family": "nova", + "ownedBy": "lucidquery", + "openWeights": false + }, + { + "id": "gpt-4-1-nano", + "name": "gpt-4.1-nano", + "description": "For tasks that demand low latency, GPT‑4.1 nano is the fastest and cheapest model in the GPT-4.1 series. It delivers exceptional performance at a small size with its 1 million token context window, and scores 80.1% on MMLU, 50.3% on GPQA, and 9.8% on Aider polyglot coding – even higher than GPT‑4o mini. It’s ideal for tasks like classification or autocompletion.", + "capabilities": ["function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 1000000, + "maxOutputTokens": 32768, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.1 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.4 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.03 + } + }, + "family": "gpt-nano", + "ownedBy": "openai", + "openWeights": false + }, + { + "id": "doubao-seed-code-preview-251028", + "name": "doubao-seed-code-preview-251028", + "capabilities": ["function-call", "file-input", "image-recognition", "reasoning"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 256000, + "maxOutputTokens": 32000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.17 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.14 + } + }, + "reasoning": { + "supportedEfforts": ["none", "high"] + }, + "ownedBy": "bytedance", + "openWeights": false + }, + { + "id": "glm-4-7", + "name": "glm-4.7", + "description": "GLM-4.7 is Z.ai’s latest flagship model, featuring upgrades in two key areas: enhanced programming capabilities and more stable multi-step reasoning/execution. It demonstrates significant improvements in executing complex agent tasks while delivering more natural conversational experiences and superior front-end aesthetics.", + "capabilities": ["function-call", "reasoning"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 131072, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.286 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.142 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.08 + } + }, + "reasoning": { + "supportedEfforts": ["none"], + "interleaved": true + }, + "family": "glm", + "ownedBy": "zhipu", + "openWeights": false + }, + { + "id": "grok-4-fast-non-reasoning", + "name": "grok-4-fast-non-reasoning", + "description": "Grok-4-fast is a cost-effective inference model developed by xAI that delivers cutting-edge performance with excellent token efficiency. The model features a 2 million token context window, advanced Web and X search capabilities, and a unified architecture supporting both \"inference\" and \"non-inference\" modes. Compared to Grok 4, it reduces thinking tokens by an average of 40% and lowers the price by 98% while achieving the same performance.", + "capabilities": ["function-call", "file-input", "image-recognition", "web-search"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 2000000, + "maxOutputTokens": 30000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.5 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.05 + } + }, + "family": "grok", + "ownedBy": "xai", + "openWeights": false + }, + { + "id": "qwen3-a22b-instruct-2507", + "name": "qwen3-235b-a22b-instruct-2507", + "description": "Qwen3-235B-A22B-Instruct-2507", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 65536, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.29 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.143 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.04 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": false + }, + { + "id": "grok-4-fast-reasoning", + "name": "grok-4-fast-reasoning", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition", "web-search"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 2000000, + "maxOutputTokens": 30000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.5 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.05 + } + }, + "reasoning": { + "supportedEfforts": [] + }, + "family": "grok", + "ownedBy": "xai", + "openWeights": false + }, + { + "id": "mistral-large-2512", + "name": "mistral-large-2512", + "description": "Mistral Large 3 2512 is Mistral’s most capable model to date, featuring a sparse mixture-of-experts architecture with 41B active parameters (675B total), and released under the Apache 2.0 license.", + "capabilities": ["function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 262144, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.1 + }, + "output": { + "currency": "USD", + "perMillionTokens": 3.3 + } + }, + "family": "mistral-large", + "ownedBy": "mistral", + "openWeights": false + }, + { + "id": "minimax-m2", + "name": "MiniMax-M2", + "description": "MiniMax-M2 is a compact, high-efficiency large language model optimized for end-to-end coding and agentic workflows. With 10 billion activated parameters (230 billion total), it delivers near-frontier intelligence across general reasoning, tool use, and multi-step task execution while maintaining low latency and deployment efficiency.\n\nThe model excels in code generation, multi-file editing, compile-run-fix loops, and test-validated repair, showing strong results on SWE-Bench Verified, Multi-SWE-Bench, and Terminal-Bench. It also performs competitively in agentic evaluations such as BrowseComp and GAIA, effectively handling long-horizon planning, retrieval, and recovery from execution errors.\n\nBenchmarked by [Artificial Analysis](https://artificialanalysis.ai/models/minimax-m2), MiniMax-M2 ranks among the top open-source models for composite intelligence, spanning mathematics, science, and instruction-following. Its small activation footprint enables fast inference, high concurrency, and improved unit economics, making it well-suited for large-scale agents, developer assistants, and reasoning-driven applications that require responsiveness and cost efficiency.\n\nTo avoid degrading this model's performance, MiniMax highly recommends preserving reasoning between turns. Learn more about using reasoning_details to pass back reasoning in our [docs](https://openrouter.ai/docs/use-cases/reasoning-tokens#preserving-reasoning-blocks).", + "capabilities": ["function-call", "reasoning"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 1000000, + "maxOutputTokens": 128000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.33 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.32 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.03 + } + }, + "reasoning": { + "supportedEfforts": [], + "interleaved": true + }, + "family": "minimax", + "ownedBy": "minimax", + "openWeights": false + }, + { + "id": "grok-4-1-fast-reasoning", + "name": "grok-4-1-fast-reasoning", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition", "web-search"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 2000000, + "maxOutputTokens": 30000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.5 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.05 + } + }, + "reasoning": { + "supportedEfforts": [] + }, + "family": "grok", + "ownedBy": "xai", + "openWeights": false + }, + { + "id": "deepseek-v3-2", + "name": "DeepSeek-V3.2-Thinking", + "description": "DeepSeek-V3.2 is a large language model designed to harmonize high computational efficiency with strong reasoning and agentic tool-use performance. It introduces DeepSeek Sparse Attention (DSA), a fine-grained sparse attention mechanism that reduces training and inference cost while preserving quality in long-context scenarios. A scalable reinforcement learning post-training framework further improves reasoning, with reported performance in the GPT-5 class, and the model has demonstrated gold-medal results on the 2025 IMO and IOI. V3.2 also uses a large-scale agentic task synthesis pipeline to better integrate reasoning into tool-use settings, boosting compliance and generalization in interactive environments.\n\nUsers can control the reasoning behaviour with the `reasoning` `enabled` boolean. [Learn more in our docs](https://openrouter.ai/docs/use-cases/reasoning-tokens#enable-reasoning-with-default-config)", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 128000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.29 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.43 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.03 + } + }, + "reasoning": { + "supportedEfforts": ["none"], + "interleaved": true + }, + "family": "deepseek", + "ownedBy": "deepseek", + "openWeights": false + }, + { + "id": "ministral-2512", + "name": "ministral-14b-2512", + "description": "The largest model in the Ministral 3 family, Ministral 3 14B offers frontier capabilities and performance comparable to its larger Mistral Small 3.2 24B counterpart. A powerful and efficient language model with vision capabilities.", + "capabilities": ["function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 128000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.33 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.33 + } + }, + "ownedBy": "mistral", + "openWeights": false + }, + { + "id": "glm-4-5", + "name": "GLM-4.5", + "description": "GLM-4.5 is our latest flagship foundation model, purpose-built for agent-based applications. It leverages a Mixture-of-Experts (MoE) architecture and supports a context length of up to 128k tokens. GLM-4.5 delivers significantly enhanced capabilities in reasoning, code generation, and agent alignment. It supports a hybrid inference mode with two options, a \"thinking mode\" designed for complex reasoning and tool use, and a \"non-thinking mode\" optimized for instant responses. Users can control the reasoning behaviour with the `reasoning` `enabled` boolean. [Learn more in our docs](https://openrouter.ai/docs/use-cases/reasoning-tokens#enable-reasoning-with-default-config)", + "capabilities": ["function-call", "reasoning"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 98304, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.286 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.142 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.06 + } + }, + "reasoning": { + "supportedEfforts": ["none"], + "interleaved": true + }, + "family": "glm", + "ownedBy": "zhipu", + "openWeights": false + }, + { + "id": "gemini-2-5-flash-image", + "name": "gemini-2.5-flash-image", + "description": "Gemini 2.5 Flash Image, a.k.a. \"Nano Banana,\" is now generally available. It is a state of the art image generation model with contextual understanding. It is capable of image generation, edits, and multi-turn conversations. Aspect ratios can be controlled with the [image_config API Parameter](https://openrouter.ai/docs/features/multimodal/image-generation#image-aspect-ratio-configuration)", + "capabilities": [ + "file-input", + "image-recognition", + "function-call", + "image-generation", + "web-search", + "reasoning" + ], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 32768, + "maxOutputTokens": 32768, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 30 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.075 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 0, + "max": 24576 + } + }, + "family": "gemini-flash", + "ownedBy": "google", + "openWeights": false + }, + { + "id": "deepseek-chat", + "name": "Deepseek-Chat", + "description": "DeepSeek-V3 is the latest model from the DeepSeek team, building upon the instruction following and coding abilities of the previous versions. Pre-trained on nearly 15 trillion tokens, the reported evaluations reveal that the model outperforms other open-source models and rivals leading closed-source models.\n\nFor model details, please visit [the DeepSeek-V3 repo](https://github.com/deepseek-ai/DeepSeek-V3) for more information, or see the [launch announcement](https://api-docs.deepseek.com/news/news1226).", + "capabilities": ["function-call", "reasoning"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.29 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.43 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.028 + } + }, + "reasoning": { + "supportedEfforts": ["none"] + }, + "family": "deepseek", + "ownedBy": "deepseek", + "openWeights": false + }, + { + "id": "gpt-4-1-mini", + "name": "gpt-4.1-mini", + "description": "GPT-4.1 Mini is a mid-sized model delivering performance competitive with GPT-4o at substantially lower latency and cost. It retains a 1 million token context window and scores 45.1% on hard instruction evals, 35.8% on MultiChallenge, and 84.1% on IFEval. Mini also shows strong coding ability (e.g., 31.6% on Aider’s polyglot diff benchmark) and vision understanding, making it suitable for interactive applications with tight performance constraints.", + "capabilities": ["function-call", "file-input", "image-recognition", "web-search"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 1000000, + "maxOutputTokens": 32768, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.4 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.6 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.1 + } + }, + "family": "gpt-mini", + "ownedBy": "openai", + "openWeights": false + }, + { + "id": "gemini-2-0-flash-lite", + "name": "gemini-2.0-flash-lite", + "description": "Gemini-2.0-flash Lightweight Official Version", + "capabilities": ["file-input", "image-recognition", "function-call", "web-search"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 2000000, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.075 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.3 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.076 + } + }, + "family": "gemini-flash-lite", + "ownedBy": "google", + "openWeights": false + }, + { + "id": "claude-sonnet-4-5-20250929", + "name": "claude-sonnet-4-5-20250929-thinking", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition", "web-search", "computer-use"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 15 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.30000000000000004 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 64000 + } + }, + "family": "claude-sonnet", + "ownedBy": "anthropic", + "openWeights": false, + "alias": ["claude-sonnet-4-5"] + }, + { + "id": "claude-opus-4-5-20251101", + "name": "claude-opus-4-5-20251101", + "capabilities": ["function-call", "file-input", "image-recognition", "reasoning", "web-search", "computer-use"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 25 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.5 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 64000 + } + }, + "family": "claude-opus", + "ownedBy": "anthropic", + "openWeights": false, + "alias": ["claude-opus-4-5"] + }, + { + "id": "qwen3-max-2025-09-23", + "name": "qwen3-max-2025-09-23", + "capabilities": ["function-call", "reasoning"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 258048, + "maxOutputTokens": 65536, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.86 + }, + "output": { + "currency": "USD", + "perMillionTokens": 3.43 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 0, + "max": 81920 + } + }, + "ownedBy": "alibaba", + "openWeights": false + }, + { + "id": "grok-4-1", + "name": "grok-4.1", + "capabilities": ["function-call", "file-input", "image-recognition", "reasoning", "web-search"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 10 + } + }, + "ownedBy": "xai", + "openWeights": false + }, + { + "id": "chatgpt-4o-latest", + "name": "chatgpt-4o-latest", + "description": "This model will point to the latest GPT-4o model used by ChatGPT.", + "capabilities": ["file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 16384, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 15 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 2.5 + } + }, + "family": "gpt", + "ownedBy": "openai", + "openWeights": false + }, + { + "id": "gpt-5-2-chat-latest", + "name": "gpt-5.2-chat-latest", + "description": "GPT-5.2Chat refers to the GPT-5.2 snapshot currently used in ChatGPT and is optimized for conversational use cases. While GPT-5.2 is recommended for most API applications, GPT-5.2Chat is ideal for testing the latest improvements in chat-based interactions.", + "capabilities": ["function-call", "file-input", "image-recognition", "web-search", "reasoning"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 16384, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.75 + }, + "output": { + "currency": "USD", + "perMillionTokens": 14 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.175 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high", "max"] + }, + "family": "gpt", + "ownedBy": "openai", + "openWeights": false + }, + { + "id": "gpt-5-1", + "name": "gpt-5.1", + "description": "GPT-5.1 is the latest frontier-grade model in the GPT-5 series, offering stronger general-purpose reasoning, improved instruction adherence, and a more natural conversational style compared to GPT-5. It uses adaptive reasoning to allocate computation dynamically, responding quickly to simple queries while spending more depth on complex tasks. The model produces clearer, more grounded explanations with reduced jargon, making it easier to follow even on technical or multi-step problems.\n\nBuilt for broad task coverage, GPT-5.1 delivers consistent gains across math, coding, and structured analysis workloads, with more coherent long-form answers and improved tool-use reliability. It also features refined conversational alignment, enabling warmer, more intuitive responses without compromising precision. GPT-5.1 serves as the primary full-capability successor to GPT-5", + "capabilities": ["function-call", "file-input", "image-recognition", "web-search", "reasoning"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 400000, + "maxOutputTokens": 128000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.25 + }, + "output": { + "currency": "USD", + "perMillionTokens": 10 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.13 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"] + }, + "family": "gpt", + "ownedBy": "openai", + "openWeights": false + }, + { + "id": "doubao-seed-1-6-vision-250815", + "name": "doubao-seed-1-6-vision-250815", + "description": "Doubao-Seed-1.6-vision is a visual deep-thinking model that demonstrates stronger general multimodal understanding and reasoning capabilities in scenarios such as education, image moderation, inspection and security, and AI search Q&A. It supports a 256K context window and an output length of up to 64K tokens.", + "capabilities": ["function-call", "file-input", "image-recognition", "reasoning"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 256000, + "maxOutputTokens": 32000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.114 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.143 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.021918 + } + }, + "ownedBy": "bytedance", + "openWeights": false + }, + { + "id": "qwen-flash", + "name": "Qwen-Flash", + "description": "The model adopts tiered pricing.", + "capabilities": ["function-call", "reasoning"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 1000000, + "maxOutputTokens": 32768, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.022 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.22 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.02 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 0, + "max": 81920 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": false + }, + { + "id": "gemini-3-pro-image-preview", + "name": "gemini-3-pro-image-preview", + "description": "Nano Banana Pro is Google’s most advanced image-generation and editing model, built on Gemini 3 Pro. It extends the original Nano Banana with significantly improved multimodal reasoning, real-world grounding, and high-fidelity visual synthesis. The model generates context-rich graphics, from infographics and diagrams to cinematic composites, and can incorporate real-time information via Search grounding.\n\nIt offers industry-leading text rendering in images (including long passages and multilingual layouts), consistent multi-image blending, and accurate identity preservation across up to five subjects. Nano Banana Pro adds fine-grained creative controls such as localized edits, lighting and focus adjustments, camera transformations, and support for 2K/4K outputs and flexible aspect ratios. It is designed for professional-grade design, product visualization, storyboarding, and complex multi-element compositions while remaining efficient for general image creation workflows.", + "capabilities": ["file-input", "image-recognition", "function-call", "image-generation", "web-search"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 32768, + "maxOutputTokens": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 120 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.19999999999999998 + } + }, + "ownedBy": "google", + "openWeights": false + }, + { + "id": "doubao-seed-1-6-thinking-250715", + "name": "doubao-seed-1-6-thinking-250715", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 256000, + "maxOutputTokens": 16000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.121 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.21 + } + }, + "ownedBy": "bytedance", + "openWeights": false + }, + { + "id": "gpt-4-1", + "name": "gpt-4.1", + "description": "GPT-4.1 is a flagship large language model optimized for advanced instruction following, real-world software engineering, and long-context reasoning. It supports a 1 million token context window and outperforms GPT-4o and GPT-4.5 across coding (54.6% SWE-bench Verified), instruction compliance (87.4% IFEval), and multimodal understanding benchmarks. It is tuned for precise code diffs, agent reliability, and high recall in large document contexts, making it ideal for agents, IDE tooling, and enterprise knowledge retrieval.", + "capabilities": ["function-call", "file-input", "image-recognition", "web-search"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 1000000, + "maxOutputTokens": 32768, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 8 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.5 + } + }, + "family": "gpt", + "ownedBy": "openai", + "openWeights": false + }, + { + "id": "qwen-plus", + "name": "Qwen-Plus", + "description": "Qwen-Plus, based on the Qwen2.5 foundation model, is a 131K context model with a balanced performance, speed, and cost combination.", + "capabilities": ["function-call", "reasoning"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 1000000, + "maxOutputTokens": 32768, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.12 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.2 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.08 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 0, + "max": 81920 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": false + }, + { + "id": "glm-4-5v", + "name": "GLM-4.5V", + "description": "GLM-4.5V is a vision-language foundation model for multimodal agent applications. Built on a Mixture-of-Experts (MoE) architecture with 106B parameters and 12B activated parameters, it achieves state-of-the-art results in video understanding, image Q&A, OCR, and document parsing, with strong gains in front-end web coding, grounding, and spatial reasoning. It offers a hybrid inference mode: a \"thinking mode\" for deep reasoning and a \"non-thinking mode\" for fast responses. Reasoning behavior can be toggled via the `reasoning` `enabled` boolean. [Learn more in our docs](https://openrouter.ai/docs/use-cases/reasoning-tokens#enable-reasoning-with-default-config)", + "capabilities": ["function-call", "file-input", "image-recognition", "reasoning"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 64000, + "maxOutputTokens": 16384, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.29 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.86 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.11 + } + }, + "reasoning": { + "supportedEfforts": ["none"], + "interleaved": true + }, + "family": "glm", + "ownedBy": "zhipu", + "openWeights": false + }, + { + "id": "glm-4-6", + "name": "glm-4.6", + "description": "Compared with GLM-4.5, this generation brings several key improvements:\n\nLonger context window: The context window has been expanded from 128K to 200K tokens, enabling the model to handle more complex agentic tasks.\nSuperior coding performance: The model achieves higher scores on code benchmarks and demonstrates better real-world performance in applications such as Claude Code、Cline、Roo Code and Kilo Code, including improvements in generating visually polished front-end pages.\nAdvanced reasoning: GLM-4.6 shows a clear improvement in reasoning performance and supports tool use during inference, leading to stronger overall capability.\nMore capable agents: GLM-4.6 exhibits stronger performance in tool using and search-based agents, and integrates more effectively within agent frameworks.\nRefined writing: Better aligns with human preferences in style and readability, and performs more naturally in role-playing scenarios.", + "capabilities": ["function-call", "reasoning"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 131072, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.286 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.142 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.11 + } + }, + "reasoning": { + "supportedEfforts": ["none"], + "interleaved": true + }, + "family": "glm", + "ownedBy": "zhipu", + "openWeights": false + }, + { + "id": "gemini-2-5-flash-preview-09-2025", + "name": "gemini-2.5-flash-preview-09-2025", + "description": "This latest 2.5 Flash model comes with improvements in two key areas we heard consistent feedback on:\n\nBetter agentic tool use: We've improved how the model uses tools, leading to better performance in more complex, agentic and multi-step applications. This model shows noticeable improvements on key agentic benchmarks, including a 5% gain on SWE-Bench Verified, compared to our last release (48.9% → 54%). More efficient: With thinking on, the model is now significantly more cost-efficient—achieving higher quality outputs while using fewer tokens, reducing latency and cost (see charts above).", + "capabilities": ["function-call", "file-input", "image-recognition", "web-search", "reasoning"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 1000000, + "maxOutputTokens": 65536, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2.5 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.03 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 0, + "max": 24576 + } + }, + "family": "gemini-flash", + "ownedBy": "google", + "openWeights": false + }, + { + "id": "claude-opus-4-1-20250805", + "name": "claude-opus-4-1-20250805-thinking", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition", "web-search", "computer-use"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 32000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 15 + }, + "output": { + "currency": "USD", + "perMillionTokens": 75 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 1.5 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 32000 + } + }, + "family": "claude-opus", + "ownedBy": "anthropic", + "openWeights": false + }, + { + "id": "qwen3-a22b", + "name": "Qwen3-235B-A22B", + "description": "Qwen3-235B-A22B is a 235B parameter mixture-of-experts (MoE) model developed by Qwen, activating 22B parameters per forward pass. It supports seamless switching between a \"thinking\" mode for complex reasoning, math, and code tasks, and a \"non-thinking\" mode for general conversational efficiency. The model demonstrates strong reasoning ability, multilingual support (100+ languages and dialects), advanced instruction-following, and agent tool-calling capabilities. It natively handles a 32K token context window and extends up to 131K tokens using YaRN-based scaling.", + "capabilities": ["function-call", "reasoning"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 16384, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.29 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2.86 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.15 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 38912 + }, + "interleaved": true + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": false + }, + { + "id": "minimax-m1", + "name": "MiniMax-M1", + "description": "MiniMax-M1 is a large-scale, open-weight reasoning model designed for extended context and high-efficiency inference. It leverages a hybrid Mixture-of-Experts (MoE) architecture paired with a custom \"lightning attention\" mechanism, allowing it to process long sequences—up to 1 million tokens—while maintaining competitive FLOP efficiency. With 456 billion total parameters and 45.9B active per token, this variant is optimized for complex, multi-step reasoning tasks.\n\nTrained via a custom reinforcement learning pipeline (CISPO), M1 excels in long-context understanding, software engineering, agentic tool use, and mathematical reasoning. Benchmarks show strong performance across FullStackBench, SWE-bench, MATH, GPQA, and TAU-Bench, often outperforming other open models like DeepSeek R1 and Qwen3-235B.", + "capabilities": ["function-call", "reasoning"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 1000000, + "maxOutputTokens": 128000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.132 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.254 + } + }, + "family": "minimax", + "ownedBy": "minimax", + "openWeights": false + }, + { + "id": "deepseek-reasoner", + "name": "Deepseek-Reasoner", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 128000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.29 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.43 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.028 + } + }, + "family": "deepseek-thinking", + "ownedBy": "deepseek", + "openWeights": false + }, + { + "id": "qwen-max-latest", + "name": "Qwen-Max-Latest", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.343 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.372 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": false + }, + { + "id": "qwen3-coder-a35b-instruct", + "name": "qwen3-coder-480b-a35b-instruct", + "description": "The code generation model based on Qwen3 has powerful Coding Agent capabilities, achieving state-of-the-art performance compared to open-source models.The model adopts tiered pricing.", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 262144, + "maxOutputTokens": 65536, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.86 + }, + "output": { + "currency": "USD", + "perMillionTokens": 3.43 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.82 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": false + }, + { + "id": "qwen3-a3b", + "name": "Qwen3-30B-A3B", + "description": "Qwen3, the latest generation in the Qwen large language model series, features both dense and mixture-of-experts (MoE) architectures to excel in reasoning, multilingual support, and advanced agent tasks. Its unique ability to switch seamlessly between a thinking mode for complex reasoning and a non-thinking mode for efficient dialogue ensures versatile, high-quality performance.\n\nSignificantly outperforming prior models like QwQ and Qwen2.5, Qwen3 delivers superior mathematics, coding, commonsense reasoning, creative writing, and interactive dialogue capabilities. The Qwen3-30B-A3B variant includes 30.5 billion parameters (3.3 billion activated), 48 layers, 128 experts (8 activated per task), and supports up to 131K token contexts with YaRN, setting a new standard among open-source models.", + "capabilities": ["function-call", "reasoning"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.11 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.08 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.03 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 38912 + }, + "interleaved": true + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": false + }, + { + "id": "minimax-m2-1", + "name": "MiniMax-M2.1", + "description": "MiniMax-M2.1 is a lightweight, state-of-the-art large language model optimized for coding, agentic workflows, and modern application development. With only 10 billion activated parameters, it delivers a major jump in real-world capability while maintaining exceptional latency, scalability, and cost efficiency.\n\nCompared to its predecessor, M2.1 delivers cleaner, more concise outputs and faster perceived response times. It shows leading multilingual coding performance across major systems and application languages, achieving 49.4% on Multi-SWE-Bench and 72.5% on SWE-Bench Multilingual, and serves as a versatile agent “brain” for IDEs, coding tools, and general-purpose assistance.\n\nTo avoid degrading this model's performance, MiniMax highly recommends preserving reasoning between turns. Learn more about using reasoning_details to pass back reasoning in our [docs](https://openrouter.ai/docs/use-cases/reasoning-tokens#preserving-reasoning-blocks).", + "capabilities": ["function-call", "reasoning"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 1000000, + "maxOutputTokens": 131072, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.2 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.03 + } + }, + "reasoning": { + "supportedEfforts": [], + "interleaved": true + }, + "family": "minimax", + "ownedBy": "minimax", + "openWeights": false + }, + { + "id": "gemini-2-5-flash-lite-preview-09-2025", + "name": "gemini-2.5-flash-lite-preview-09-2025", + "description": "Gemini 2.5 Flash-Lite is a lightweight reasoning model in the Gemini 2.5 family, optimized for ultra-low latency and cost efficiency. It offers improved throughput, faster token generation, and better performance across common benchmarks compared to earlier Flash models. By default, \"thinking\" (i.e. multi-pass reasoning) is disabled to prioritize speed, but developers can enable it via the [Reasoning API parameter](https://openrouter.ai/docs/use-cases/reasoning-tokens) to selectively trade off cost for intelligence. ", + "capabilities": ["function-call", "file-input", "image-recognition", "web-search", "reasoning"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 1000000, + "maxOutputTokens": 65536, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.1 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.4 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.01 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 512, + "max": 24576 + } + }, + "family": "gemini-flash-lite", + "ownedBy": "google", + "openWeights": false + }, + { + "id": "grok-4-1-fast-non-reasoning", + "name": "grok-4-1-fast-non-reasoning", + "description": "Grok 4.1 is a new conversational model with significant improvements in real-world usability, delivering exceptional performance in creative, emotional, and collaborative interactions. It is more perceptive to nuanced user intent, more engaging to converse with, and more coherent in personality, while fully preserving its core intelligence and reliability. Built on large-scale reinforcement learning infrastructure, the model is optimized for style, personality, helpfulness, and alignment, and leverages frontier agentic reasoning models as reward evaluators to autonomously assess and iterate on responses at scale, significantly enhancing overall interaction quality.", + "capabilities": ["function-call", "file-input", "image-recognition", "web-search"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 2000000, + "maxOutputTokens": 30000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.5 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.05 + } + }, + "family": "grok", + "ownedBy": "xai", + "openWeights": false + }, + { + "id": "doubao-seed-1-8-251215", + "name": "doubao-seed-1-8-251215", + "capabilities": ["function-call", "file-input", "image-recognition", "reasoning"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 224000, + "maxOutputTokens": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.114 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.286 + } + }, + "reasoning": { + "supportedEfforts": ["minimal", "low", "medium", "high"] + }, + "ownedBy": "bytedance", + "openWeights": false + }, + { + "id": "gpt-5-pro", + "name": "gpt-5-pro", + "description": "GPT-5 Pro is OpenAI’s most advanced model, offering major improvements in reasoning, code quality, and user experience. It is optimized for complex tasks that require step-by-step reasoning, instruction following, and accuracy in high-stakes use cases. It supports test-time routing features and advanced prompt understanding, including user-specified intent like \"think hard about this.\" Improvements include reductions in hallucination, sycophancy, and better performance in coding, writing, and health-related tasks.", + "capabilities": ["function-call", "file-input", "image-recognition", "web-search", "reasoning"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 400000, + "maxOutputTokens": 272000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 15 + }, + "output": { + "currency": "USD", + "perMillionTokens": 120 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 3.5 + } + }, + "reasoning": { + "supportedEfforts": ["high"] + }, + "family": "gpt-pro", + "ownedBy": "openai", + "openWeights": false + }, + { + "id": "gpt-5-1-chat-latest", + "name": "gpt-5.1-chat-latest", + "description": "GPT-5.1 Chat refers to the GPT-5.1 snapshot currently used in ChatGPT and is optimized for conversational use cases. While GPT-5.1 is recommended for most API applications, GPT-5.1 Chat is ideal for testing the latest improvements in chat-based interactions.", + "capabilities": ["function-call", "file-input", "image-recognition", "web-search", "reasoning"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 16384, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.25 + }, + "output": { + "currency": "USD", + "perMillionTokens": 10 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.12500000000000003 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"] + }, + "family": "gpt", + "ownedBy": "openai", + "openWeights": false + }, + { + "id": "glm-4-6v", + "name": "GLM-4.6V", + "description": "GLM-4.6V is a large multimodal model designed for high-fidelity visual understanding and long-context reasoning across images, documents, and mixed media. It supports up to 128K tokens, processes complex page layouts and charts directly as visual inputs, and integrates native multimodal function calling to connect perception with downstream tool execution. The model also enables interleaved image-text generation and UI reconstruction workflows, including screenshot-to-HTML synthesis and iterative visual editing.", + "capabilities": ["function-call", "file-input", "image-recognition", "reasoning"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 32768, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.145 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.43 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.05 + } + }, + "reasoning": { + "supportedEfforts": ["none"], + "interleaved": true + }, + "family": "glm", + "ownedBy": "zhipu", + "openWeights": false + }, + { + "id": "claude-haiku-4-5-20251001", + "name": "claude-haiku-4-5-20251001", + "capabilities": ["function-call", "file-input", "image-recognition", "reasoning", "web-search", "computer-use"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1 + }, + "output": { + "currency": "USD", + "perMillionTokens": 5 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.09999999999999999 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 64000 + } + }, + "family": "claude-haiku", + "ownedBy": "anthropic", + "openWeights": false, + "alias": ["claude-haiku-4-5"] + }, + { + "id": "gpt-5-2-codex", + "name": "GPT-5.2 Codex", + "description": "GPT-5.2-Codex is an upgraded version of GPT-5.1-Codex optimized for software engineering and coding workflows. It is designed for both interactive development sessions and long, independent execution of complex engineering tasks. The model supports building projects from scratch, feature development, debugging, large-scale refactoring, and code review. Compared to GPT-5.1-Codex, 5.2-Codex is more steerable, adheres closely to developer instructions, and produces cleaner, higher-quality code outputs. Reasoning effort can be adjusted with the `reasoning.effort` parameter. Read the [docs here](https://openrouter.ai/docs/use-cases/reasoning-tokens#reasoning-effort-level)\n\nCodex integrates into developer environments including the CLI, IDE extensions, GitHub, and cloud tasks. It adapts reasoning effort dynamically—providing fast responses for small tasks while sustaining extended multi-hour runs for large projects. The model is trained to perform structured code reviews, catching critical flaws by reasoning over dependencies and validating behavior against tests. It also supports multimodal inputs such as images or screenshots for UI development and integrates tool use for search, dependency installation, and environment setup. Codex is intended specifically for agentic coding applications.", + "capabilities": [ + "reasoning", + "function-call", + "structured-output", + "file-input", + "image-recognition", + "web-search" + ], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 400000, + "maxOutputTokens": 128000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.14 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.14 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.175 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high", "max"] + }, + "family": "gpt-codex", + "ownedBy": "openai", + "openWeights": false + }, + { + "id": "glm-5", + "name": "GLM-5", + "description": "GLM-5 is Z.ai’s flagship open-source foundation model engineered for complex systems design and long-horizon agent workflows. Built for expert developers, it delivers production-grade performance on large-scale programming tasks, rivaling leading closed-source models. With advanced agentic planning, deep backend reasoning, and iterative self-correction, GLM-5 moves beyond code generation to full-system construction and autonomous execution.", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 204800, + "maxOutputTokens": 131072, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1 + }, + "output": { + "currency": "USD", + "perMillionTokens": 3.2 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.2 + } + }, + "reasoning": { + "supportedEfforts": ["none", "auto"] + }, + "family": "glm", + "ownedBy": "zhipu", + "openWeights": false + }, + { + "id": "glm-4-5-flash", + "name": "GLM-4.5-Flash", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 98304, + "reasoning": { + "supportedEfforts": ["none"], + "interleaved": true + }, + "family": "glm-flash", + "ownedBy": "zhipu", + "openWeights": true + }, + { + "id": "glm-4-7-flash", + "name": "GLM-4.7-Flash", + "description": "As a 30B-class SOTA model, GLM-4.7-Flash offers a new option that balances performance and efficiency. It is further optimized for agentic coding use cases, strengthening coding capabilities, long-horizon task planning, and tool collaboration, and has achieved leading performance among open-source models of the same size on several current public benchmark leaderboards.", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 131072, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.07 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.4 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.01 + } + }, + "reasoning": { + "supportedEfforts": ["none"], + "interleaved": true + }, + "family": "glm-flash", + "ownedBy": "zhipu", + "openWeights": true + }, + { + "id": "glm-4-5-air", + "name": "GLM-4.5-Air", + "description": "GLM-4.5-Air is the lightweight variant of our latest flagship model family, also purpose-built for agent-centric applications. Like GLM-4.5, it adopts the Mixture-of-Experts (MoE) architecture but with a more compact parameter size. GLM-4.5-Air also supports hybrid inference modes, offering a \"thinking mode\" for advanced reasoning and tool use, and a \"non-thinking mode\" for real-time interaction. Users can control the reasoning behaviour with the `reasoning` `enabled` boolean. [Learn more in our docs](https://openrouter.ai/docs/use-cases/reasoning-tokens#enable-reasoning-with-default-config)", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 98304, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.1 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.02 + } + }, + "reasoning": { + "supportedEfforts": ["none"], + "interleaved": true + }, + "family": "glm-air", + "ownedBy": "zhipu", + "openWeights": true + }, + { + "id": "glm-4-7-flashx", + "name": "GLM-4.7-FlashX", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 131072, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.07 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.4 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.01 + } + }, + "reasoning": { + "supportedEfforts": ["none"], + "interleaved": true + }, + "family": "glm-flash", + "ownedBy": "zhipu", + "openWeights": true + }, + { + "id": "qwen3-next:80b", + "name": "qwen3-next:80b", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 262144, + "maxOutputTokens": 32768, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 38912 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": true + }, + { + "id": "cogito-2-1:671b", + "name": "cogito-2.1:671b", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 163840, + "maxOutputTokens": 32000, + "family": "cogito", + "ownedBy": "cogito", + "openWeights": true + }, + { + "id": "qwen3-vl:235b", + "name": "qwen3-vl:235b", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 262144, + "maxOutputTokens": 32768, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 38912 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": true + }, + { + "id": "ministral-3:3b", + "name": "ministral-3:3b", + "capabilities": ["function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 262144, + "maxOutputTokens": 128000, + "family": "ministral", + "ownedBy": "mistral", + "openWeights": true + }, + { + "id": "qwen3-vl:235b-instruct", + "name": "qwen3-vl:235b-instruct", + "capabilities": ["function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 262144, + "maxOutputTokens": 131072, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": true + }, + { + "id": "devstral-small-2:24b", + "name": "devstral-small-2:24b", + "capabilities": ["function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 262144, + "maxOutputTokens": 262144, + "family": "devstral", + "ownedBy": "mistral", + "openWeights": true + }, + { + "id": "qwen3-coder:480b", + "name": "qwen3-coder:480b", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 262144, + "maxOutputTokens": 65536, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": true + }, + { + "id": "gpt-oss:20b", + "name": "gpt-oss:20b", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 32768, + "reasoning": { + "supportedEfforts": ["low", "medium", "high"] + }, + "family": "gpt-oss", + "ownedBy": "openai", + "openWeights": true + }, + { + "id": "gemma3:27b", + "name": "gemma3:27b", + "capabilities": ["file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 131072, + "family": "gemma", + "ownedBy": "google", + "openWeights": true + }, + { + "id": "rnj-1:8b", + "name": "rnj-1:8b", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 32768, + "maxOutputTokens": 4096, + "family": "rnj", + "ownedBy": "essentialai", + "openWeights": true + }, + { + "id": "qwen3-5:397b", + "name": "qwen3.5:397b", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 262144, + "maxOutputTokens": 81920, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 38912 + }, + "interleaved": true + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": true + }, + { + "id": "deepseek-v3-1:671b", + "name": "deepseek-v3.1:671b", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 163840, + "maxOutputTokens": 163840, + "reasoning": { + "supportedEfforts": ["none"] + }, + "family": "deepseek", + "ownedBy": "deepseek", + "openWeights": true + }, + { + "id": "ministral-3:8b", + "name": "ministral-3:8b", + "capabilities": ["function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 262144, + "maxOutputTokens": 128000, + "family": "ministral", + "ownedBy": "mistral", + "openWeights": true + }, + { + "id": "kimi-k2:1t", + "name": "kimi-k2:1t", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 262144, + "maxOutputTokens": 262144, + "family": "kimi", + "ownedBy": "moonshot", + "openWeights": true + }, + { + "id": "devstral-2:123b", + "name": "devstral-2:123b", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 262144, + "maxOutputTokens": 262144, + "family": "devstral", + "ownedBy": "mistral", + "openWeights": true + }, + { + "id": "gpt-oss:120b", + "name": "gpt-oss:120b", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 32768, + "reasoning": { + "supportedEfforts": ["low", "medium", "high"] + }, + "family": "gpt-oss", + "ownedBy": "openai", + "openWeights": true + }, + { + "id": "gemma3:4b", + "name": "gemma3:4b", + "capabilities": ["file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 131072, + "family": "gemma", + "ownedBy": "google", + "openWeights": true + }, + { + "id": "nemotron-3-nano:30b", + "name": "nemotron-3-nano:30b", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 1048576, + "maxOutputTokens": 131072, + "family": "nemotron", + "ownedBy": "nvidia", + "openWeights": true + }, + { + "id": "ministral-3:14b", + "name": "ministral-3:14b", + "capabilities": ["function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 262144, + "maxOutputTokens": 128000, + "family": "ministral", + "ownedBy": "mistral", + "openWeights": true + }, + { + "id": "mistral-large-3:675b", + "name": "mistral-large-3:675b", + "capabilities": ["function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 262144, + "maxOutputTokens": 262144, + "family": "mistral-large", + "ownedBy": "mistral", + "openWeights": true + }, + { + "id": "qwen3-coder-next", + "name": "qwen3-coder-next", + "description": "Qwen3-Coder-Next is an open-weight causal language model optimized for coding agents and local development workflows. It uses a sparse MoE design with 80B total parameters and only 3B activated per token, delivering performance comparable to models with 10 to 20x higher active compute, which makes it well suited for cost-sensitive, always-on agent deployment.\n\nThe model is trained with a strong agentic focus and performs reliably on long-horizon coding tasks, complex tool usage, and recovery from execution failures. With a native 256k context window, it integrates cleanly into real-world CLI and IDE environments and adapts well to common agent scaffolds used by modern coding tools. The model operates exclusively in non-thinking mode and does not emit blocks, simplifying integration for production coding agents.", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 262144, + "maxOutputTokens": 65536, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.5 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.035 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": true + }, + { + "id": "gemma3:12b", + "name": "gemma3:12b", + "capabilities": ["file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 131072, + "family": "gemma", + "ownedBy": "google", + "openWeights": true + }, + { + "id": "minimax-m2-5", + "name": "minimax-m2.5", + "description": "MiniMax-M2.5 is a SOTA large language model designed for real-world productivity. Trained in a diverse range of complex real-world digital working environments, M2.5 builds upon the coding expertise of M2.1 to extend into general office work, reaching fluency in generating and operating Word, Excel, and Powerpoint files, context switching between diverse software environments, and working across different agent and human teams. Scoring 80.2% on SWE-Bench Verified, 51.3% on Multi-SWE-Bench, and 76.3% on BrowseComp, M2.5 is also more token efficient than previous generations, having been trained to optimize its actions and output through planning.", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 204800, + "maxOutputTokens": 131072, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.2 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.03 + } + }, + "reasoning": { + "supportedEfforts": [], + "interleaved": true + }, + "family": "minimax", + "ownedBy": "minimax", + "openWeights": true + }, + { + "id": "mimo-v2-flash", + "name": "MiMo-V2-Flash", + "description": "MiMo-V2-Flash is an open-source foundation language model developed by Xiaomi. It is a Mixture-of-Experts model with 309B total parameters and 15B active parameters, adopting hybrid attention architecture. MiMo-V2-Flash supports a hybrid-thinking toggle and a 256K context window, and excels at reasoning, coding, and agent scenarios. On SWE-bench Verified and SWE-bench Multilingual, MiMo-V2-Flash ranks as the top #1 open-source model globally, delivering performance comparable to Claude Sonnet 4.5 while costing only about 3.5% as much.\n\nUsers can control the reasoning behaviour with the `reasoning` `enabled` boolean. [Learn more in our docs](https://openrouter.ai/docs/use-cases/reasoning-tokens#enable-reasoning-with-default-config).", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 256000, + "maxOutputTokens": 32000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.07 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.21 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.3 + } + }, + "reasoning": { + "supportedEfforts": ["none"], + "interleaved": true + }, + "family": "mimo", + "ownedBy": "xiaomi", + "openWeights": true + }, + { + "id": "qwen3-livetranslate-flash-realtime", + "name": "Qwen3-LiveTranslate Flash Realtime", + "capabilities": [ + "image-recognition", + "audio-recognition", + "audio-generation", + "video-recognition", + "function-call", + "reasoning" + ], + "inputModalities": ["text", "image", "audio", "video"], + "outputModalities": ["text", "audio"], + "contextWindow": 53248, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 10 + }, + "output": { + "currency": "USD", + "perMillionTokens": 10 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 38912 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": false + }, + { + "id": "qwen3-asr-flash", + "name": "Qwen3-ASR Flash", + "capabilities": ["audio-recognition", "function-call", "reasoning"], + "inputModalities": ["audio"], + "outputModalities": ["text"], + "contextWindow": 53248, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.035 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.035 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 38912 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": false + }, + { + "id": "qwen-omni-turbo", + "name": "Qwen-Omni Turbo", + "capabilities": [ + "function-call", + "image-recognition", + "audio-recognition", + "audio-generation", + "video-recognition" + ], + "inputModalities": ["text", "image", "audio", "video"], + "outputModalities": ["text", "audio"], + "contextWindow": 32768, + "maxOutputTokens": 2048, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.07 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.27 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": false + }, + { + "id": "qwen-vl-max", + "name": "Qwen-VL Max", + "description": "Qwen VL Max is a visual understanding model with 7500 tokens context length. It excels in delivering optimal performance for a broader spectrum of complex tasks.\n", + "capabilities": ["function-call", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.8 + }, + "output": { + "currency": "USD", + "perMillionTokens": 3.2 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": false + }, + { + "id": "qwen3-next-a3b-instruct", + "name": "Qwen3-Next 80B-A3B Instruct", + "description": "Qwen3-Next-80B-A3B-Instruct is an instruction-tuned chat model in the Qwen3-Next series optimized for fast, stable responses without “thinking” traces. It targets complex tasks across reasoning, code generation, knowledge QA, and multilingual use, while remaining robust on alignment and formatting. Compared with prior Qwen3 instruct variants, it focuses on higher throughput and stability on ultra-long inputs and multi-turn dialogues, making it well-suited for RAG, tool use, and agentic workflows that require consistent final answers rather than visible chain-of-thought.\n\nThe model employs scaling-efficient training and decoding to improve parameter efficiency and inference speed, and has been validated on a broad set of public benchmarks where it reaches or approaches larger Qwen3 systems in several categories while outperforming earlier mid-sized baselines. It is best used as a general assistant, code helper, and long-context task solver in production settings where deterministic, instruction-following outputs are preferred.", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 32768, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.05 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": true + }, + { + "id": "qwen-turbo", + "name": "Qwen Turbo", + "description": "Qwen-Turbo, based on Qwen2.5, is a 1M context model that provides fast speed and low cost, suitable for simple tasks.", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 1000000, + "maxOutputTokens": 16384, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.05 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.2 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.01 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 0, + "max": 38912 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": false + }, + { + "id": "qwen3-vl-a22b", + "name": "Qwen3-VL 235B-A22B", + "description": "The Qwen3 series open-source models include hybrid models, thinking models, and non-thinking models, with both reasoning capabilities and general abilities reaching industry SOTA levels at the same scale.", + "capabilities": ["reasoning", "function-call", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 32768, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.7 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2.8 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 38912 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": true + }, + { + "id": "qwen3-coder-flash", + "name": "Qwen3 Coder Flash", + "description": "Qwen3 Coder Flash is Alibaba's fast and cost efficient version of their proprietary Qwen3 Coder Plus. It is a powerful coding agent model specializing in autonomous programming via tool calling and environment interaction, combining coding proficiency with versatile general-purpose abilities.", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 1000000, + "maxOutputTokens": 65536, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.5 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.06 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": false + }, + { + "id": "qwen3-vl-a3b", + "name": "Qwen3-VL 30B-A3B", + "description": "The Qwen3-VL series’ second-largest MoE model Thinking version offers fast response speed, stronger multimodal understanding and reasoning, visual agent capabilities, and ultra-long context support for long videos and long documents; it features comprehensive upgrades in image/video understanding, spatial perception, and universal recognition abilities, making it capable of handling complex real-world tasks.", + "capabilities": ["reasoning", "function-call", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 32768, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.8 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 38912 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": true + }, + { + "id": "qwen3", + "name": "Qwen3 14B", + "description": "Qwen3-4B is a 4 billion parameter dense language model from the Qwen3 series, designed to support both general-purpose and reasoning-intensive tasks. It introduces a dual-mode architecture—thinking and non-thinking—allowing dynamic switching between high-precision logical reasoning and efficient dialogue generation. This makes it well-suited for multi-turn chat, instruction following, and complex agent workflows.", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.35 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.4 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.01 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": true + }, + { + "id": "qvq-max", + "name": "QVQ Max", + "capabilities": ["reasoning", "function-call", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 4.8 + } + }, + "family": "qvq", + "ownedBy": "alibaba", + "openWeights": false + }, + { + "id": "qwen3-5-a17b", + "name": "Qwen3.5 397B-A17B", + "description": "The Qwen3.5 series 397B-A17B native vision-language model is built on a hybrid architecture that integrates a linear attention mechanism with a sparse mixture-of-experts model, achieving higher inference efficiency. It delivers state-of-the-art performance comparable to leading-edge models across a wide range of tasks, including language understanding, logical reasoning, code generation, agent-based tasks, image understanding, video understanding, and graphical user interface (GUI) interactions. With its robust code-generation and agent capabilities, the model exhibits strong generalization across diverse agent.", + "capabilities": ["reasoning", "function-call", "image-recognition", "video-recognition"], + "inputModalities": ["text", "image", "video"], + "outputModalities": ["text"], + "contextWindow": 262144, + "maxOutputTokens": 65536, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.6 + }, + "output": { + "currency": "USD", + "perMillionTokens": 3.6 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.15 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 38912 + }, + "interleaved": true + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": true + }, + { + "id": "qwen-plus-character-ja", + "name": "Qwen Plus Character (Japanese)", + "capabilities": ["function-call", "reasoning"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 8192, + "maxOutputTokens": 512, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.4 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 0, + "max": 81920 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": false + }, + { + "id": "qwen2-5-instruct", + "name": "Qwen2.5 14B Instruct", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.35 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.4 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": true + }, + { + "id": "qwq-plus", + "name": "QwQ Plus", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.8 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2.4 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": false + }, + { + "id": "qwen3-coder-a3b-instruct", + "name": "Qwen3-Coder 30B-A3B Instruct", + "description": "Qwen3-Coder-30B-A3B-Instruct is a 30.5B parameter Mixture-of-Experts (MoE) model with 128 experts (8 active per forward pass), designed for advanced code generation, repository-scale understanding, and agentic tool use. Built on the Qwen3 architecture, it supports a native context length of 256K tokens (extendable to 1M with Yarn) and performs strongly in tasks involving function calls, browser use, and structured code completion.\n\nThis model is optimized for instruction-following without “thinking mode”, and integrates well with OpenAI-compatible tool-use formats. ", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 262144, + "maxOutputTokens": 65536, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.45 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2.25 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.01 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": true + }, + { + "id": "qwen-vl-ocr", + "name": "Qwen-VL OCR", + "capabilities": ["image-recognition", "function-call", "file-input"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 34096, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.72 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.72 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": false + }, + { + "id": "qwen3-omni-flash", + "name": "Qwen3-Omni Flash", + "capabilities": [ + "reasoning", + "function-call", + "image-recognition", + "audio-recognition", + "audio-generation", + "video-recognition" + ], + "inputModalities": ["text", "image", "audio", "video"], + "outputModalities": ["text", "audio"], + "contextWindow": 65536, + "maxOutputTokens": 16384, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.43 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.66 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 38912 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": false + }, + { + "id": "qwen3-omni-flash-realtime", + "name": "Qwen3-Omni Flash Realtime", + "capabilities": [ + "function-call", + "image-recognition", + "audio-recognition", + "audio-generation", + "video-recognition", + "reasoning" + ], + "inputModalities": ["text", "image", "audio", "video"], + "outputModalities": ["text", "audio"], + "contextWindow": 65536, + "maxOutputTokens": 16384, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.52 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.99 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 38912 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": false + }, + { + "id": "qwen2-5-vl-instruct", + "name": "Qwen2.5-VL 72B Instruct", + "description": "Qwen2.5-VL-32B is a multimodal vision-language model fine-tuned through reinforcement learning for enhanced mathematical reasoning, structured outputs, and visual problem-solving capabilities. It excels at visual analysis tasks, including object recognition, textual interpretation within images, and precise event localization in extended videos. Qwen2.5-VL-32B demonstrates state-of-the-art performance across multimodal benchmarks such as MMMU, MathVista, and VideoMME, while maintaining strong reasoning and clarity in text-based tasks like MMLU, mathematical problem-solving, and code generation.", + "capabilities": ["function-call", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2.8 + }, + "output": { + "currency": "USD", + "perMillionTokens": 8.4 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.025 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": true + }, + { + "id": "qwen3-vl-plus", + "name": "Qwen3-VL Plus", + "description": "The Qwen3 series visual understanding model achieves an effective fusion of thinking and non-thinking modes. Its visual agent capabilities reach world-class levels on public test sets such as OS World. This version features comprehensive upgrades in visual coding, spatial perception, and multimodal reasoning; visual perception and recognition abilities are greatly enhanced, supporting ultra-long video understanding.", + "capabilities": ["reasoning", "function-call", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 262144, + "maxOutputTokens": 32768, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.6 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.0274 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 38912 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": false + }, + { + "id": "qwen2-5-omni", + "name": "Qwen2.5-Omni 7B", + "capabilities": [ + "function-call", + "image-recognition", + "audio-recognition", + "audio-generation", + "video-recognition" + ], + "inputModalities": ["text", "image", "audio", "video"], + "outputModalities": ["text", "audio"], + "contextWindow": 32768, + "maxOutputTokens": 2048, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.1 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.4 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": true + }, + { + "id": "qwen-max", + "name": "Qwen Max", + "description": "Qwen-Max, based on Qwen2.5, provides the best inference performance among [Qwen models](/qwen), especially for complex multi-step tasks. It's a large-scale MoE model that has been pretrained on over 20 trillion tokens and further post-trained with curated Supervised Fine-Tuning (SFT) and Reinforcement Learning from Human Feedback (RLHF) methodologies. The parameter count is unknown.", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 32768, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.6 + }, + "output": { + "currency": "USD", + "perMillionTokens": 6.4 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.32 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": false + }, + { + "id": "qwen-omni-turbo-realtime", + "name": "Qwen-Omni Turbo Realtime", + "capabilities": ["function-call", "image-recognition", "audio-recognition", "audio-generation"], + "inputModalities": ["text", "image", "audio"], + "outputModalities": ["text", "audio"], + "contextWindow": 32768, + "maxOutputTokens": 2048, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.27 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.07 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": false + }, + { + "id": "qwen-mt-turbo", + "name": "Qwen-MT Turbo", + "description": "Based on the comprehensive upgrade of Qwen3, this flagship translation large model supports bidirectional translation across 92 languages. It offers fully enhanced model performance and translation quality, along with more stable terminology customization, format fidelity, and domain-prompting capabilities, making translations more accurate and natural.", + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 16384, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.16 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.49 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": false + }, + { + "id": "qwen-mt-plus", + "name": "Qwen-MT Plus", + "description": "Based on the comprehensive upgrade of Qwen3, this flagship translation large model supports bidirectional translation across 92 languages. It offers fully enhanced model performance and translation quality, along with more stable terminology customization, format fidelity, and domain-prompting capabilities, making translations more accurate and natural.", + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 16384, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2.46 + }, + "output": { + "currency": "USD", + "perMillionTokens": 7.37 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": false + }, + { + "id": "qwen3-max", + "name": "Qwen3 Max", + "description": "Qwen3-Max is an updated release built on the Qwen3 series, offering major improvements in reasoning, instruction following, multilingual support, and long-tail knowledge coverage compared to the January 2025 version. It delivers higher accuracy in math, coding, logic, and science tasks, follows complex instructions in Chinese and English more reliably, reduces hallucinations, and produces higher-quality responses for open-ended Q&A, writing, and conversation. The model supports over 100 languages with stronger translation and commonsense reasoning, and is optimized for retrieval-augmented generation (RAG) and tool calling, though it does not include a dedicated “thinking” mode.", + "capabilities": ["function-call", "reasoning"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 262144, + "maxOutputTokens": 65536, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 6 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.24 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 0, + "max": 81920 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": false + }, + { + "id": "qwen3-5-plus", + "name": "Qwen3.5 Plus", + "description": "The Qwen 3.5 native vision-language Plus model is built on a hybrid architecture that integrates linear attention mechanisms with sparse mixture-of-experts models, achieving higher inference efficiency. In multiple task evaluations, the 3.5 series has demonstrated outstanding performance comparable to current leading frontier models, with leapfrog improvements over the 3 series in both pure-text and multimodal capabilities. This model version is functionally equivalent to the snapshot model qwen3.5-plus-2026-02-15.", + "capabilities": ["reasoning", "function-call", "image-recognition", "video-recognition"], + "inputModalities": ["text", "image", "video"], + "outputModalities": ["text"], + "contextWindow": 1000000, + "maxOutputTokens": 65536, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.4 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2.4 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.04 + }, + "cacheWrite": { + "currency": "USD", + "perMillionTokens": 0.17125 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 38912 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": false + }, + { + "id": "qwen3-coder-plus", + "name": "Qwen3 Coder Plus", + "description": "Qwen3 Coder Plus is Alibaba's proprietary version of the Open Source Qwen3 Coder 480B A35B. It is a powerful coding agent model specializing in autonomous programming via tool calling and environment interaction, combining coding proficiency with versatile general-purpose abilities.", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 1048576, + "maxOutputTokens": 65536, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1 + }, + "output": { + "currency": "USD", + "perMillionTokens": 5 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.1 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": true + }, + { + "id": "qwen3-next-a3b", + "name": "Qwen3-Next 80B-A3B (Thinking)", + "description": "Qwen3-Next-80B-A3B-Thinking is a reasoning-first chat model in the Qwen3-Next line that excels by outputting structured 'thinking' traces (Chain-of-Thought) by default.\n\nDesigned for hard, multi-step problems, it is ideal for tasks like math proofs, code synthesis, logic puzzles, and agentic planning. Compared to other Qwen3 variants, it offers greater stability during long reasoning chains and is tuned to follow complex instructions without getting repetitive or off-task.\n\nThis model is perfectly suited for agent frameworks, tool use (function calling), and benchmarks where a step-by-step breakdown is required. It leverages throughput-oriented techniques for fast generation of detailed, procedural outputs.", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 32768, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 6 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.015 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 38912 + }, + "interleaved": true + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": true + }, + { + "id": "qwen-vl-plus", + "name": "Qwen-VL Plus", + "description": "Qwen's Enhanced Large Visual Language Model. Significantly upgraded for detailed recognition capabilities and text recognition abilities, supporting ultra-high pixel resolutions up to millions of pixels and extreme aspect ratios for image input. It delivers significant performance across a broad range of visual tasks.\n", + "capabilities": ["function-call", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.21 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.63 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.042 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": false + }, + { + "id": "grok-3-fast", + "name": "Grok 3 Fast", + "capabilities": ["function-call", "web-search"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 25 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 1.25 + } + }, + "family": "grok", + "ownedBy": "xai", + "openWeights": false + }, + { + "id": "grok-4", + "name": "Grok 4", + "description": "Grok 4 is xAI's latest reasoning model with a 256k context window. It supports parallel tool calling, structured outputs, and both image and text inputs. Note that reasoning is not exposed, reasoning cannot be disabled, and the reasoning effort cannot be specified. Pricing increases once the total tokens in a given request is greater than 128k tokens. See more details on the [xAI docs](https://docs.x.ai/docs/models/grok-4-0709)", + "capabilities": ["reasoning", "function-call", "image-recognition", "web-search"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 256000, + "maxOutputTokens": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 15 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.75 + } + }, + "family": "grok", + "ownedBy": "xai", + "openWeights": false + }, + { + "id": "grok-2-vision", + "name": "Grok 2 Vision", + "capabilities": ["function-call", "file-input", "image-recognition", "web-search"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 8192, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 10 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 2 + } + }, + "family": "grok", + "ownedBy": "xai", + "openWeights": false + }, + { + "id": "grok-code-fast-1", + "name": "Grok Code Fast 1", + "description": "Grok Code Fast 1 is a speedy and economical reasoning model that excels at agentic coding. With reasoning traces visible in the response, developers can steer Grok Code for high-quality work flows.", + "capabilities": ["reasoning", "function-call", "web-search"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 256000, + "maxOutputTokens": 10000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.5 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.02 + } + }, + "family": "grok", + "ownedBy": "xai", + "openWeights": false + }, + { + "id": "grok-2", + "name": "Grok 2", + "capabilities": ["function-call", "web-search"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 10 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 2 + } + }, + "family": "grok", + "ownedBy": "xai", + "openWeights": false + }, + { + "id": "grok-3-mini-fast-latest", + "name": "Grok 3 Mini Fast Latest", + "capabilities": ["reasoning", "function-call", "web-search"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.6 + }, + "output": { + "currency": "USD", + "perMillionTokens": 4 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.15 + } + }, + "reasoning": { + "supportedEfforts": ["low", "high"] + }, + "family": "grok", + "ownedBy": "xai", + "openWeights": false + }, + { + "id": "grok-2-vision-1212", + "name": "Grok 2 Vision (1212)", + "description": "grok-2-vision-1212 is the latest vision model in the Grok family, delivering outstanding performance on vision-based tasks and achieving state-of-the-art results in visual mathematical reasoning and document-based question answering. It supports a wide range of visual inputs, including documents, charts, screenshots, and real-world images, making it well-suited for advanced visual understanding and reasoning use cases.\n\nThe price of calling this model in AIhubMix is ​​10% lower than on the official website.", + "capabilities": ["function-call", "file-input", "image-recognition", "web-search"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 8192, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 10 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 2 + } + }, + "family": "grok", + "ownedBy": "xai", + "openWeights": false + }, + { + "id": "grok-3", + "name": "Grok 3", + "description": "Grok 3 is the latest model from xAI. It's their flagship model that excels at enterprise use cases like data extraction, coding, and text summarization. Possesses deep domain knowledge in finance, healthcare, law, and science.\n\n", + "capabilities": ["function-call", "web-search"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 15 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.75 + } + }, + "family": "grok", + "ownedBy": "xai", + "openWeights": false + }, + { + "id": "grok-4-fast", + "name": "Grok 4 Fast", + "description": "Grok 4 Fast is xAI's latest multimodal model with SOTA cost-efficiency and a 2M token context window. It comes in two flavors: non-reasoning and reasoning. Read more about the model on xAI's [news post](http://x.ai/news/grok-4-fast).\n\nReasoning can be enabled/disabled using the `reasoning` `enabled` parameter in the API. [Learn more in our docs](https://openrouter.ai/docs/use-cases/reasoning-tokens#controlling-reasoning-tokens)", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition", "web-search"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 2000000, + "maxOutputTokens": 30000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.5 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.05 + } + }, + "reasoning": { + "supportedEfforts": [] + }, + "family": "grok", + "ownedBy": "xai", + "openWeights": false + }, + { + "id": "grok-2-latest", + "name": "Grok 2 Latest", + "capabilities": ["function-call", "web-search"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 10 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 2 + } + }, + "family": "grok", + "ownedBy": "xai", + "openWeights": false + }, + { + "id": "grok-4-1-fast", + "name": "Grok 4.1 Fast", + "description": "Grok 4.1 Fast is xAI's best agentic tool calling model that shines in real-world use cases like customer support and deep research. 2M context window.\n\nReasoning can be enabled/disabled using the `reasoning` `enabled` parameter in the API. [Learn more in our docs](https://openrouter.ai/docs/use-cases/reasoning-tokens#controlling-reasoning-tokens)", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition", "web-search"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 2000000, + "maxOutputTokens": 30000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.5 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.05 + } + }, + "reasoning": { + "supportedEfforts": [] + }, + "family": "grok", + "ownedBy": "xai", + "openWeights": false + }, + { + "id": "grok-2-1212", + "name": "Grok 2 (1212)", + "capabilities": ["function-call", "web-search"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 10 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 2 + } + }, + "family": "grok", + "ownedBy": "xai", + "openWeights": false + }, + { + "id": "grok-3-fast-latest", + "name": "Grok 3 Fast Latest", + "capabilities": ["function-call", "web-search"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 25 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 1.25 + } + }, + "family": "grok", + "ownedBy": "xai", + "openWeights": false + }, + { + "id": "grok-3-latest", + "name": "Grok 3 Latest", + "capabilities": ["function-call", "web-search"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 15 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.75 + } + }, + "family": "grok", + "ownedBy": "xai", + "openWeights": false + }, + { + "id": "grok-2-vision-latest", + "name": "Grok 2 Vision Latest", + "capabilities": ["function-call", "file-input", "image-recognition", "web-search"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 8192, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 10 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 2 + } + }, + "family": "grok", + "ownedBy": "xai", + "openWeights": false + }, + { + "id": "grok-vision-beta", + "name": "Grok Vision Beta", + "capabilities": ["function-call", "file-input", "image-recognition", "web-search"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 8192, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 15 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 5 + } + }, + "family": "grok-vision", + "ownedBy": "xai", + "openWeights": false + }, + { + "id": "grok-3-mini", + "name": "Grok 3 Mini", + "description": "A lightweight model that thinks before responding. Fast, smart, and great for logic-based tasks that do not require deep domain knowledge. The raw thinking traces are accessible.", + "capabilities": ["reasoning", "function-call", "web-search"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.5 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.075 + } + }, + "reasoning": { + "supportedEfforts": ["low", "high"] + }, + "family": "grok", + "ownedBy": "xai", + "openWeights": false + }, + { + "id": "grok-beta", + "name": "Grok Beta", + "capabilities": ["function-call", "web-search"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 15 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 5 + } + }, + "family": "grok-beta", + "ownedBy": "xai", + "openWeights": false + }, + { + "id": "grok-3-mini-latest", + "name": "Grok 3 Mini Latest", + "capabilities": ["reasoning", "function-call", "web-search"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.5 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.075 + } + }, + "reasoning": { + "supportedEfforts": ["low", "high"] + }, + "family": "grok", + "ownedBy": "xai", + "openWeights": false + }, + { + "id": "grok-3-mini-fast", + "name": "Grok 3 Mini Fast", + "capabilities": ["reasoning", "function-call", "web-search"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.6 + }, + "output": { + "currency": "USD", + "perMillionTokens": 4 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.15 + } + }, + "reasoning": { + "supportedEfforts": ["low", "high"] + }, + "family": "grok", + "ownedBy": "xai", + "openWeights": false + }, + { + "id": "deepseek-r1-distill-qwen", + "name": "DeepSeek R1 Distill Qwen 32B", + "description": "DeepSeek R1 Distill Qwen 32B is a distilled large language model based on [Qwen 2.5 32B](https://huggingface.co/Qwen/Qwen2.5-32B), using outputs from [DeepSeek R1](/deepseek/deepseek-r1). It outperforms OpenAI's o1-mini across various benchmarks, achieving new state-of-the-art results for dense models.\\n\\nOther benchmark results include:\\n\\n- AIME 2024 pass@1: 72.6\\n- MATH-500 pass@1: 94.3\\n- CodeForces Rating: 1691\\n\\nThe model leverages fine-tuning from DeepSeek R1's outputs, enabling competitive performance comparable to larger frontier models.", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 121808, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.2 + } + }, + "family": "qwen", + "ownedBy": "deepseek", + "openWeights": true + }, + { + "id": "qwen2-5-coder-instruct", + "name": "Qwen2.5 Coder 32B Instruct", + "description": "Qwen2.5-Coder-7B-Instruct is a 7B parameter instruction-tuned language model optimized for code-related tasks such as code generation, reasoning, and bug fixing. Based on the Qwen2.5 architecture, it incorporates enhancements like RoPE, SwiGLU, RMSNorm, and GQA attention with support for up to 128K tokens using YaRN-based extrapolation. It is trained on a large corpus of source code, synthetic data, and text-code grounding, providing robust performance across programming languages and agentic coding workflows.\n\nThis model is part of the Qwen2.5-Coder family and offers strong compatibility with tools like vLLM for efficient deployment. Released under the Apache 2.0 license.", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 12952, + "maxOutputTokens": 2048, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.2 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": true + }, + { + "id": "kimi-k2-instruct", + "name": "Kimi K2 Instruct", + "description": "For Claude code only", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 58904, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.2 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.05 + } + }, + "family": "kimi", + "ownedBy": "moonshot", + "openWeights": true + }, + { + "id": "deepseek-r1-distill-llama", + "name": "DeepSeek R1 Distill Llama 70B", + "description": "DeepSeek R1 Distill Llama 70B is a distilled large language model based on [Llama-3.3-70B-Instruct](/meta-llama/llama-3.3-70b-instruct), using outputs from [DeepSeek R1](/deepseek/deepseek-r1). The model combines advanced distillation techniques to achieve high performance across multiple benchmarks, including:\n\n- AIME 2024 pass@1: 70.0\n- MATH-500 pass@1: 94.5\n- CodeForces Rating: 1633\n\nThe model leverages fine-tuning from DeepSeek R1's outputs, enabling competitive performance comparable to larger frontier models.", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 121808, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.2 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.015 + } + }, + "family": "deepseek-thinking", + "ownedBy": "deepseek", + "openWeights": true + }, + { + "id": "kimi-k2-instruct-0905", + "name": "Kimi K2 0905", + "description": "For Claude code only", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 262144, + "maxOutputTokens": 262144, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1 + }, + "output": { + "currency": "USD", + "perMillionTokens": 3 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.195 + } + }, + "family": "kimi", + "ownedBy": "moonshot", + "openWeights": true + }, + { + "id": "nemotron-nano-v2", + "name": "nvidia-nemotron-nano-9b-v2", + "description": "NVIDIA-Nemotron-Nano-9B-v2 is a large language model (LLM) trained from scratch by NVIDIA, and designed as a unified model for both reasoning and non-reasoning tasks. It responds to user queries and tasks by first generating a reasoning trace and then concluding with a final response. \n\nThe model's reasoning capabilities can be controlled via a system prompt. If the user prefers the model to provide its final answer without intermediate reasoning traces, it can be configured to do so.", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 131072, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.04 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.16 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.007 + } + }, + "family": "nemotron", + "ownedBy": "nvidia", + "openWeights": true + }, + { + "id": "cosmos-nemotron", + "name": "Cosmos Nemotron 34B", + "capabilities": ["reasoning", "image-recognition", "video-recognition"], + "inputModalities": ["text", "image", "video"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 8192, + "family": "nemotron", + "ownedBy": "nvidia", + "openWeights": false + }, + { + "id": "llama-embed-nemotron", + "name": "Llama Embed Nemotron 8B", + "capabilities": ["embedding"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 32768, + "maxOutputTokens": 2048, + "family": "llama", + "ownedBy": "meta", + "openWeights": false + }, + { + "id": "nemotron-3-nano-a3b", + "name": "nemotron-3-nano-30b-a3b", + "description": "NVIDIA Nemotron 3 Nano 30B A3B is a small language MoE model with highest compute efficiency and accuracy for developers to build specialized agentic AI systems.\n\nThe model is fully open with open-weights, datasets and recipes so developers can easily\ncustomize, optimize, and deploy the model on their infrastructure for maximum privacy and\nsecurity.\n\nNote: For the free endpoint, all prompts and output are logged to improve the provider's model and its product and services. Please do not upload any personal, confidential, or otherwise sensitive information. This is a trial use only. Do not use for production or business-critical systems.", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 131072, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.06 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.24 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.006 + } + }, + "family": "nemotron", + "ownedBy": "nvidia", + "openWeights": true + }, + { + "id": "parakeet-tdt-v2", + "name": "Parakeet TDT 0.6B v2", + "capabilities": ["audio-recognition"], + "inputModalities": ["audio"], + "outputModalities": ["text"], + "maxOutputTokens": 4096, + "family": "parakeet", + "ownedBy": "nvidia", + "openWeights": false + }, + { + "id": "nemoretriever-ocr-v1", + "name": "NeMo Retriever OCR v1", + "capabilities": ["image-recognition", "file-input"], + "inputModalities": ["image"], + "outputModalities": ["text"], + "maxOutputTokens": 4096, + "family": "nemoretriever", + "ownedBy": "nvidia", + "openWeights": false + }, + { + "id": "llama-3-3-nemotron-super-v1", + "name": "Llama 3.3 Nemotron Super 49b V1", + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4096, + "ownedBy": "meta", + "openWeights": false + }, + { + "id": "llama-3-1-nemotron-instruct", + "name": "Llama 3.1 Nemotron 51b Instruct", + "description": "NVIDIA's Llama 3.1 Nemotron 70B is a language model designed for generating precise and useful responses. Leveraging [Llama 3.1 70B](/models/meta-llama/llama-3.1-70b-instruct) architecture and Reinforcement Learning from Human Feedback (RLHF), it excels in automatic alignment benchmarks. This model is tailored for applications requiring high accuracy in helpfulness and response generation, suitable for diverse user queries across multiple domains.\n\nUsage of this model is subject to [Meta's Acceptable Use Policy](https://www.llama.com/llama3/use-policy/).", + "capabilities": ["function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.2 + } + }, + "ownedBy": "meta", + "openWeights": false + }, + { + "id": "llama3-chatqa-1-5", + "name": "Llama3 Chatqa 1.5 70b", + "capabilities": ["function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4096, + "ownedBy": "meta", + "openWeights": false + }, + { + "id": "llama-3-1-nemotron-ultra-v1", + "name": "Llama-3.1-Nemotron-Ultra-253B-v1", + "description": "Llama-3.1-Nemotron-Ultra-253B-v1 is a large language model (LLM) optimized for advanced reasoning, human-interactive chat, retrieval-augmented generation (RAG), and tool-calling tasks. Derived from Meta’s Llama-3.1-405B-Instruct, it has been significantly customized using Neural Architecture Search (NAS), resulting in enhanced efficiency, reduced memory usage, and improved inference latency. The model supports a context length of up to 128K tokens and can operate efficiently on an 8x NVIDIA H100 node.\n\nNote: you must include `detailed thinking on` in the system prompt to enable reasoning. Please see [Usage Recommendations](https://huggingface.co/nvidia/Llama-3_1-Nemotron-Ultra-253B-v1#quick-start-and-usage-recommendations) for more.", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.6 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.8 + } + }, + "family": "llama", + "ownedBy": "meta", + "openWeights": false + }, + { + "id": "nemotron-4-instruct", + "name": "Nemotron 4 340b Instruct", + "capabilities": ["function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4096, + "ownedBy": "nvidia", + "openWeights": false + }, + { + "id": "llama-3-3-nemotron-super-v1-5", + "name": "Llama 3.3 Nemotron Super 49b V1.5", + "description": "Llama-3.3-Nemotron-Super-49B-v1.5 is a 49B-parameter, English-centric reasoning/chat model derived from Meta’s Llama-3.3-70B-Instruct with a 128K context. It’s post-trained for agentic workflows (RAG, tool calling) via SFT across math, code, science, and multi-turn chat, followed by multiple RL stages; Reward-aware Preference Optimization (RPO) for alignment, RL with Verifiable Rewards (RLVR) for step-wise reasoning, and iterative DPO to refine tool-use behavior. A distillation-driven Neural Architecture Search (“Puzzle”) replaces some attention blocks and varies FFN widths to shrink memory footprint and improve throughput, enabling single-GPU (H100/H200) deployment while preserving instruction following and CoT quality.\n\nIn internal evaluations (NeMo-Skills, up to 16 runs, temp = 0.6, top_p = 0.95), the model reports strong reasoning/coding results, e.g., MATH500 pass@1 = 97.4, AIME-2024 = 87.5, AIME-2025 = 82.71, GPQA = 71.97, LiveCodeBench (24.10–25.02) = 73.58, and MMLU-Pro (CoT) = 79.53. The model targets practical inference efficiency (high tokens/s, reduced VRAM) with Transformers/vLLM support and explicit “reasoning on/off” modes (chat-first defaults, greedy recommended when disabled). Suitable for building agents, assistants, and long-context retrieval systems where balanced accuracy-to-cost and reliable tool use matter.\n", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.1 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.4 + } + }, + "ownedBy": "meta", + "openWeights": false + }, + { + "id": "gemma-3n-e2b-it", + "name": "Gemma 3n E2b It", + "description": "Gemma 3n E2B IT is a multimodal, instruction-tuned model developed by Google DeepMind, designed to operate efficiently at an effective parameter size of 2B while leveraging a 6B architecture. Based on the MatFormer architecture, it supports nested submodels and modular composition via the Mix-and-Match framework. Gemma 3n models are optimized for low-resource deployment, offering 32K context length and strong multilingual and reasoning performance across common benchmarks. This variant is trained on a diverse corpus including code, math, web, and multimodal data.", + "capabilities": ["function-call", "structured-output", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4096, + "family": "gemma", + "ownedBy": "google", + "openWeights": true + }, + { + "id": "codegemma-1-1", + "name": "Codegemma 1.1 7b", + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4096, + "ownedBy": "nvidia", + "openWeights": true + }, + { + "id": "gemma-3n-e4b-it", + "name": "Gemma 3n E4b It", + "description": "Gemma 3n E4B-it is optimized for efficient execution on mobile and low-resource devices, such as phones, laptops, and tablets. It supports multimodal inputs—including text, visual data, and audio—enabling diverse tasks such as text generation, speech recognition, translation, and image analysis. Leveraging innovations like Per-Layer Embedding (PLE) caching and the MatFormer architecture, Gemma 3n dynamically manages memory usage and computational load by selectively activating model parameters, significantly reducing runtime resource requirements.\n\nThis model supports a wide linguistic range (trained in over 140 languages) and features a flexible 32K token context window. Gemma 3n can selectively load parameters, optimizing memory and computational efficiency based on the task or device capabilities, making it well-suited for privacy-focused, offline-capable applications and on-device AI solutions. [Read more in the blog post](https://developers.googleblog.com/en/introducing-gemma-3n/)", + "capabilities": ["function-call", "structured-output", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.02 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.04 + } + }, + "family": "gemma", + "ownedBy": "google", + "openWeights": true + }, + { + "id": "gemma-2-it", + "name": "Gemma 2 2b It", + "description": "Gemma 2 27B by Google is an open model built from the same research and technology used to create the [Gemini models](/models?q=gemini).\n\nGemma models are well-suited for a variety of text generation tasks, including question answering, summarization, and reasoning.\n\nSee the [launch announcement](https://blog.google/technology/developers/google-gemma-2/) for more details. Usage of Gemma is subject to Google's [Gemma Terms of Use](https://ai.google.dev/gemma/terms).", + "capabilities": ["function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.02 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.06 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.002 + } + }, + "family": "gemma", + "ownedBy": "google", + "openWeights": true + }, + { + "id": "gemma-3-it", + "name": "Gemma 3 12b It", + "description": "Gemma 3 introduces multimodality, supporting vision-language input and text outputs. It handles context windows up to 128k tokens, understands over 140 languages, and offers improved math, reasoning, and chat capabilities, including structured outputs and function calling.", + "capabilities": ["function-call", "structured-output", "image-recognition"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.1 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.3 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.01 + } + }, + "family": "gemma", + "ownedBy": "google", + "openWeights": true + }, + { + "id": "codegemma", + "name": "Codegemma 7b", + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4096, + "ownedBy": "nvidia", + "openWeights": true + }, + { + "id": "phi-3-medium-128k-instruct", + "name": "Phi 3 Medium 128k Instruct", + "capabilities": ["function-call", "structured-output", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.17 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.68 + } + }, + "family": "phi", + "ownedBy": "microsoft", + "openWeights": true + }, + { + "id": "phi-3-small-128k-instruct", + "name": "Phi 3 Small 128k Instruct", + "capabilities": ["function-call", "structured-output", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.15 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.6 + } + }, + "family": "phi", + "ownedBy": "microsoft", + "openWeights": true + }, + { + "id": "phi-3-5-vision-instruct", + "name": "Phi 3.5 Vision Instruct", + "capabilities": ["function-call", "structured-output", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.4 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.6 + } + }, + "family": "phi", + "ownedBy": "microsoft", + "openWeights": true + }, + { + "id": "phi-3-small-8k-instruct", + "name": "Phi 3 Small 8k Instruct", + "capabilities": ["function-call", "structured-output", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 8000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.15 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.6 + } + }, + "family": "phi", + "ownedBy": "microsoft", + "openWeights": true + }, + { + "id": "phi-3-5-moe-instruct", + "name": "Phi 3.5 Moe Instruct", + "capabilities": ["function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.16 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.64 + } + }, + "family": "phi", + "ownedBy": "microsoft", + "openWeights": true + }, + { + "id": "phi-4-mini-instruct", + "name": "Phi-4-Mini", + "description": "Microsoft's latest model", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition", "audio-recognition"], + "inputModalities": ["text", "image", "audio"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.08 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.35 + } + }, + "family": "phi", + "ownedBy": "microsoft", + "openWeights": false + }, + { + "id": "phi-3-medium-4k-instruct", + "name": "Phi 3 Medium 4k Instruct", + "capabilities": ["function-call", "structured-output", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 4000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.17 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.68 + } + }, + "family": "phi", + "ownedBy": "microsoft", + "openWeights": true + }, + { + "id": "phi-3-vision-128k-instruct", + "name": "Phi 3 Vision 128k Instruct", + "capabilities": ["function-call", "structured-output", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4096, + "ownedBy": "microsoft", + "openWeights": true + }, + { + "id": "glm4-7", + "name": "GLM-4.7", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 204800, + "maxOutputTokens": 131072, + "family": "glm", + "ownedBy": "zhipu", + "openWeights": true + }, + { + "id": "glm5", + "name": "GLM5", + "capabilities": ["reasoning", "function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 202752, + "maxOutputTokens": 131000, + "family": "glm", + "ownedBy": "zhipu", + "openWeights": true + }, + { + "id": "qwq", + "name": "Qwq 32b", + "description": "QwQ is the reasoning model of the Qwen series. Compared with conventional instruction-tuned models, QwQ, which is capable of thinking and reasoning, can achieve significantly enhanced performance in downstream tasks, especially hard problems. QwQ-32B is the medium-sized reasoning model, which is capable of achieving competitive performance against state-of-the-art reasoning models, e.g., DeepSeek-R1, o1-mini.", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.4 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.4 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": true + }, + { + "id": "devstral-2-instruct-2512", + "name": "Devstral-2-123B-Instruct-2512", + "capabilities": ["reasoning", "function-call", "structured-output", "file-input"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 262144, + "maxOutputTokens": 262144, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.05 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.22 + } + }, + "family": "devstral", + "ownedBy": "mistral", + "openWeights": true + }, + { + "id": "mistral-large-3-instruct-2512", + "name": "Mistral Large 3 675B Instruct 2512", + "capabilities": ["function-call", "structured-output", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 262144, + "maxOutputTokens": 262144, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2 + } + }, + "family": "mistral-large", + "ownedBy": "mistral", + "openWeights": true + }, + { + "id": "ministral-instruct-2512", + "name": "Ministral 3 14B Instruct 2512", + "capabilities": ["function-call", "structured-output", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 262144, + "maxOutputTokens": 262144, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2 + } + }, + "family": "ministral", + "ownedBy": "mistral", + "openWeights": true + }, + { + "id": "mamba-codestral-v0-1", + "name": "Mamba Codestral 7b V0.1", + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4096, + "ownedBy": "nvidia", + "openWeights": true + }, + { + "id": "mistral-large-2-instruct", + "name": "Mistral Large 2 Instruct", + "capabilities": ["function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4096, + "ownedBy": "mistral", + "openWeights": true + }, + { + "id": "codestral-instruct-v0-1", + "name": "Codestral 22b Instruct V0.1", + "capabilities": ["function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4096, + "ownedBy": "mistral", + "openWeights": true + }, + { + "id": "mistral-small-3-1-instruct-2503", + "name": "Mistral Small 3.1 24b Instruct 2503", + "description": "Mistral's latest open-source small model; provided by chutes.ai.", + "capabilities": ["function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.03 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.11 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.015 + } + }, + "family": "chutesai", + "ownedBy": "mistral", + "openWeights": true + }, + { + "id": "llama-3-2-vision-instruct", + "name": "Llama 3.2 11b Vision Instruct", + "description": "Llama 3.2 11B Vision is a multimodal model with 11 billion parameters, designed to handle tasks combining visual and textual data. It excels in tasks such as image captioning and visual question answering, bridging the gap between language generation and visual reasoning. Pre-trained on a massive dataset of image-text pairs, it performs well in complex, high-accuracy image analysis.\n\nIts ability to integrate visual understanding with language processing makes it an ideal solution for industries requiring comprehensive visual-linguistic AI applications, such as content creation, AI-driven customer service, and research.\n\nClick here for the [original model card](https://github.com/meta-llama/llama-models/blob/main/models/llama3_2/MODEL_CARD_VISION.md).\n\nUsage of this model is subject to [Meta's Acceptable Use Policy](https://www.llama.com/llama3/use-policy/).", + "capabilities": ["function-call", "structured-output", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.37 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.37 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.175 + } + }, + "family": "llama", + "ownedBy": "meta", + "openWeights": true + }, + { + "id": "llama3-instruct", + "name": "Llama3 70b Instruct", + "capabilities": ["function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4096, + "ownedBy": "meta", + "openWeights": true + }, + { + "id": "llama-3-3-instruct", + "name": "Llama 3.3 70b Instruct", + "description": "The Meta Llama 3.3 multilingual large language model (LLM) is a pretrained and instruction tuned generative model in 70B (text in/text out). The Llama 3.3 instruction tuned text only model is optimized for multilingual dialogue use cases and outperforms many of the available open source and closed chat models on common industry benchmarks.\n\nSupported languages: English, German, French, Italian, Portuguese, Hindi, Spanish, and Thai.\n\n[Model Card](https://github.com/meta-llama/llama-models/blob/main/models/llama3_3/MODEL_CARD.md)", + "capabilities": ["function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2.92 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2.92 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.013 + } + }, + "family": "llama", + "ownedBy": "meta", + "openWeights": true + }, + { + "id": "llama-3-2-instruct", + "name": "Llama 3.2 1b Instruct", + "description": "Llama 3.2 3B is a 3-billion-parameter multilingual large language model, optimized for advanced natural language processing tasks like dialogue generation, reasoning, and summarization. Designed with the latest transformer architecture, it supports eight languages, including English, Spanish, and Hindi, and is adaptable for additional languages.\n\nTrained on 9 trillion tokens, the Llama 3.2 3B model excels in instruction-following, complex reasoning, and tool use. Its balanced performance makes it ideal for applications needing accuracy and efficiency in text generation across multilingual settings.\n\nClick here for the [original model card](https://github.com/meta-llama/llama-models/blob/main/models/llama3_2/MODEL_CARD.md).\n\nUsage of this model is subject to [Meta's Acceptable Use Policy](https://www.llama.com/llama3/use-policy/).", + "capabilities": ["function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.01 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.01 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.005 + } + }, + "family": "unsloth", + "ownedBy": "meta", + "openWeights": true + }, + { + "id": "llama-4-scout-16e-instruct", + "name": "Llama 4 Scout 17b 16e Instruct", + "capabilities": ["function-call", "structured-output", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.11 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.34 + } + }, + "family": "llama", + "ownedBy": "meta", + "openWeights": true + }, + { + "id": "llama-4-maverick-128e-instruct", + "name": "Llama 4 Maverick 17b 128e Instruct", + "capabilities": ["function-call", "structured-output", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.6 + } + }, + "family": "llama", + "ownedBy": "meta", + "openWeights": true + }, + { + "id": "codellama", + "name": "Codellama 70b", + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4096, + "ownedBy": "nvidia", + "openWeights": true + }, + { + "id": "llama-3-1-instruct", + "name": "Llama 3.1 405b Instruct", + "description": "Meta's latest class of model (Llama 3.1) launched with a variety of sizes & flavors. This 8B instruct-tuned version is fast and efficient.\n\nIt has demonstrated strong performance compared to leading closed-source models in human evaluations.\n\nTo read more about the model release, [click here](https://ai.meta.com/blog/meta-llama-3-1/). Usage of this model is subject to [Meta's Acceptable Use Policy](https://llama.meta.com/llama3/use-policy/).", + "capabilities": ["function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.02 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.05 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.002 + } + }, + "family": "llama", + "ownedBy": "meta", + "openWeights": true + }, + { + "id": "deepseek-r1-0528", + "name": "Deepseek R1 0528", + "description": "May 28th update to the [original DeepSeek R1](/deepseek/deepseek-r1) Performance on par with [OpenAI o1](/openai/o1), but open-sourced and with fully open reasoning tokens. It's 671B parameters in size, with 37B active in an inference pass.\n\nFully open-source model.", + "capabilities": ["reasoning", "function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.7 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2.5 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.08 + } + }, + "family": "deepseek-thinking", + "ownedBy": "deepseek", + "openWeights": true + }, + { + "id": "deepseek-r1", + "name": "Deepseek R1", + "description": "DeepSeek R1 is here: Performance on par with [OpenAI o1](/openai/o1), but open-sourced and with fully open reasoning tokens. It's 671B parameters in size, with 37B active in an inference pass.\n\nFully open-source model & [technical report](https://api-docs.deepseek.com/news/news250120).\n\nMIT licensed: Distill & commercialize freely!", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 7 + } + }, + "family": "deepseek-thinking", + "ownedBy": "deepseek", + "openWeights": true + }, + { + "id": "deepseek-v3-1-terminus", + "name": "DeepSeek V3.1 Terminus", + "description": "DeepSeek-V3.1 Terminus is an update to [DeepSeek V3.1](/deepseek/deepseek-chat-v3.1) that maintains the model's original capabilities while addressing issues reported by users, including language consistency and agent capabilities, further optimizing the model's performance in coding and search agents. It is a large hybrid reasoning model (671B parameters, 37B active) that supports both thinking and non-thinking modes. It extends the DeepSeek-V3 base with a two-phase long-context training process, reaching up to 128K tokens, and uses FP8 microscaling for efficient inference. Users can control the reasoning behaviour with the `reasoning` `enabled` boolean. [Learn more in our docs](https://openrouter.ai/docs/use-cases/reasoning-tokens#enable-reasoning-with-default-config)\n\nThe model improves tool use, code generation, and reasoning efficiency, achieving performance comparable to DeepSeek-R1 on difficult benchmarks while responding more quickly. It supports structured tool calling, code agents, and search agents, making it suitable for research, coding, and agentic workflows. ", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.27 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.135 + } + }, + "reasoning": { + "supportedEfforts": ["none"], + "interleaved": true + }, + "family": "deepseek", + "ownedBy": "deepseek", + "openWeights": false + }, + { + "id": "deepseek-v3-1", + "name": "DeepSeek V3.1", + "description": "Thinking mode of DeepSeek-V3.1; \nDeepSeek V3.1 is a text generation model provided by DeepSeek, featuring a hybrid reasoning architecture that achieves an effective integration of thinking and non-thinking modes.", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.27 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.135 + } + }, + "reasoning": { + "supportedEfforts": ["none"], + "interleaved": true + }, + "family": "deepseek", + "ownedBy": "deepseek", + "openWeights": false + }, + { + "id": "deepseek-coder-instruct", + "name": "Deepseek Coder 6.7b Instruct", + "capabilities": ["function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4096, + "ownedBy": "deepseek", + "openWeights": true + }, + { + "id": "flux.1-dev", + "name": "FLUX.1-dev", + "capabilities": ["image-generation"], + "inputModalities": ["text"], + "outputModalities": ["image"], + "contextWindow": 4096, + "family": "flux", + "ownedBy": "bfl", + "openWeights": false + }, + { + "id": "command-a-translate-08-2025", + "name": "Command A Translate", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 8000, + "maxOutputTokens": 8000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2.5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 10 + } + }, + "family": "command-a", + "ownedBy": "cohere", + "openWeights": true + }, + { + "id": "command-r7b-arabic-02-2025", + "name": "Command R7B Arabic", + "capabilities": ["function-call", "reasoning"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.0375 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.15 + } + }, + "family": "command-r", + "ownedBy": "cohere", + "openWeights": true + }, + { + "id": "command-a-03-2025", + "name": "Command A", + "description": "Command A is Cohere most performant model to date, excelling at tool use, agents, retrieval augmented generation (RAG), and multilingual use cases. Command A has a context length of 256K, only requires two GPUs to run, and has 150% higher throughput compared to Command R+ 08-2024.", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 256000, + "maxOutputTokens": 8000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2.5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 10 + } + }, + "family": "command-a", + "ownedBy": "cohere", + "openWeights": true + }, + { + "id": "command-r-08-2024", + "name": "Command R", + "description": "command-r-08-2024 is an update of the [Command R](/models/cohere/command-r) with improved performance for multilingual retrieval-augmented generation (RAG) and tool use. More broadly, it is better at math, code and reasoning and is competitive with the previous version of the larger Command R+ model.\n\nRead the launch post [here](https://docs.cohere.com/changelog/command-gets-refreshed).\n\nUse of this model is subject to Cohere's [Usage Policy](https://docs.cohere.com/docs/usage-policy) and [SaaS Agreement](https://cohere.com/saas-agreement).", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.15 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.6 + } + }, + "family": "command-r", + "ownedBy": "cohere", + "openWeights": true + }, + { + "id": "command-r-plus-08-2024", + "name": "Command R+", + "description": "command-r-plus-08-2024 is an update of the [Command R+](/models/cohere/command-r-plus) with roughly 50% higher throughput and 25% lower latencies as compared to the previous Command R+ version, while keeping the hardware footprint the same.\n\nRead the launch post [here](https://docs.cohere.com/changelog/command-gets-refreshed).\n\nUse of this model is subject to Cohere's [Usage Policy](https://docs.cohere.com/docs/usage-policy) and [SaaS Agreement](https://cohere.com/saas-agreement).", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2.5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 10 + } + }, + "family": "command-r", + "ownedBy": "cohere", + "openWeights": true + }, + { + "id": "c4ai-aya-vision", + "name": "Aya Vision 32B", + "capabilities": ["function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 16000, + "maxOutputTokens": 4000, + "ownedBy": "cohere", + "openWeights": true + }, + { + "id": "command-r7b-12-2024", + "name": "Command R7B", + "description": "Command R7B (12-2024) is a small, fast update of the Command R+ model, delivered in December 2024. It excels at RAG, tool use, agents, and similar tasks requiring complex reasoning and multiple steps.\n\nUse of this model is subject to Cohere's [Usage Policy](https://docs.cohere.com/docs/usage-policy) and [SaaS Agreement](https://cohere.com/saas-agreement).", + "capabilities": ["function-call", "reasoning"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.0375 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.15 + } + }, + "family": "command-r", + "ownedBy": "cohere", + "openWeights": true + }, + { + "id": "c4ai-aya-expanse", + "name": "Aya Expanse 32B", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4000, + "ownedBy": "cohere", + "openWeights": true + }, + { + "id": "command-a-reasoning-08-2025", + "name": "Command A Reasoning", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 256000, + "maxOutputTokens": 32000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2.5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 10 + } + }, + "family": "command-a", + "ownedBy": "cohere", + "openWeights": true + }, + { + "id": "command-a-vision-07-2025", + "name": "Command A Vision", + "capabilities": ["image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 8000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2.5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 10 + } + }, + "family": "command-a", + "ownedBy": "cohere", + "openWeights": true + }, + { + "id": "solar-mini", + "name": "solar-mini", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 32768, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.15 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.15 + } + }, + "family": "solar-mini", + "ownedBy": "upstageai", + "openWeights": false + }, + { + "id": "solar-pro3", + "name": "solar-pro3", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.25 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.25 + } + }, + "family": "solar-pro", + "ownedBy": "upstageai", + "openWeights": false + }, + { + "id": "solar-pro2", + "name": "solar-pro2", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 65536, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.25 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.25 + } + }, + "family": "solar-pro", + "ownedBy": "upstageai", + "openWeights": false + }, + { + "id": "llama-3-1-instant", + "name": "Llama 3.1 8B Instant", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 131072, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.05 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.08 + } + }, + "family": "llama", + "ownedBy": "meta", + "openWeights": true + }, + { + "id": "mistral-saba", + "name": "Mistral Saba 24B", + "description": "Mistral Saba is a 24B-parameter language model specifically designed for the Middle East and South Asia, delivering accurate and contextually relevant responses while maintaining efficient performance. Trained on curated regional datasets, it supports multiple Indian-origin languages—including Tamil and Malayalam—alongside Arabic. This makes it a versatile option for a range of regional and multilingual applications. Read more at the blog post [here](https://mistral.ai/en/news/mistral-saba)", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 32768, + "maxOutputTokens": 32768, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.79 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.79 + } + }, + "family": "mistral", + "ownedBy": "mistral", + "openWeights": false + }, + { + "id": "llama3-8192", + "name": "Llama 3 8B", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 8192, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.05 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.08 + } + }, + "family": "llama", + "ownedBy": "meta", + "openWeights": true + }, + { + "id": "qwen-qwq", + "name": "Qwen QwQ 32B", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 16384, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.29 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.39 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": true + }, + { + "id": "llama-guard-3", + "name": "Llama Guard 3 8B", + "description": "Llama Guard 3 is a Llama-3.1-8B pretrained model, fine-tuned for content safety classification. Similar to previous versions, it can be used to classify content in both LLM inputs (prompt classification) and in LLM responses (response classification). It acts as an LLM – it generates text in its output that indicates whether a given prompt or response is safe or unsafe, and if unsafe, it also lists the content categories violated.\n\nLlama Guard 3 was aligned to safeguard against the MLCommons standardized hazards taxonomy and designed to support Llama 3.1 capabilities. Specifically, it provides content moderation in 8 languages, and was optimized to support safety and security for search and code interpreter tool calls.\n", + "capabilities": ["structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 8192, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.2 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.002 + } + }, + "family": "llama", + "ownedBy": "meta", + "openWeights": true + }, + { + "id": "gemma2-it", + "name": "Gemma 2 9B", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 8192, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.2 + } + }, + "family": "gemma", + "ownedBy": "google", + "openWeights": true + }, + { + "id": "llama-3-3-versatile", + "name": "Llama 3.3 70B Versatile", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 32768, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.59 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.79 + } + }, + "family": "llama", + "ownedBy": "meta", + "openWeights": true + }, + { + "id": "llama-guard-4", + "name": "Llama Guard 4 12B", + "description": "Llama Guard 4 is a Llama 4 Scout-derived multimodal pretrained model, fine-tuned for content safety classification. Similar to previous versions, it can be used to classify content in both LLM inputs (prompt classification) and in LLM responses (response classification). It acts as an LLM—generating text in its output that indicates whether a given prompt or response is safe or unsafe, and if unsafe, it also lists the content categories violated.\n\nLlama Guard 4 was aligned to safeguard against the standardized MLCommons hazards taxonomy and designed to support multimodal Llama 4 capabilities. Specifically, it combines features from previous Llama Guard models, providing content moderation for English and multiple supported languages, along with enhanced capabilities to handle mixed text-and-image prompts, including multiple images. Additionally, Llama Guard 4 is integrated into the Llama Moderations API, extending robust safety classification to text and images.", + "capabilities": ["image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 1024, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.2 + } + }, + "family": "llama", + "ownedBy": "meta", + "openWeights": true + }, + { + "id": "ling-1t", + "name": "Ling-1T", + "description": "Ling-1T is the first flagship non-thinking model in the “Ling 2.0” series, featuring 1 trillion total parameters and approximately 50 billion active parameters per token. Built on the Ling 2.0 architecture, Ling-1T is designed to push the limits of efficient inference and scalable cognition. Ling-1T-base was pretrained on over 20 trillion high-quality, reasoning-intensive tokens, supports up to a 128K context length, and incorporates an Evolutionary Chain of Thought (Evo-CoT) process during mid-stage and post-stage training. This training regimen greatly enhances the model’s efficiency and depth of reasoning, enabling Ling-1T to achieve top performance across multiple complex reasoning benchmarks, balancing accuracy and efficiency.", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 32000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.57 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2.29 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.11 + } + }, + "family": "ling", + "ownedBy": "bailing", + "openWeights": true + }, + { + "id": "ring-1t", + "name": "Ring-1T", + "description": "Ring-1T is an open-source idea model with a trillion parameters released by the Bailing team. It is based on the Ling 2.0 architecture and the Ling-1T-base foundational model for training, with a total parameter count of 1 trillion, an active parameter count of 50 billion, and supports up to a 128K context window. The model is trained via large-scale verifiable reward reinforcement learning (RLVR), combined with the self-developed Icepop reinforcement learning stabilization method and the efficient ASystem reinforcement learning system, significantly improving the model’s deep reasoning and natural language reasoning capabilities. Ring-1T achieves leading performance among open-source models on high-difficulty reasoning benchmarks such as mathematics competitions (e.g., IMO 2025), code generation (e.g., ICPC World Finals 2025), and logical reasoning.", + "capabilities": ["reasoning"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 32000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.57 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2.29 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.11 + } + }, + "family": "ring", + "ownedBy": "bailing", + "openWeights": true + }, + { + "id": "gpt-5-1-codex", + "name": "GPT-5.1-Codex", + "description": "GPT-5.1-Codex is a specialized version of GPT-5.1 optimized for software engineering and coding workflows. It is designed for both interactive development sessions and long, independent execution of complex engineering tasks. The model supports building projects from scratch, feature development, debugging, large-scale refactoring, and code review. Compared to GPT-5.1, Codex is more steerable, adheres closely to developer instructions, and produces cleaner, higher-quality code outputs. Reasoning effort can be adjusted with the `reasoning.effort` parameter. Read the [docs here](https://openrouter.ai/docs/use-cases/reasoning-tokens#reasoning-effort-level)\n\nCodex integrates into developer environments including the CLI, IDE extensions, GitHub, and cloud tasks. It adapts reasoning effort dynamically—providing fast responses for small tasks while sustaining extended multi-hour runs for large projects. The model is trained to perform structured code reviews, catching critical flaws by reasoning over dependencies and validating behavior against tests. It also supports multimodal inputs such as images or screenshots for UI development and integrates tool use for search, dependency installation, and environment setup. Codex is intended specifically for agentic coding applications.", + "capabilities": ["reasoning", "function-call", "image-recognition", "web-search"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 128000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.125 + }, + "output": { + "currency": "USD", + "perMillionTokens": 9 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.13 + } + }, + "reasoning": { + "supportedEfforts": ["none", "medium", "high"] + }, + "family": "gpt-codex", + "ownedBy": "openai", + "openWeights": false + }, + { + "id": "gpt-5-1-codex-mini", + "name": "GPT-5.1-Codex-mini", + "description": "GPT-5.1-Codex-Mini is a smaller and faster version of GPT-5.1-Codex", + "capabilities": ["reasoning", "function-call", "image-recognition", "web-search"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 128000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.225 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.8 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.03 + } + }, + "reasoning": { + "supportedEfforts": ["none", "medium", "high"] + }, + "family": "gpt-codex", + "ownedBy": "openai", + "openWeights": false + }, + { + "id": "claude-opus-41", + "name": "Claude Opus 4.1", + "capabilities": ["reasoning", "file-input", "image-recognition", "function-call", "computer-use"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 80000, + "maxOutputTokens": 16000, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"] + }, + "family": "claude-opus", + "ownedBy": "anthropic", + "openWeights": false + }, + { + "id": "gpt-5-1-codex-max", + "name": "GPT-5.1-Codex-max", + "description": "GPT-5.1-Codex-Max is OpenAI’s latest agentic coding model, designed for long-running, high-context software development tasks. It is based on an updated version of the 5.1 reasoning stack and trained on agentic workflows spanning software engineering, mathematics, and research. \nGPT-5.1-Codex-Max delivers faster performance, improved reasoning, and higher token efficiency across the development lifecycle. ", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition", "web-search"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 128000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.125 + }, + "output": { + "currency": "USD", + "perMillionTokens": 9 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.13 + } + }, + "reasoning": { + "supportedEfforts": ["none", "medium", "high", "max"] + }, + "family": "gpt-codex", + "ownedBy": "openai", + "openWeights": false + }, + { + "id": "gemini-3-1-pro-preview", + "name": "Gemini 3.1 Pro Preview", + "description": "Gemini 3.1 Pro Preview is Google’s frontier reasoning model, delivering enhanced software engineering performance, improved agentic reliability, and more efficient token usage across complex workflows. Building on the multimodal foundation of the Gemini 3 series, it combines high-precision reasoning across text, image, video, audio, and code with a 1M-token context window. Reasoning Details must be preserved when using multi-turn tool calling, see our docs here: https://openrouter.ai/docs/use-cases/reasoning-tokens#preserving-reasoning. The 3.1 update introduces measurable gains in SWE benchmarks and real-world coding environments, along with stronger autonomous task execution in structured domains such as finance and spreadsheet-based workflows.\n\nDesigned for advanced development and agentic systems, Gemini 3.1 Pro Preview improves long-horizon stability and tool orchestration while increasing token efficiency. It introduces a new medium thinking level to better balance cost, speed, and performance. The model excels in agentic coding, structured planning, multimodal analysis, and workflow automation, making it well-suited for autonomous agents, financial modeling, spreadsheet automation, and high-context enterprise tasks.", + "capabilities": ["reasoning", "function-call", "structured-output", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2.5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 15 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.5 + } + }, + "reasoning": { + "supportedEfforts": ["low", "medium", "high"], + "thinkingTokenLimits": { + "min": 128, + "max": 32768 + } + }, + "family": "gemini-pro", + "ownedBy": "google", + "openWeights": false + }, + { + "id": "claude-sonnet-4", + "name": "Claude Sonnet 4", + "description": "Claude Sonnet 4 significantly enhances the capabilities of its predecessor, Sonnet 3.7, excelling in both coding and reasoning tasks with improved precision and controllability. Achieving state-of-the-art performance on SWE-bench (72.7%), Sonnet 4 balances capability and computational efficiency, making it suitable for a broad range of applications from routine coding tasks to complex software development projects. Key enhancements include improved autonomous codebase navigation, reduced error rates in agent-driven workflows, and increased reliability in following intricate instructions. Sonnet 4 is optimized for practical everyday use, providing advanced reasoning capabilities while maintaining efficiency and responsiveness in diverse internal and external scenarios.\n\nRead more at the [blog post here](https://www.anthropic.com/news/claude-4)", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition", "web-search", "computer-use"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 16000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 15 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.3 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 64000 + } + }, + "family": "claude-sonnet", + "ownedBy": "anthropic", + "openWeights": false + }, + { + "id": "gemini-2-5-flash-preview-05-20", + "name": "gemini-2.5-flash-preview-05-20", + "description": "Gemini-2.5-flash-preview-05-20 is enabled by default for thinking; to disable it, request the name gemini-2.5-flash-preview-05-20-nothink.Only OpenAI-compatible format calls are supported; Gemini SDK is not supported. For the native Gemini SDK, please set the parameter budget=0 directly.", + "capabilities": [ + "function-call", + "structured-output", + "file-input", + "image-recognition", + "audio-recognition", + "video-recognition", + "web-search", + "reasoning" + ], + "inputModalities": ["text", "image", "video", "audio"], + "outputModalities": ["text"], + "contextWindow": 1048576, + "maxOutputTokens": 200000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.135 + }, + "output": { + "currency": "USD", + "perMillionTokens": 3.15 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.0375 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 0, + "max": 24576 + } + }, + "family": "gemini-flash", + "ownedBy": "google", + "openWeights": false + }, + { + "id": "gemini-2-5-flash-lite", + "name": "gemini-2.5-flash-lite", + "description": "Gemini 2.5 Flash-Lite is a lightweight reasoning model in the Gemini 2.5 family, optimized for ultra-low latency and cost efficiency. It offers improved throughput, faster token generation, and better performance across common benchmarks compared to earlier Flash models. By default, \"thinking\" (i.e. multi-pass reasoning) is disabled to prioritize speed, but developers can enable it via the [Reasoning API parameter](https://openrouter.ai/docs/use-cases/reasoning-tokens) to selectively trade off cost for intelligence. ", + "capabilities": [ + "function-call", + "structured-output", + "file-input", + "image-recognition", + "audio-recognition", + "video-recognition", + "web-search", + "reasoning" + ], + "inputModalities": ["text", "image", "video", "audio"], + "outputModalities": ["text"], + "contextWindow": 1048576, + "maxOutputTokens": 65535, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.09 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.36 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.01 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 512, + "max": 24576 + } + }, + "family": "gemini-flash-lite", + "ownedBy": "google", + "openWeights": false + }, + { + "id": "gpt-5-2-pro", + "name": "gpt-5.2-pro", + "description": "GPT-5.2 Pro is OpenAI’s most advanced model, offering major improvements in agentic coding and long context performance over GPT-5 Pro. It is optimized for complex tasks that require step-by-step reasoning, instruction following, and accuracy in high-stakes use cases. It supports test-time routing features and advanced prompt understanding, including user-specified intent like \"think hard about this.\" Improvements include reductions in hallucination, sycophancy, and better performance in coding, writing, and health-related tasks.", + "capabilities": [ + "reasoning", + "function-call", + "structured-output", + "file-input", + "image-recognition", + "web-search" + ], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 400000, + "maxOutputTokens": 128000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 18.9 + }, + "output": { + "currency": "USD", + "perMillionTokens": 151.2 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 2.1 + } + }, + "reasoning": { + "supportedEfforts": ["medium", "high", "max"] + }, + "family": "gpt-pro", + "ownedBy": "openai", + "openWeights": false + }, + { + "id": "gemini-2-5-pro-preview-06-05", + "name": "gemini-2.5-pro-preview-06-05", + "description": "Integrated with Google's official search function.", + "capabilities": [ + "function-call", + "structured-output", + "file-input", + "image-recognition", + "audio-recognition", + "video-recognition", + "web-search", + "reasoning" + ], + "inputModalities": ["text", "image", "video", "audio"], + "outputModalities": ["text"], + "contextWindow": 1048576, + "maxOutputTokens": 200000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.125 + }, + "output": { + "currency": "USD", + "perMillionTokens": 9 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.31 + } + }, + "reasoning": { + "supportedEfforts": ["low", "medium", "high"], + "thinkingTokenLimits": { + "min": 128, + "max": 32768 + } + }, + "family": "gemini-pro", + "ownedBy": "google", + "openWeights": false + }, + { + "id": "gemini-2-5-flash-lite-preview-06-17", + "name": "gemini-2.5-flash-lite-preview-06-17", + "capabilities": [ + "function-call", + "structured-output", + "file-input", + "image-recognition", + "audio-recognition", + "video-recognition", + "web-search", + "reasoning" + ], + "inputModalities": ["text", "video", "image", "audio"], + "outputModalities": ["text"], + "contextWindow": 1048576, + "maxOutputTokens": 65535, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.09 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.36 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.025 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 512, + "max": 24576 + } + }, + "family": "gemini-flash-lite", + "ownedBy": "google", + "openWeights": false + }, + { + "id": "grok-4-0709", + "name": "grok-4-0709", + "capabilities": [ + "function-call", + "structured-output", + "file-input", + "image-recognition", + "reasoning", + "web-search" + ], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 256000, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2.7 + }, + "output": { + "currency": "USD", + "perMillionTokens": 13.5 + } + }, + "family": "grok", + "ownedBy": "xai", + "openWeights": false + }, + { + "id": "o3-mini", + "name": "o3-mini", + "description": "OpenAI o3-mini is a cost-efficient language model optimized for STEM reasoning tasks, particularly excelling in science, mathematics, and coding.\n\nThis model supports the `reasoning_effort` parameter, which can be set to \"high\", \"medium\", or \"low\" to control the thinking time of the model. The default is \"medium\". OpenRouter also offers the model slug `openai/o3-mini-high` to default the parameter to \"high\".\n\nThe model features three adjustable reasoning effort levels and supports key developer capabilities including function calling, structured outputs, and streaming, though it does not include vision processing capabilities.\n\nThe model demonstrates significant improvements over its predecessor, with expert testers preferring its responses 56% of the time and noting a 39% reduction in major errors on complex questions. With medium reasoning effort settings, o3-mini matches the performance of the larger o1 model on challenging reasoning evaluations like AIME and GPQA, while maintaining lower latency and cost.", + "capabilities": [ + "function-call", + "structured-output", + "file-input", + "image-recognition", + "reasoning", + "web-search" + ], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 131072, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.1 + }, + "output": { + "currency": "USD", + "perMillionTokens": 4.4 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.55 + } + }, + "reasoning": { + "supportedEfforts": ["low", "medium", "high"] + }, + "family": "o", + "ownedBy": "openai", + "openWeights": false + }, + { + "id": "gpt-5-codex", + "name": "gpt-5-codex", + "description": "GPT-5-Codex is a specialized version of GPT-5 optimized for software engineering and coding workflows. It is designed for both interactive development sessions and long, independent execution of complex engineering tasks. The model supports building projects from scratch, feature development, debugging, large-scale refactoring, and code review. Compared to GPT-5, Codex is more steerable, adheres closely to developer instructions, and produces cleaner, higher-quality code outputs. Reasoning effort can be adjusted with the `reasoning.effort` parameter. Read the [docs here](https://openrouter.ai/docs/use-cases/reasoning-tokens#reasoning-effort-level)\n\nCodex integrates into developer environments including the CLI, IDE extensions, GitHub, and cloud tasks. It adapts reasoning effort dynamically—providing fast responses for small tasks while sustaining extended multi-hour runs for large projects. The model is trained to perform structured code reviews, catching critical flaws by reasoning over dependencies and validating behavior against tests. It also supports multimodal inputs such as images or screenshots for UI development and integrates tool use for search, dependency installation, and environment setup. Codex is intended specifically for agentic coding applications.", + "capabilities": [ + "function-call", + "structured-output", + "file-input", + "image-recognition", + "web-search", + "reasoning" + ], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 400000, + "maxOutputTokens": 128000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.125 + }, + "output": { + "currency": "USD", + "perMillionTokens": 9 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.125 + } + }, + "reasoning": { + "supportedEfforts": ["low", "medium", "high"] + }, + "family": "gpt-codex", + "ownedBy": "openai", + "openWeights": false + }, + { + "id": "claude-sonnet-4-20250514", + "name": "claude-sonnet-4-20250514", + "capabilities": [ + "function-call", + "structured-output", + "file-input", + "image-recognition", + "reasoning", + "web-search", + "computer-use" + ], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2.7 + }, + "output": { + "currency": "USD", + "perMillionTokens": 13.5 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.3 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 64000 + } + }, + "family": "claude-sonnet", + "ownedBy": "anthropic", + "openWeights": false, + "alias": ["claude-sonnet-4", "claude-sonnet-4-0"] + }, + { + "id": "o4-mini", + "name": "o4-mini", + "description": "OpenAI o4-mini is a compact reasoning model in the o-series, optimized for fast, cost-efficient performance while retaining strong multimodal and agentic capabilities. It supports tool use and demonstrates competitive reasoning and coding performance across benchmarks like AIME (99.5% with Python) and SWE-bench, outperforming its predecessor o3-mini and even approaching o3 in some domains.\n\nDespite its smaller size, o4-mini exhibits high accuracy in STEM tasks, visual problem solving (e.g., MathVista, MMMU), and code editing. It is especially well-suited for high-throughput scenarios where latency or cost is critical. Thanks to its efficient architecture and refined reinforcement learning training, o4-mini can chain tools, generate structured outputs, and solve multi-step tasks with minimal delay—often in under a minute.", + "capabilities": [ + "function-call", + "structured-output", + "file-input", + "image-recognition", + "reasoning", + "web-search" + ], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 100000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.1 + }, + "output": { + "currency": "USD", + "perMillionTokens": 4.4 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.28 + } + }, + "reasoning": { + "supportedEfforts": ["low", "medium", "high"] + }, + "family": "o", + "ownedBy": "openai", + "openWeights": false + }, + { + "id": "claude-opus-4-20250514", + "name": "claude-opus-4-20250514", + "capabilities": [ + "function-call", + "structured-output", + "file-input", + "image-recognition", + "reasoning", + "web-search", + "computer-use" + ], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 32000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 13.5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 67.5 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 1.5 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 32000 + } + }, + "family": "claude-opus", + "ownedBy": "anthropic", + "openWeights": false, + "alias": ["claude-opus-4", "claude-opus-4-0"] + }, + { + "id": "o3", + "name": "o3", + "description": "o3 is a well-rounded and powerful model across domains. It sets a new standard for math, science, coding, and visual reasoning tasks. It also excels at technical writing and instruction-following. Use it to think through multi-step problems that involve analysis across text, code, and images. ", + "capabilities": [ + "function-call", + "structured-output", + "file-input", + "image-recognition", + "reasoning", + "web-search" + ], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 131072, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 10 + }, + "output": { + "currency": "USD", + "perMillionTokens": 40 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.5 + } + }, + "reasoning": { + "supportedEfforts": ["low", "medium", "high"] + }, + "family": "o", + "ownedBy": "openai", + "openWeights": false + }, + { + "id": "gpt-5-chat-latest", + "name": "gpt-5-chat-latest", + "description": "GPT-5 Chat points to the GPT-5 snapshot currently used in ChatGPT. GPT-5 is our next-generation, high-intelligence flagship model. It accepts both text and image inputs, and produces text outputs.", + "capabilities": [ + "function-call", + "structured-output", + "file-input", + "image-recognition", + "web-search", + "reasoning" + ], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 400000, + "maxOutputTokens": 128000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.125 + }, + "output": { + "currency": "USD", + "perMillionTokens": 9 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.12500000000000003 + } + }, + "reasoning": { + "supportedEfforts": ["minimal", "low", "medium", "high"] + }, + "family": "gpt", + "ownedBy": "openai", + "openWeights": false + }, + { + "id": "kimi-k2-0905", + "name": "Kimi K2 0905", + "description": "Kimi K2 0905 is the September update of [Kimi K2 0711](moonshotai/kimi-k2). It is a large-scale Mixture-of-Experts (MoE) language model developed by Moonshot AI, featuring 1 trillion total parameters with 32 billion active per forward pass. It supports long-context inference up to 256k tokens, extended from the previous 128k.\n\nThis update improves agentic coding with higher accuracy and better generalization across scaffolds, and enhances frontend coding with more aesthetic and functional outputs for web, 3D, and related tasks. Kimi K2 is optimized for agentic capabilities, including advanced tool use, reasoning, and code synthesis. It excels across coding (LiveCodeBench, SWE-bench), reasoning (ZebraLogic, GPQA), and tool-use (Tau2, AceBench) benchmarks. The model is trained with a novel stack incorporating the MuonClip optimizer for stable large-scale MoE training.", + "capabilities": ["function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 262144, + "maxOutputTokens": 262144, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.6 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2.5 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.39999999999999997 + } + }, + "family": "kimi", + "ownedBy": "moonshot", + "openWeights": true + }, + { + "id": "deepseek-v3-0324", + "name": "DeepSeek V3 0324", + "capabilities": ["function-call", "structured-output", "reasoning"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 163840, + "maxOutputTokens": 163840, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.28 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.14 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.05 + } + }, + "reasoning": { + "supportedEfforts": ["none"] + }, + "family": "deepseek", + "ownedBy": "deepseek", + "openWeights": true + }, + { + "id": "minimax-m1-80k", + "name": "MiniMax M1", + "description": "MiniMax-M1 is an open-source large-scale hybrid attention model with 456B total parameters (45.9B activated per token). It natively supports 1M-token context and reduces FLOPs by 75% versus DeepSeek R1 in 100K-token generation tasks via lightning attention. Built on MoE architecture and optimized by CISPO algorithm, it achieves state-of-the-art performance in long-context reasoning and real-world software engineering scenarios.", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 1000000, + "maxOutputTokens": 40000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.55 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2.2 + } + }, + "family": "minimax", + "ownedBy": "minimax", + "openWeights": true + }, + { + "id": "ernie-4-5-a47b-paddle", + "name": "ERNIE 4.5 300B A47B", + "capabilities": ["function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 123000, + "maxOutputTokens": 12000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.28 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.1 + } + }, + "family": "ernie", + "ownedBy": "baidu", + "openWeights": true + }, + { + "id": "ernie-4-5-vl-a47b", + "name": "ERNIE 4.5 VL 424B A47B", + "description": "ERNIE-4.5-VL-424B-A47B is a multimodal Mixture-of-Experts (MoE) model from Baidu’s ERNIE 4.5 series, featuring 424B total parameters with 47B active per token. It is trained jointly on text and image data using a heterogeneous MoE architecture and modality-isolated routing to enable high-fidelity cross-modal reasoning, image understanding, and long-context generation (up to 131k tokens). Fine-tuned with techniques like SFT, DPO, UPO, and RLVR, this model supports both “thinking” and non-thinking inference modes. Designed for vision-language tasks in English and Chinese, it is optimized for efficient scaling and can operate under 4-bit/8-bit quantization.", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 123000, + "maxOutputTokens": 16000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.42 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.25 + } + }, + "family": "ernie", + "ownedBy": "baidu", + "openWeights": true + }, + { + "id": "qwen3-a22b-thinking-2507", + "name": "Qwen3 235B A22b Thinking 2507", + "description": "Qwen3-235B-A22B-Thinking-2507 is a high-performance, open-weight Mixture-of-Experts (MoE) language model optimized for complex reasoning tasks. It activates 22B of its 235B parameters per forward pass and natively supports up to 262,144 tokens of context. This \"thinking-only\" variant enhances structured logical reasoning, mathematics, science, and long-form generation, showing strong benchmark performance across AIME, SuperGPQA, LiveCodeBench, and MMLU-Redux. It enforces a special reasoning mode () and is designed for high-token outputs (up to 81,920 tokens) in challenging domains.\n\nThe model is instruction-tuned and excels at step-by-step reasoning, tool use, agentic workflows, and multilingual tasks. This release represents the most capable open-source variant in the Qwen3-235B series, surpassing many closed models in structured reasoning use cases.", + "capabilities": ["reasoning", "function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 131072, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 3 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.055 + } + }, + "reasoning": { + "supportedEfforts": ["low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 38912 + }, + "interleaved": true + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": true + }, + { + "id": "qwen3-a3b-fp8", + "name": "Qwen3 30B A3B", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 40960, + "maxOutputTokens": 20000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.09 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.45 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 38912 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": true + }, + { + "id": "qwen3-fp8", + "name": "Qwen3 32B", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 40960, + "maxOutputTokens": 20000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.1 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.45 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 38912 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": true + }, + { + "id": "qwen3-a22b-fp8", + "name": "Qwen3 235B A22B", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 40960, + "maxOutputTokens": 20000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.8 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 38912 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": true + }, + { + "id": "devstral-medium-2507", + "name": "Devstral Medium", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 128000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.4 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2 + } + }, + "family": "devstral", + "ownedBy": "mistral", + "openWeights": true + }, + { + "id": "open-mixtral-8x22b", + "name": "Mixtral 8x22B", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 64000, + "maxOutputTokens": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 6 + } + }, + "family": "mixtral", + "ownedBy": "mistral", + "openWeights": true + }, + { + "id": "ministral-latest", + "name": "Ministral 8B", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 128000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.1 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.1 + } + }, + "family": "ministral", + "ownedBy": "mistral", + "openWeights": true + }, + { + "id": "pixtral-large-latest", + "name": "Pixtral Large", + "capabilities": ["function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 128000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 6 + } + }, + "family": "pixtral", + "ownedBy": "mistral", + "openWeights": true + }, + { + "id": "mistral-small-2506", + "name": "Mistral Small 3.2", + "capabilities": ["function-call", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 16384, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.1 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.3 + } + }, + "family": "mistral-small", + "ownedBy": "mistral", + "openWeights": true + }, + { + "id": "devstral-2512", + "name": "Devstral 2", + "description": "Devstral 2 is a state-of-the-art open-source model by Mistral AI specializing in agentic coding. It is a 123B-parameter dense transformer model supporting a 256K context window.\n\nDevstral 2 supports exploring codebases and orchestrating changes across multiple files while maintaining architecture-level context. It tracks framework dependencies, detects failures, and retries with corrections—solving challenges like bug fixing and modernizing legacy systems. The model can be fine-tuned to prioritize specific languages or optimize for large enterprise codebases. It is available under a modified MIT license.", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 262144, + "maxOutputTokens": 262144, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.4 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.025 + } + }, + "family": "devstral", + "ownedBy": "mistral", + "openWeights": true + }, + { + "id": "pixtral", + "name": "Pixtral 12B", + "capabilities": ["function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 128000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.15 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.15 + } + }, + "family": "pixtral", + "ownedBy": "mistral", + "openWeights": true + }, + { + "id": "mistral-medium-2505", + "name": "Mistral Medium 3", + "capabilities": ["function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 131072, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.4 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2 + } + }, + "family": "mistral-medium", + "ownedBy": "mistral", + "openWeights": false + }, + { + "id": "labs-devstral-small-2512", + "name": "Devstral Small 2", + "capabilities": ["function-call", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 256000, + "maxOutputTokens": 256000, + "family": "devstral", + "ownedBy": "mistral", + "openWeights": true + }, + { + "id": "devstral-medium-latest", + "name": "Devstral 2", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 262144, + "maxOutputTokens": 262144, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.4 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2 + } + }, + "family": "devstral", + "ownedBy": "mistral", + "openWeights": true + }, + { + "id": "devstral-small-2505", + "name": "Devstral Small 2505", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 128000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.1 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.3 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.025 + } + }, + "family": "devstral", + "ownedBy": "mistral", + "openWeights": true + }, + { + "id": "mistral-medium-2508", + "name": "Mistral Medium 3.1", + "capabilities": ["function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 262144, + "maxOutputTokens": 262144, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.4 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2 + } + }, + "family": "mistral-medium", + "ownedBy": "mistral", + "openWeights": false + }, + { + "id": "mistral-embed", + "name": "Mistral Embed", + "capabilities": ["embedding"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 8000, + "maxOutputTokens": 3072, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.1 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "family": "mistral-embed", + "ownedBy": "mistral", + "openWeights": false + }, + { + "id": "mistral-small-latest", + "name": "Mistral Small", + "capabilities": ["function-call", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 16384, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.1 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.3 + } + }, + "family": "mistral-small", + "ownedBy": "mistral", + "openWeights": true + }, + { + "id": "magistral-small", + "name": "Magistral Small", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 128000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.5 + } + }, + "family": "magistral-small", + "ownedBy": "mistral", + "openWeights": true + }, + { + "id": "devstral-small-2507", + "name": "Devstral Small", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 128000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.1 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.3 + } + }, + "family": "devstral", + "ownedBy": "mistral", + "openWeights": true + }, + { + "id": "codestral-latest", + "name": "Codestral", + "description": "Mistral has launched a new code model - Codestral 25.01; https://mistral.ai/news/codestral-2501/", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 256000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.9 + } + }, + "family": "codestral", + "ownedBy": "mistral", + "openWeights": true + }, + { + "id": "open-mixtral-8x7b", + "name": "Mixtral 8x7B", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 32000, + "maxOutputTokens": 32000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.7 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.7 + } + }, + "family": "mixtral", + "ownedBy": "mistral", + "openWeights": true + }, + { + "id": "mistral-nemo", + "name": "Mistral Nemo", + "description": "A 12B parameter model with a 128k token context length built by Mistral in collaboration with NVIDIA.\n\nThe model is multilingual, supporting English, French, German, Spanish, Italian, Portuguese, Chinese, Japanese, Korean, Arabic, and Hindi.\n\nIt supports function calling and is released under the Apache 2.0 license.", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 128000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.15 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.15 + } + }, + "family": "mistral-nemo", + "ownedBy": "mistral", + "openWeights": true + }, + { + "id": "open-mistral", + "name": "Mistral 7B", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 8000, + "maxOutputTokens": 8000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.25 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.25 + } + }, + "family": "mistral", + "ownedBy": "mistral", + "openWeights": true + }, + { + "id": "mistral-large-latest", + "name": "Mistral Large", + "capabilities": ["function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 262144, + "maxOutputTokens": 262144, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.5 + } + }, + "family": "mistral-large", + "ownedBy": "mistral", + "openWeights": true + }, + { + "id": "mistral-medium-latest", + "name": "Mistral Medium", + "capabilities": ["function-call", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 16384, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.4 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2 + } + }, + "family": "mistral-medium", + "ownedBy": "mistral", + "openWeights": true + }, + { + "id": "mistral-large-2411", + "name": "Mistral Large 2.1", + "description": "Mistral Large 2 2411 is an update of [Mistral Large 2](/mistralai/mistral-large) released together with [Pixtral Large 2411](/mistralai/pixtral-large-2411)\n\nIt provides a significant upgrade on the previous [Mistral Large 24.07](/mistralai/mistral-large-2407), with notable improvements in long context understanding, a new system prompt, and more accurate function calling.", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 16384, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 6 + } + }, + "family": "mistral-large", + "ownedBy": "mistral", + "openWeights": true + }, + { + "id": "magistral-medium-latest", + "name": "Magistral Medium", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 16384, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 5 + } + }, + "family": "magistral-medium", + "ownedBy": "mistral", + "openWeights": true + }, + { + "id": "bielik-v3-0-instruct", + "name": "Bielik 11B v3.0 Instruct", + "capabilities": ["function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 32000, + "maxOutputTokens": 32000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.67 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.67 + } + }, + "ownedBy": "cloudferro-sherlock", + "openWeights": true + }, + { + "id": "bielik-v2-6-instruct", + "name": "Bielik 11B v2.6 Instruct", + "capabilities": ["function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 32000, + "maxOutputTokens": 32000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.67 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.67 + } + }, + "ownedBy": "cloudferro-sherlock", + "openWeights": true + }, + { + "id": "gemini-2-0-flash-001", + "name": "Gemini 2.0 Flash", + "description": "Gemini Flash 2.0 offers a significantly faster time to first token (TTFT) compared to [Gemini Flash 1.5](/google/gemini-flash-1.5), while maintaining quality on par with larger models like [Gemini Pro 1.5](/google/gemini-pro-1.5). It introduces notable enhancements in multimodal understanding, coding capabilities, complex instruction following, and function calling. These advancements come together to deliver more seamless and robust agentic experiences.", + "capabilities": [ + "function-call", + "file-input", + "image-recognition", + "audio-recognition", + "video-recognition", + "web-search" + ], + "inputModalities": ["text", "image", "audio", "video"], + "outputModalities": ["text"], + "contextWindow": 1000000, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.1 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.4 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.025 + } + }, + "family": "gemini-flash", + "ownedBy": "google", + "openWeights": false + }, + { + "id": "route-llm", + "name": "Route LLM", + "capabilities": ["function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 16384, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.5 + } + }, + "family": "gpt", + "ownedBy": "abacus", + "openWeights": false + }, + { + "id": "qwen-2-5-coder", + "name": "Qwen 2.5 Coder 32B", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.79 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.79 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": true + }, + { + "id": "gemini-2-0-pro-exp-02-05", + "name": "Gemini 2.0 Pro Exp", + "description": "Integrated with Google's official search and internet connectivity features.", + "capabilities": [ + "function-call", + "file-input", + "image-recognition", + "audio-recognition", + "video-recognition", + "web-search" + ], + "inputModalities": ["text", "image", "audio", "video"], + "outputModalities": ["text"], + "contextWindow": 2000000, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.25 + }, + "output": { + "currency": "USD", + "perMillionTokens": 5 + } + }, + "family": "gemini-pro", + "ownedBy": "google", + "openWeights": false + }, + { + "id": "o3-pro", + "name": "o3-pro", + "description": "The o-series of models are trained with reinforcement learning to think before they answer and perform complex reasoning. The o3-pro model uses more compute to think harder and provide consistently better answers.\n\nNote that BYOK is required for this model. Set up here: https://openrouter.ai/settings/integrations", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition", "web-search"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 100000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 20 + }, + "output": { + "currency": "USD", + "perMillionTokens": 80 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 20 + } + }, + "reasoning": { + "supportedEfforts": ["low", "medium", "high"] + }, + "family": "o-pro", + "ownedBy": "openai", + "openWeights": false + }, + { + "id": "claude-3-7-sonnet-20250219", + "name": "Claude Sonnet 3.7", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition", "web-search", "computer-use"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 15 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.3 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 64000 + } + }, + "family": "claude-sonnet", + "ownedBy": "anthropic", + "openWeights": false, + "alias": ["claude-3-7-sonnet", "claude-3-7-sonnet-latest"] + }, + { + "id": "gpt-4o-2024-11-20", + "name": "GPT-4o (2024-11-20)", + "description": "The 2024-11-20 version of GPT-4o offers a leveled-up creative writing ability with more natural, engaging, and tailored writing to improve relevance & readability. It’s also better at working with uploaded files, providing deeper insights & more thorough responses.\n\nGPT-4o (\"o\" for \"omni\") is OpenAI's latest AI model, supporting both text and image inputs with text outputs. It maintains the intelligence level of [GPT-4 Turbo](/models/openai/gpt-4-turbo) while being twice as fast and 50% more cost-effective. GPT-4o also offers improved performance in processing non-English languages and enhanced visual capabilities.", + "capabilities": ["function-call", "file-input", "image-recognition", "audio-recognition", "web-search"], + "inputModalities": ["text", "image", "audio"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 16384, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2.5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 10 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 1.25 + } + }, + "family": "gpt", + "ownedBy": "openai", + "openWeights": false + }, + { + "id": "gpt-4o-mini", + "name": "GPT-4o Mini", + "description": "GPT-4o mini is OpenAI's newest model after [GPT-4 Omni](/models/openai/gpt-4o), supporting both text and image inputs with text outputs.\n\nAs their most advanced small model, it is many multiples more affordable than other recent frontier models, and more than 60% cheaper than [GPT-3.5 Turbo](/models/openai/gpt-3.5-turbo). It maintains SOTA intelligence, while being significantly more cost-effective.\n\nGPT-4o mini achieves an 82% score on MMLU and presently ranks higher than GPT-4 on chat preferences [common leaderboards](https://arena.lmsys.org/).\n\nCheck out the [launch announcement](https://openai.com/index/gpt-4o-mini-advancing-cost-efficient-intelligence/) to learn more.\n\n#multimodal", + "capabilities": ["function-call", "file-input", "image-recognition", "web-search"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 16384, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.15 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.6 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.08 + } + }, + "family": "gpt", + "ownedBy": "openai", + "openWeights": false + }, + { + "id": "llama-3-1-instruct-turbo", + "name": "Llama 3.1 405B Instruct Turbo", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 3.5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 3.5 + } + }, + "family": "llama", + "ownedBy": "meta", + "openWeights": true + }, + { + "id": "llama-4-maverick-128e-instruct-fp8", + "name": "Llama 4 Maverick 17B 128E Instruct FP8", + "capabilities": ["function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 1000000, + "maxOutputTokens": 32768, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.14 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.59 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.075 + } + }, + "family": "llama", + "ownedBy": "meta", + "openWeights": true + }, + { + "id": "kimi-k2-turbo", + "name": "Kimi K2 Turbo", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 256000, + "maxOutputTokens": 16384, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2.4 + }, + "output": { + "currency": "USD", + "perMillionTokens": 10 + } + }, + "family": "kimi", + "ownedBy": "moonshot", + "openWeights": false + }, + { + "id": "qwen3-coder", + "name": "Qwen3 Coder 480B A35B Instruct", + "description": "Qwen3-Coder-480B-A35B-Instruct is a Mixture-of-Experts (MoE) code generation model developed by the Qwen team. It is optimized for agentic coding tasks such as function calling, tool use, and long-context reasoning over repositories. The model features 480 billion total parameters, with 35 billion active per forward pass (8 out of 160 experts).\n\nPricing for the Alibaba endpoints varies by context length. Once a request is greater than 128k input tokens, the higher pricing is used.", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 262144, + "maxOutputTokens": 66536, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.38 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.53 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.022 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": false + }, + { + "id": "qwen-3", + "name": "Qwen 3.32B", + "description": "cerebras", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 40960, + "maxOutputTokens": 16384, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.1 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.3 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": false + }, + { + "id": "qwen3-vl-instruct", + "name": "Qwen3 VL Instruct", + "description": "Qwen3-VL-32B-Instruct is a large-scale multimodal vision-language model designed for high-precision understanding and reasoning across text, images, and video. With 32 billion parameters, it combines deep visual perception with advanced text comprehension, enabling fine-grained spatial reasoning, document and scene analysis, and long-horizon video understanding.Robust OCR in 32 languages, and enhanced multimodal fusion through Interleaved-MRoPE and DeepStack architectures. Optimized for agentic interaction and visual tool use, Qwen3-VL-32B delivers state-of-the-art performance for complex real-world multimodal tasks.", + "capabilities": ["function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 129024, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.7 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2.8 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": true + }, + { + "id": "qwen3-vl", + "name": "Qwen3 VL Thinking", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 129024, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.7 + }, + "output": { + "currency": "USD", + "perMillionTokens": 8.4 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 38912 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": true + }, + { + "id": "qwen3-max-preview", + "name": "Qwen3 Max Preview", + "description": "Qwen3-Max-Preview is the latest preview model in the Qwen3 series. This version is functionally equivalent to Qwen3-Max-Thinking — simply set extra_body={\"enable_thinking\": True} to enable the thinking mode. Compared to the Qwen2.5 series, it delivers significant improvements in overall general capabilities, including English–Chinese text understanding, complex instruction following, open-ended reasoning, multilingual processing, and tool-use proficiency. The model also exhibits fewer hallucinations and stronger overall reliability.", + "capabilities": ["function-call", "reasoning"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 262144, + "maxOutputTokens": 32768, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 6 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.24 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 0, + "max": 81920 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": false + }, + { + "id": "longcat-flash", + "name": "LongCat Flash Thinking", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.15 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.5 + } + }, + "family": "longcat", + "ownedBy": "meituan", + "openWeights": false + }, + { + "id": "longcat-flash-chat", + "name": "LongCat Flash Chat", + "description": "LongCat-Flash-Chat is a large-scale Mixture-of-Experts (MoE) model with 560B total parameters, of which 18.6B–31.3B (≈27B on average) are dynamically activated per input. It introduces a shortcut-connected MoE design to reduce communication overhead and achieve high throughput while maintaining training stability through advanced scaling strategies such as hyperparameter transfer, deterministic computation, and multi-stage optimization.\n\nThis release, LongCat-Flash-Chat, is a non-thinking foundation model optimized for conversational and agentic tasks. It supports long context windows up to 128K tokens and shows competitive performance across reasoning, coding, instruction following, and domain benchmarks, with particular strengths in tool use and complex multi-step interactions.", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.8 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.2 + } + }, + "family": "longcat", + "ownedBy": "meituan", + "openWeights": false + }, + { + "id": "grok-imagine-image", + "name": "Grok Imagine Image", + "capabilities": ["image-generation", "web-search"], + "inputModalities": ["text"], + "outputModalities": ["text", "image"], + "family": "grok", + "ownedBy": "xai", + "openWeights": false + }, + { + "id": "grok-imagine-image-pro", + "name": "Grok Imagine Image Pro", + "capabilities": ["image-generation", "web-search"], + "inputModalities": ["text"], + "outputModalities": ["text", "image"], + "family": "grok", + "ownedBy": "xai", + "openWeights": false + }, + { + "id": "nemotron-nano-v2-vl", + "name": "Nvidia Nemotron Nano 12B V2 VL", + "description": "NVIDIA Nemotron Nano 2 VL is a 12-billion-parameter open multimodal reasoning model designed for video understanding and document intelligence. It introduces a hybrid Transformer-Mamba architecture, combining transformer-level accuracy with Mamba’s memory-efficient sequence modeling for significantly higher throughput and lower latency.\n\nThe model supports inputs of text and multi-image documents, producing natural-language outputs. It is trained on high-quality NVIDIA-curated synthetic datasets optimized for optical-character recognition, chart reasoning, and multimodal comprehension.\n\nNemotron Nano 2 VL achieves leading results on OCRBench v2 and scores ≈ 74 average across MMMU, MathVista, AI2D, OCRBench, OCR-Reasoning, ChartQA, DocVQA, and Video-MME—surpassing prior open VL baselines. With Efficient Video Sampling (EVS), it handles long-form videos while reducing inference cost.\n\nOpen-weights, training data, and fine-tuning recipes are released under a permissive NVIDIA open license, with deployment supported across NeMo, NIM, and major inference runtimes.", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 131072, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.6 + } + }, + "family": "nemotron", + "ownedBy": "nvidia", + "openWeights": false + }, + { + "id": "embed-v4-0", + "name": "Embed v4.0", + "capabilities": ["embedding"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 8192, + "maxOutputTokens": 1536, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.12 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "family": "cohere-embed", + "ownedBy": "cohere", + "openWeights": false + }, + { + "id": "command-a", + "name": "Command A", + "description": "Command A is an open-weights 111B parameter model with a 256k context window focused on delivering great performance across agentic, multilingual, and coding use cases.\nCompared to other leading proprietary and open-weights models Command A delivers maximum performance with minimum hardware costs, excelling on business-critical agentic and multilingual tasks.", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 256000, + "maxOutputTokens": 8000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2.5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 10 + } + }, + "family": "command", + "ownedBy": "cohere", + "openWeights": false + }, + { + "id": "kat-coder-pro-v1", + "name": "KAT-Coder-Pro V1", + "capabilities": ["reasoning"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 256000, + "maxOutputTokens": 32000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.2 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.06 + } + }, + "family": "kat-coder", + "ownedBy": "streamlake", + "openWeights": false + }, + { + "id": "mistral", + "name": "Mistral Medium 3.1", + "description": "Mistral Medium 3 is a SOTA & versatile model designed for a wide range of tasks, including programming, mathematical reasoning, understanding long documents, summarization, and dialogue.\n\nIt boasts multi-modal capabilities, enabling it to process visual inputs, and supports dozens of languages, including over 80 coding languages. Additionally, it features function calling and agentic workflows.\n\nMistral Medium 3 is optimized for single-node inference, particularly for long-context applications. Its size allows it to achieve high throughput on a single node.", + "capabilities": ["function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.4 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2 + } + }, + "family": "mistral-medium", + "ownedBy": "mistral", + "openWeights": false + }, + { + "id": "codestral-embed", + "name": "Codestral Embed", + "capabilities": ["embedding"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 8192, + "maxOutputTokens": 1536, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.15 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "family": "codestral-embed", + "ownedBy": "mistral", + "openWeights": false + }, + { + "id": "devstral-2", + "name": "Devstral 2", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 256000, + "maxOutputTokens": 256000, + "family": "devstral", + "ownedBy": "mistral", + "openWeights": false + }, + { + "id": "devstral-small", + "name": "Devstral Small 1.1", + "description": "Devstral Small 1.1 is a 24B parameter open-weight language model for software engineering agents, developed by Mistral AI in collaboration with All Hands AI. Finetuned from Mistral Small 3.1 and released under the Apache 2.0 license, it features a 128k token context window and supports both Mistral-style function calling and XML output formats.\n\nDesigned for agentic coding workflows, Devstral Small 1.1 is optimized for tasks such as codebase exploration, multi-file edits, and integration into autonomous development agents like OpenHands and Cline. It achieves 53.6% on SWE-Bench Verified, surpassing all other open models on this benchmark, while remaining lightweight enough to run on a single 4090 GPU or Apple silicon machine. The model uses a Tekken tokenizer with a 131k vocabulary and is deployable via vLLM, Transformers, Ollama, LM Studio, and other OpenAI-compatible runtimes.\n", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.1 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.3 + } + }, + "family": "devstral", + "ownedBy": "mistral", + "openWeights": false + }, + { + "id": "mistral-large-3", + "name": "Mistral Large 3", + "description": "Mistral Large 3 is a MoE model with 67.5B total parameters and 41B active parameters, supporting a 256K-token context window. Trained from scratch on 3,000 NVIDIA H200 GPUs, it is one of the strongest permissively licensed open-weight models available.\n\nDesigned for advanced reasoning and long-context understanding, Mistral Large 3 delivers performance on par with the best instruction-tuned open-weight models for general-purpose tasks, while also offering image understanding capabilities. Its multilingual strengths are particularly notable for non-English/Chinese languages, making it well-suited for global applications.\n\nTypical use cases include enterprise assistants, multilingual customer support, content generation and editing, data analysis over long documents, code assistance, and research workflows that require handling large corpora or complex instructions. With its MoE architecture, Mistral Large 3 balances strong performance with efficient inference, providing a versatile backbone for building reliable, production-grade AI systems.", + "capabilities": ["file-input", "image-recognition", "function-call"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 256000, + "maxOutputTokens": 256000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.5 + } + }, + "family": "mistral-large", + "ownedBy": "mistral", + "openWeights": false + }, + { + "id": "ministral", + "name": "Ministral 14B", + "capabilities": ["file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 256000, + "maxOutputTokens": 256000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.2 + } + }, + "family": "ministral", + "ownedBy": "mistral", + "openWeights": false + }, + { + "id": "devstral-small-2", + "name": "Devstral Small 2", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 256000, + "maxOutputTokens": 256000, + "family": "devstral", + "ownedBy": "mistral", + "openWeights": false + }, + { + "id": "codestral", + "name": "Codestral", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 256000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.9 + } + }, + "family": "codestral", + "ownedBy": "mistral", + "openWeights": true + }, + { + "id": "magistral", + "name": "Magistral Medium", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 16384, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 5 + } + }, + "family": "magistral-medium", + "ownedBy": "mistral", + "openWeights": true + }, + { + "id": "pixtral-large", + "name": "Pixtral Large", + "capabilities": ["function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 128000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 6 + } + }, + "family": "pixtral", + "ownedBy": "mistral", + "openWeights": true + }, + { + "id": "mistral-small", + "name": "Mistral Small", + "capabilities": ["function-call", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 16384, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.1 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.3 + } + }, + "family": "mistral-small", + "ownedBy": "mistral", + "openWeights": true + }, + { + "id": "mixtral-8x22b-instruct", + "name": "Mixtral 8x22B", + "description": "Mistral's official instruct fine-tuned version of [Mixtral 8x22B](/models/mistralai/mixtral-8x22b). It uses 39B active parameters out of 141B, offering unparalleled cost efficiency for its size. Its strengths include:\n- strong math, coding, and reasoning\n- large context length (64k)\n- fluency in English, French, Italian, German, and Spanish\n\nSee benchmarks on the launch announcement [here](https://mistral.ai/news/mixtral-8x22b/).\n#moe", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 64000, + "maxOutputTokens": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 6 + } + }, + "family": "mixtral", + "ownedBy": "mistral", + "openWeights": true + }, + { + "id": "v0-1-0-md", + "name": "v0-1.0-md", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 32000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 15 + } + }, + "family": "v0", + "ownedBy": "vercel", + "openWeights": false + }, + { + "id": "v0-1-5-md", + "name": "v0-1.5-md", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 32000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 15 + } + }, + "family": "v0", + "ownedBy": "vercel", + "openWeights": false + }, + { + "id": "deepseek-v3", + "name": "DeepSeek V3 0324", + "description": "It has been automatically upgraded to the latest released version, 250324.\nAutomatically upgraded to the latest released version 250324.", + "capabilities": ["function-call", "reasoning"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 163840, + "maxOutputTokens": 16384, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.77 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.77 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.07 + } + }, + "reasoning": { + "supportedEfforts": ["none"] + }, + "family": "deepseek", + "ownedBy": "deepseek", + "openWeights": false + }, + { + "id": "deepseek-v3-2-exp", + "name": "DeepSeek V3.2 Exp", + "description": "DeepSeek-V3.2-Exp is an experimental large language model released by DeepSeek as an intermediate step between V3.1 and future architectures. It introduces DeepSeek Sparse Attention (DSA), a fine-grained sparse attention mechanism designed to improve training and inference efficiency in long-context scenarios while maintaining output quality. Users can control the reasoning behaviour with the `reasoning` `enabled` boolean. [Learn more in our docs](https://openrouter.ai/docs/use-cases/reasoning-tokens#enable-reasoning-with-default-config)\n\nThe model was trained under conditions aligned with V3.1-Terminus to enable direct comparison. Benchmarking shows performance roughly on par with V3.1 across reasoning, coding, and agentic tool-use tasks, with minor tradeoffs and gains depending on the domain. This release focuses on validating architectural optimizations for extended context lengths rather than advancing raw task accuracy, making it primarily a research-oriented model for exploring efficient transformer designs.", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 163840, + "maxOutputTokens": 163840, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.27 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.4 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.0274 + } + }, + "reasoning": { + "supportedEfforts": ["none"] + }, + "family": "deepseek", + "ownedBy": "deepseek", + "openWeights": false + }, + { + "id": "recraft-v3", + "name": "Recraft V3", + "capabilities": ["image-generation"], + "inputModalities": ["text"], + "outputModalities": ["image"], + "contextWindow": 512, + "family": "recraft", + "ownedBy": "recraft", + "openWeights": false + }, + { + "id": "recraft-v2", + "name": "Recraft V2", + "capabilities": ["image-generation"], + "inputModalities": ["text"], + "outputModalities": ["image"], + "contextWindow": 512, + "family": "recraft", + "ownedBy": "recraft", + "openWeights": false + }, + { + "id": "flux-kontext-pro", + "name": "FLUX.1 Kontext Pro", + "capabilities": ["image-generation"], + "inputModalities": ["text"], + "outputModalities": ["image"], + "contextWindow": 512, + "family": "flux", + "ownedBy": "bfl", + "openWeights": false + }, + { + "id": "flux-kontext-max", + "name": "FLUX.1 Kontext Max", + "capabilities": ["image-generation"], + "inputModalities": ["text"], + "outputModalities": ["image"], + "contextWindow": 512, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "family": "flux", + "ownedBy": "bfl", + "openWeights": false + }, + { + "id": "flux-pro-1-1", + "name": "FLUX1.1 [pro]", + "capabilities": ["image-generation"], + "inputModalities": ["text"], + "outputModalities": ["image"], + "contextWindow": 512, + "family": "flux", + "ownedBy": "bfl", + "openWeights": false + }, + { + "id": "flux-pro-1-1-ultra", + "name": "FLUX1.1 [pro] Ultra", + "capabilities": ["image-generation"], + "inputModalities": ["text"], + "outputModalities": ["image"], + "contextWindow": 512, + "family": "flux", + "ownedBy": "bfl", + "openWeights": false + }, + { + "id": "flux-pro-1-0-fill", + "name": "FLUX.1 Fill [pro]", + "capabilities": ["image-generation"], + "inputModalities": ["text"], + "outputModalities": ["image"], + "contextWindow": 512, + "family": "flux", + "ownedBy": "bfl", + "openWeights": false + }, + { + "id": "trinity-mini", + "name": "Trinity Mini", + "description": "Trinity Mini is a 26B-parameter (3B active) sparse mixture-of-experts language model featuring 128 experts with 8 active per token. Engineered for efficient reasoning over long contexts (131k) with robust function calling and multi-step agent workflows.", + "capabilities": ["function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 131072, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.05 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.15 + } + }, + "family": "trinity", + "ownedBy": "arceeai", + "openWeights": false + }, + { + "id": "trinity-large-preview", + "name": "Trinity Large Preview", + "description": "Trinity-Large-Preview is a frontier-scale open-weight language model from Arcee, built as a 400B-parameter sparse Mixture-of-Experts with 13B active parameters per token using 4-of-256 expert routing. \n\nIt excels in creative writing, storytelling, role-play, chat scenarios, and real-time voice assistance, better than your average reasoning model usually can. But we’re also introducing some of our newer agentic performance. It was trained to navigate well in agent harnesses like OpenCode, Cline, and Kilo Code, and to handle complex toolchains and long, constraint-filled prompts. \n\nThe architecture natively supports very long context windows up to 512k tokens, with the Preview API currently served at 128k context using 8-bit quantization for practical deployment. Trinity-Large-Preview reflects Arcee’s efficiency-first design philosophy, offering a production-oriented frontier model with open weights and permissive licensing suitable for real-world applications and experimentation.", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131000, + "maxOutputTokens": 131000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.25 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1 + } + }, + "family": "trinity", + "ownedBy": "arceeai", + "openWeights": false + }, + { + "id": "minimax-m2-1-lightning", + "name": "MiniMax M2.1 Lightning", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 204800, + "maxOutputTokens": 131072, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2.4 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.03 + } + }, + "reasoning": { + "supportedEfforts": [], + "interleaved": true + }, + "family": "minimax", + "ownedBy": "minimax", + "openWeights": false + }, + { + "id": "seed-1-6", + "name": "Seed 1.6", + "description": "Seed 1.6 is a general-purpose model released by the ByteDance Seed team. It incorporates multimodal capabilities and adaptive deep thinking with a 256K context window.", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 256000, + "maxOutputTokens": 32000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.25 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.05 + } + }, + "family": "seed", + "ownedBy": "bytedance", + "openWeights": false + }, + { + "id": "seed-1-8", + "name": "Seed 1.8", + "capabilities": ["reasoning", "function-call", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 256000, + "maxOutputTokens": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.25 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.05 + } + }, + "family": "seed", + "ownedBy": "bytedance", + "openWeights": false + }, + { + "id": "voyage-code-2", + "name": "voyage-code-2", + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 8192, + "maxOutputTokens": 1536, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.12 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "family": "voyage", + "ownedBy": "voyage", + "openWeights": false + }, + { + "id": "voyage-3-5-lite", + "name": "voyage-3.5-lite", + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 8192, + "maxOutputTokens": 1536, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.02 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "family": "voyage", + "ownedBy": "voyage", + "openWeights": false + }, + { + "id": "voyage-3-5", + "name": "voyage-3.5", + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 8192, + "maxOutputTokens": 1536, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.06 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "family": "voyage", + "ownedBy": "voyage", + "openWeights": false + }, + { + "id": "voyage-finance-2", + "name": "voyage-finance-2", + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 8192, + "maxOutputTokens": 1536, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.12 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "family": "voyage", + "ownedBy": "voyage", + "openWeights": false + }, + { + "id": "voyage-law-2", + "name": "voyage-law-2", + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 8192, + "maxOutputTokens": 1536, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.12 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "family": "voyage", + "ownedBy": "voyage", + "openWeights": false + }, + { + "id": "voyage-code-3", + "name": "voyage-code-3", + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 8192, + "maxOutputTokens": 1536, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.18 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "family": "voyage", + "ownedBy": "voyage", + "openWeights": false + }, + { + "id": "voyage-3-large", + "name": "voyage-3-large", + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 8192, + "maxOutputTokens": 1536, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.18 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "family": "voyage", + "ownedBy": "voyage", + "openWeights": false + }, + { + "id": "gemini-embedding-001", + "name": "Gemini Embedding 001", + "description": "gemini-embedding-001 provides a unified cutting edge experience across domains, including science, legal, finance, and coding. This embedding model has consistently held a top spot on the Massive Text Embedding Benchmark (MTEB) Multilingual leaderboard since the experimental launch in March.", + "capabilities": ["embedding"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 8192, + "maxOutputTokens": 1536, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.15 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "family": "gemini-embedding", + "ownedBy": "google", + "openWeights": false + }, + { + "id": "imagen-4-0-ultra-generate-001", + "name": "Imagen 4 Ultra", + "capabilities": ["image-generation"], + "inputModalities": ["text"], + "outputModalities": ["image"], + "contextWindow": 480, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2 + } + }, + "family": "imagen", + "ownedBy": "google", + "openWeights": false + }, + { + "id": "imagen-4-0-fast-generate-001", + "name": "Imagen 4 Fast", + "description": "Imagen 4 is a new-generation image generation model designed to balance high-quality output, inference efficiency, and content safety. It supports image generation, digital watermarking with authenticity verification, user-configurable safety settings, and prompt enhancement via the Prompt Rewriter, while also delivering reliable person generation capabilities. The model ID is imagen-4.0-generate-001, making it suitable for professional creation, design workflows, and various generative AI applications.", + "capabilities": ["image-generation"], + "inputModalities": ["text"], + "outputModalities": ["image"], + "contextWindow": 480, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2 + } + }, + "family": "imagen", + "ownedBy": "google", + "openWeights": false + }, + { + "id": "imagen-4-0-generate-001", + "name": "Imagen 4", + "description": "Imagen 4 is a new-generation image generation model designed to balance high-quality output, inference efficiency, and content safety. It supports image generation, digital watermarking with authenticity verification, user-configurable safety settings, and prompt enhancement via the Prompt Rewriter, while also delivering reliable person generation capabilities. The model ID is imagen-4.0-generate-001, making it suitable for professional creation, design workflows, and various generative AI applications.", + "capabilities": ["image-generation"], + "inputModalities": ["text"], + "outputModalities": ["image"], + "contextWindow": 480, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2 + } + }, + "family": "imagen", + "ownedBy": "google", + "openWeights": false + }, + { + "id": "text-multilingual-embedding-002", + "name": "Text Multilingual Embedding 002", + "capabilities": ["embedding"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 8192, + "maxOutputTokens": 1536, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.03 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "family": "text-embedding", + "ownedBy": "google", + "openWeights": false + }, + { + "id": "gemini-3-pro-image", + "name": "Nano Banana Pro (Gemini 3 Pro Image)", + "capabilities": ["image-generation", "function-call", "image-recognition", "web-search"], + "inputModalities": ["text"], + "outputModalities": ["text", "image"], + "contextWindow": 65536, + "maxOutputTokens": 32768, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 120 + } + }, + "family": "gemini-pro", + "ownedBy": "google", + "openWeights": false + }, + { + "id": "text-embedding-005", + "name": "Text Embedding 005", + "capabilities": ["embedding"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 8192, + "maxOutputTokens": 1536, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.03 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "family": "text-embedding", + "ownedBy": "google", + "openWeights": false + }, + { + "id": "gemini-2-5-flash-image-preview", + "name": "Nano Banana Preview (Gemini 2.5 Flash Image Preview)", + "description": "Aihubmix supports the gemini-2.5-flash-image-preview model; you can add extra parameters modalities=[\"text\", \"image\"] through the OpenAI-compatible chat interface; https://docs.aihubmix.com/en/api/Gemini-Guides#gemini-2-5-flash%3A-quick-task-support", + "capabilities": ["image-generation", "function-call", "image-recognition", "reasoning"], + "inputModalities": ["text"], + "outputModalities": ["text", "image"], + "contextWindow": 32768, + "maxOutputTokens": 32768, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2.5 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.075 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 0, + "max": 24576 + } + }, + "family": "gemini-flash", + "ownedBy": "google", + "openWeights": false + }, + { + "id": "gemini-3-flash", + "name": "Gemini 3 Flash", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition", "web-search"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 1000000, + "maxOutputTokens": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 3 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.05 + } + }, + "reasoning": { + "supportedEfforts": ["minimal", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 0, + "max": 24576 + } + }, + "family": "gemini-flash", + "ownedBy": "google", + "openWeights": false + }, + { + "id": "gemini-2-0-flash", + "name": "Gemini 2.0 Flash", + "description": "gemini-2.0-flash-free is the free, publicly available version of gemini-2.0-flash, offering the same model capabilities with usage limits in place to ensure service stability. Limits include up to 5 requests per minute, a maximum of 500 requests per day, and a daily quota of 1,000,000 tokens. Free usage is based on shared capacity and is limited in availability. This version is intended for testing and light usage; for consistent and reliable access, please switch to the paid model.", + "capabilities": [ + "function-call", + "structured-output", + "file-input", + "image-recognition", + "audio-recognition", + "video-recognition", + "web-search" + ], + "inputModalities": ["text", "image", "audio", "video"], + "outputModalities": ["text"], + "contextWindow": 1048576, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.1 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.4 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.025 + } + }, + "family": "gemini-flash", + "ownedBy": "google", + "openWeights": false + }, + { + "id": "intellect-3", + "name": "INTELLECT 3", + "description": "INTELLECT-3 is a 106B-parameter Mixture-of-Experts model (12B active) post-trained from GLM-4.5-Air-Base using supervised fine-tuning (SFT) followed by large-scale reinforcement learning (RL). It offers state-of-the-art performance for its size across math, code, science, and general reasoning, consistently outperforming many larger frontier models. Designed for strong multi-step problem solving, it maintains high accuracy on structured tasks while remaining efficient at inference thanks to its MoE architecture.", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 131072, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.1 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.02 + } + }, + "family": "intellect", + "ownedBy": "vercel", + "openWeights": false + }, + { + "id": "mercury-coder-small", + "name": "Mercury Coder Small Beta", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 32000, + "maxOutputTokens": 16384, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.25 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1 + } + }, + "family": "mercury", + "ownedBy": "inception", + "openWeights": false + }, + { + "id": "text-embedding-3-small", + "name": "text-embedding-3-small", + "description": " text-embedding-3-small is OpenAI's improved, more performant version of the ada embedding model. Embeddings are a numerical representation of text that can be used to measure the relatedness between two pieces of text. Embeddings are useful for search, clustering, recommendations, anomaly detection, and classification tasks.", + "capabilities": ["embedding"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 8192, + "maxOutputTokens": 1536, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.02 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "family": "text-embedding", + "ownedBy": "openai", + "openWeights": false + }, + { + "id": "gpt-5-2-chat", + "name": "GPT-5.2 Chat", + "description": "GPT-5.2 Chat (AKA Instant) is the fast, lightweight member of the 5.2 family, optimized for low-latency chat while retaining strong general intelligence. It uses adaptive reasoning to selectively “think” on harder queries, improving accuracy on math, coding, and multi-step tasks without slowing down typical conversations. The model is warmer and more conversational by default, with better instruction following and more stable short-form reasoning. GPT-5.2 Chat is designed for high-throughput, interactive workloads where responsiveness and consistency matter more than deep deliberation.", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition", "web-search"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 16384, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.75 + }, + "output": { + "currency": "USD", + "perMillionTokens": 14 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.18 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high", "max"] + }, + "family": "gpt", + "ownedBy": "openai", + "openWeights": false + }, + { + "id": "gpt-oss-safeguard", + "name": "gpt-oss-safeguard-20b", + "description": "gpt-oss-safeguard-20b is a safety reasoning model from OpenAI built upon gpt-oss-20b. This open-weight, 21B-parameter Mixture-of-Experts (MoE) model offers lower latency for safety tasks like content classification, LLM filtering, and trust & safety labeling.\n\nLearn more about this model in OpenAI's gpt-oss-safeguard [user guide](https://cookbook.openai.com/articles/gpt-oss-safeguard-guide).", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 65536, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.08 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.3 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.04 + } + }, + "reasoning": { + "supportedEfforts": ["low", "medium", "high"] + }, + "family": "gpt-oss", + "ownedBy": "openai", + "openWeights": false + }, + { + "id": "gpt-5-chat", + "name": "GPT-5 Chat", + "description": "GPT-5 Chat is designed for advanced, natural, multimodal, and context-aware conversations for enterprise applications.", + "capabilities": [ + "reasoning", + "function-call", + "file-input", + "image-recognition", + "image-generation", + "web-search" + ], + "inputModalities": ["text", "image"], + "outputModalities": ["text", "image"], + "contextWindow": 128000, + "maxOutputTokens": 16384, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.25 + }, + "output": { + "currency": "USD", + "perMillionTokens": 10 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.13 + } + }, + "reasoning": { + "supportedEfforts": ["minimal", "low", "medium", "high"] + }, + "family": "gpt", + "ownedBy": "openai", + "openWeights": false + }, + { + "id": "o3-deep-research", + "name": "o3-deep-research", + "description": "o3-deep-research is OpenAI's advanced model for deep research, designed to tackle complex, multi-step research tasks.\n\nNote: This model always uses the 'web_search' tool which adds additional cost.", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition", "web-search"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 100000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 10 + }, + "output": { + "currency": "USD", + "perMillionTokens": 40 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 2.5 + } + }, + "reasoning": { + "supportedEfforts": ["medium"] + }, + "family": "o", + "ownedBy": "openai", + "openWeights": false + }, + { + "id": "gpt-3-5-turbo", + "name": "GPT-3.5 Turbo", + "description": "GPT-3.5 Turbo is OpenAI's fastest model. It can understand and generate natural language or code, and is optimized for chat and traditional completion tasks.\n\nTraining data up to Sep 2021.", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 16385, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.5 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 1.25 + } + }, + "family": "gpt", + "ownedBy": "openai", + "openWeights": false + }, + { + "id": "text-embedding-3-large", + "name": "text-embedding-3-large", + "description": "text-embedding-3-large is OpenAI's most capable embedding model for both english and non-english tasks. Embeddings are a numerical representation of text that can be used to measure the relatedness between two pieces of text. Embeddings are useful for search, clustering, recommendations, anomaly detection, and classification tasks.", + "capabilities": ["embedding"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 8192, + "maxOutputTokens": 1536, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.13 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "family": "text-embedding", + "ownedBy": "openai", + "openWeights": false + }, + { + "id": "gpt-5-1-instant", + "name": "GPT-5.1 Instant", + "capabilities": [ + "reasoning", + "function-call", + "file-input", + "image-recognition", + "image-generation", + "web-search" + ], + "inputModalities": ["text", "image"], + "outputModalities": ["text", "image"], + "contextWindow": 128000, + "maxOutputTokens": 16384, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.25 + }, + "output": { + "currency": "USD", + "perMillionTokens": 10 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.13 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"] + }, + "family": "gpt", + "ownedBy": "openai", + "openWeights": false + }, + { + "id": "text-embedding-ada-002", + "name": "text-embedding-ada-002", + "description": "text-embedding-ada-002 is OpenAI's legacy text embedding model.", + "capabilities": ["embedding"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 8192, + "maxOutputTokens": 1536, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.1 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "family": "text-embedding", + "ownedBy": "openai", + "openWeights": false + }, + { + "id": "gpt-3-5-turbo-instruct", + "name": "GPT-3.5 Turbo Instruct", + "description": "This model is a variant of GPT-3.5 Turbo tuned for instructional prompts and omitting chat-related optimizations. Training data: up to Sep 2021.", + "capabilities": ["function-call", "file-input", "image-recognition"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 8192, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2 + } + }, + "family": "gpt", + "ownedBy": "openai", + "openWeights": false + }, + { + "id": "codex-mini", + "name": "Codex Mini", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 100000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 6 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.38 + } + }, + "family": "gpt-codex-mini", + "ownedBy": "vercel", + "openWeights": false + }, + { + "id": "gpt-4o-mini-search-preview", + "name": "GPT 4o Mini Search Preview", + "description": "GPT-4o mini Search Preview is a specialized model for web search in Chat Completions. It is trained to understand and execute web search queries.", + "capabilities": ["function-call", "image-recognition", "web-search"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 16384, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.15 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.6 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.075 + } + }, + "family": "gpt-mini", + "ownedBy": "openai", + "openWeights": false + }, + { + "id": "o1", + "name": "o1", + "description": "The latest and strongest model family from OpenAI, o1 is designed to spend more time thinking before responding. The o1 model series is trained with large-scale reinforcement learning to reason using chain of thought. \n\nThe o1 models are optimized for math, science, programming, and other STEM-related tasks. They consistently exhibit PhD-level accuracy on benchmarks in physics, chemistry, and biology. Learn more in the [launch announcement](https://openai.com/o1).\n", + "capabilities": ["reasoning", "function-call", "structured-output", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 100000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 15 + }, + "output": { + "currency": "USD", + "perMillionTokens": 60 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 7.5 + } + }, + "reasoning": { + "supportedEfforts": ["low", "medium", "high"] + }, + "family": "o", + "ownedBy": "openai", + "openWeights": false + }, + { + "id": "gpt-4-turbo", + "name": "GPT-4 Turbo", + "description": "The latest GPT-4 Turbo model with vision capabilities. Vision requests can now use JSON mode and function calling.\n\nTraining data: up to December 2023.", + "capabilities": ["function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 10 + }, + "output": { + "currency": "USD", + "perMillionTokens": 30 + } + }, + "family": "gpt", + "ownedBy": "openai", + "openWeights": false + }, + { + "id": "sonar-reasoning", + "name": "Sonar Reasoning", + "capabilities": ["reasoning", "web-search"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 127000, + "maxOutputTokens": 8000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1 + }, + "output": { + "currency": "USD", + "perMillionTokens": 5 + } + }, + "family": "sonar-reasoning", + "ownedBy": "perplexity", + "openWeights": false + }, + { + "id": "sonar", + "name": "Sonar", + "description": "Sonar is lightweight, affordable, fast, and simple to use — now featuring citations and the ability to customize sources. It is designed for companies seeking to integrate lightweight question-and-answer features optimized for speed.", + "capabilities": ["function-call", "file-input", "image-recognition", "web-search"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 127000, + "maxOutputTokens": 8000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1 + } + }, + "family": "sonar", + "ownedBy": "perplexity", + "openWeights": false + }, + { + "id": "sonar-pro", + "name": "Sonar Pro", + "description": "Note: Sonar Pro pricing includes Perplexity search pricing. See [details here](https://docs.perplexity.ai/guides/pricing#detailed-pricing-breakdown-for-sonar-reasoning-pro-and-sonar-pro)\n\nFor enterprises seeking more advanced capabilities, the Sonar Pro API can handle in-depth, multi-step queries with added extensibility, like double the number of citations per search as Sonar on average. Plus, with a larger context window, it can handle longer and more nuanced searches and follow-up questions. ", + "capabilities": ["function-call", "file-input", "image-recognition", "web-search"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 8000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 15 + } + }, + "family": "sonar-pro", + "ownedBy": "perplexity", + "openWeights": false + }, + { + "id": "sonar-reasoning-pro", + "name": "Sonar Reasoning Pro", + "description": "Note: Sonar Pro pricing includes Perplexity search pricing. See [details here](https://docs.perplexity.ai/guides/pricing#detailed-pricing-breakdown-for-sonar-reasoning-pro-and-sonar-pro)\n\nSonar Reasoning Pro is a premier reasoning model powered by DeepSeek R1 with Chain of Thought (CoT). Designed for advanced use cases, it supports in-depth, multi-step queries with a larger context window and can surface more citations per search, enabling more comprehensive and extensible responses.", + "capabilities": ["reasoning", "web-search"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 127000, + "maxOutputTokens": 8000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 8 + } + }, + "family": "sonar-reasoning", + "ownedBy": "perplexity", + "openWeights": false + }, + { + "id": "glm-4-6v-flash", + "name": "GLM-4.6V-Flash", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 24000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.02 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.21 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.0043 + } + }, + "reasoning": { + "supportedEfforts": ["none"], + "interleaved": true + }, + "family": "glm", + "ownedBy": "zhipu", + "openWeights": false + }, + { + "id": "nova-2-lite", + "name": "Nova 2 Lite", + "capabilities": ["reasoning", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 1000000, + "maxOutputTokens": 1000000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2.5 + } + }, + "family": "nova", + "ownedBy": "amazon", + "openWeights": false + }, + { + "id": "titan-embed-text-v2", + "name": "Titan Text Embeddings V2", + "capabilities": ["embedding"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 8192, + "maxOutputTokens": 1536, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.02 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "family": "titan-embed", + "ownedBy": "vercel", + "openWeights": false + }, + { + "id": "nova-micro", + "name": "Nova Micro", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.035 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.14 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.00875 + } + }, + "family": "nova-micro", + "ownedBy": "amazon", + "openWeights": false + }, + { + "id": "nova-pro", + "name": "Nova Pro", + "capabilities": ["function-call", "file-input", "image-recognition", "video-recognition"], + "inputModalities": ["text", "image", "video"], + "outputModalities": ["text"], + "contextWindow": 300000, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.8 + }, + "output": { + "currency": "USD", + "perMillionTokens": 3.2 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.2 + } + }, + "family": "nova-pro", + "ownedBy": "amazon", + "openWeights": false + }, + { + "id": "nova-lite", + "name": "Nova Lite", + "capabilities": ["function-call", "file-input", "image-recognition", "video-recognition"], + "inputModalities": ["text", "image", "video"], + "outputModalities": ["text"], + "contextWindow": 300000, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.06 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.24 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.015 + } + }, + "family": "nova-lite", + "ownedBy": "amazon", + "openWeights": false + }, + { + "id": "morph-v3-fast", + "name": "Morph v3 Fast", + "description": "Morph's fastest apply model for code edits. ~10,500 tokens/sec with 96% accuracy for rapid code transformations.\n\nThe model requires the prompt to be in the following format: \n{instruction}\n{initial_code}\n{edit_snippet}\n\nZero Data Retention is enabled for Morph. Learn more about this model in their [documentation](https://docs.morphllm.com/quickstart)", + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 16000, + "maxOutputTokens": 16000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.8 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.2 + } + }, + "family": "morph", + "ownedBy": "morph", + "openWeights": false + }, + { + "id": "morph-v3-large", + "name": "Morph v3 Large", + "description": "Morph's high-accuracy apply model for complex code edits. ~4,500 tokens/sec with 98% accuracy for precise code transformations.\n\nThe model requires the prompt to be in the following format: \n{instruction}\n{initial_code}\n{edit_snippet}\n\nZero Data Retention is enabled for Morph. Learn more about this model in their [documentation](https://docs.morphllm.com/quickstart)", + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 32000, + "maxOutputTokens": 32000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.9 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.9 + } + }, + "family": "morph", + "ownedBy": "morph", + "openWeights": false + }, + { + "id": "llama-3-1", + "name": "Llama 3.1 8B Instruct", + "description": "Meta's latest class of model (Llama 3.1) launched with a variety of sizes & flavors. This is the base 405B pre-trained version.\n\nIt has demonstrated strong performance compared to leading closed-source models in human evaluations.\n\nTo read more about the model release, [click here](https://ai.meta.com/blog/meta-llama-3/). Usage of this model is subject to [Meta's Acceptable Use Policy](https://llama.meta.com/llama3/use-policy/).", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 16384, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.03 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.05 + } + }, + "family": "llama", + "ownedBy": "meta", + "openWeights": false + }, + { + "id": "llama-3-2", + "name": "Llama 3.2 90B Vision Instruct", + "capabilities": ["function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.72 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.72 + } + }, + "family": "llama", + "ownedBy": "meta", + "openWeights": false + }, + { + "id": "llama-4-scout", + "name": "Llama-4-Scout-17B-16E-Instruct-FP8", + "description": "Llama 4 Scout 17B Instruct (16E) is a mixture-of-experts (MoE) language model developed by Meta, activating 17 billion parameters out of a total of 109B. It supports native multimodal input (text and image) and multilingual output (text and code) across 12 supported languages. Designed for assistant-style interaction and visual reasoning, Scout uses 16 experts per forward pass and features a context length of 10 million tokens, with a training corpus of ~40 trillion tokens.\n\nBuilt for high efficiency and local or commercial deployment, Llama 4 Scout incorporates early fusion for seamless modality integration. It is instruction-tuned for use in multilingual chat, captioning, and image understanding tasks. Released under the Llama 4 Community License, it was last trained on data up to August 2024 and launched publicly on April 5, 2025.", + "capabilities": ["function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.08 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.3 + } + }, + "family": "llama", + "ownedBy": "meta", + "openWeights": true + }, + { + "id": "llama-3-3", + "name": "Llama-3.3-70B-Instruct", + "description": "The Meta Llama 3.3 multilingual large language model (LLM) is a pretrained and instruction tuned generative model in 70B (text in/text out). The Llama 3.3 instruction tuned text only model is optimized for multilingual dialogue use cases and outperforms many of the available open source and closed chat models on common industry benchmarks.", + "capabilities": ["function-call", "file-input"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.7 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2.8 + } + }, + "family": "llama", + "ownedBy": "meta", + "openWeights": true + }, + { + "id": "llama-4-maverick", + "name": "Llama-4-Maverick-17B-128E-Instruct-FP8", + "description": "Llama 4 Maverick 17B Instruct (128E) is a high-capacity multimodal language model from Meta, built on a mixture-of-experts (MoE) architecture with 128 experts and 17 billion active parameters per forward pass (400B total). It supports multilingual text and image input, and produces multilingual text and code output across 12 supported languages. Optimized for vision-language tasks, Maverick is instruction-tuned for assistant-like behavior, image reasoning, and general-purpose multimodal interaction.\n\nMaverick features early fusion for native multimodality and a 1 million token context window. It was trained on a curated mixture of public, licensed, and Meta-platform data, covering ~22 trillion tokens, with a knowledge cutoff in August 2024. Released on April 5, 2025 under the Llama 4 Community License, Maverick is suited for research and commercial applications requiring advanced multimodal understanding and high model throughput.", + "capabilities": ["function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.15 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.6 + } + }, + "family": "llama", + "ownedBy": "meta", + "openWeights": true + }, + { + "id": "claude-3-5-sonnet-20240620", + "name": "Claude 3.5 Sonnet (2024-06-20)", + "description": "Claude 3.5 Sonnet delivers performance superior to Opus and speeds faster than its predecessor, all at the same price point. Its core strengths include:\n\nCoding: Autonomously writes, edits, and executes code with advanced reasoning and troubleshooting.\nData Science: Augments human expertise by analyzing unstructured data and using multiple tools to generate insights.\nVisual Processing: Excels at interpreting charts, graphs, and images, accurately transcribing text to derive high-level insights.\nAgentic Tasks: Exceptional tool use makes it highly effective for complex, multi-step agentic workflows that interact with other systems.", + "capabilities": ["function-call", "file-input", "image-recognition", "web-search", "computer-use"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 15 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.3 + } + }, + "family": "claude-sonnet", + "ownedBy": "anthropic", + "openWeights": false, + "alias": ["claude-3-5-sonnet-v1"] + }, + { + "id": "claude-3-5-haiku", + "name": "Claude Haiku 3.5", + "description": "Claude 3.5 Haiku features offers enhanced capabilities in speed, coding accuracy, and tool use. Engineered to excel in real-time applications, it delivers quick response times that are essential for dynamic tasks such as chat interactions and immediate coding suggestions.\n\nThis makes it highly suitable for environments that demand both speed and precision, such as software development, customer service bots, and data management systems.\n\nThis model is currently pointing to [Claude 3.5 Haiku (2024-10-22)](/anthropic/claude-3-5-haiku-20241022).", + "capabilities": ["function-call", "file-input", "image-recognition", "web-search"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.8 + }, + "output": { + "currency": "USD", + "perMillionTokens": 4 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.08 + } + }, + "family": "claude-haiku", + "ownedBy": "anthropic", + "openWeights": false + }, + { + "id": "claude-3-7-sonnet", + "name": "Claude Sonnet 3.7", + "description": "Claude 3.7 Sonnet is an advanced large language model with improved reasoning, coding, and problem-solving capabilities. It introduces a hybrid reasoning approach, allowing users to choose between rapid responses and extended, step-by-step processing for complex tasks. The model demonstrates notable improvements in coding, particularly in front-end development and full-stack updates, and excels in agentic workflows, where it can autonomously navigate multi-step processes. \n\nClaude 3.7 Sonnet maintains performance parity with its predecessor in standard mode while offering an extended reasoning mode for enhanced accuracy in math, coding, and instruction-following tasks.\n\nRead more at the [blog post here](https://www.anthropic.com/news/claude-3-7-sonnet)", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition", "web-search", "computer-use"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 15 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.3 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 64000 + } + }, + "family": "claude-sonnet", + "ownedBy": "anthropic", + "openWeights": false + }, + { + "id": "claude-opus-4-1", + "name": "Claude Opus 4", + "description": "Claude Opus 4.1 is an updated version of Anthropic’s flagship model, offering improved performance in coding, reasoning, and agentic tasks. It achieves 74.5% on SWE-bench Verified and shows notable gains in multi-file code refactoring, debugging precision, and detail-oriented reasoning. The model supports extended thinking up to 64K tokens and is optimized for tasks involving research, data analysis, and tool-assisted reasoning.", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition", "web-search", "computer-use"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 32000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 15 + }, + "output": { + "currency": "USD", + "perMillionTokens": 75 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 1.5 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 32000 + } + }, + "family": "claude-opus", + "ownedBy": "anthropic", + "openWeights": false + }, + { + "id": "claude-3-5-sonnet", + "name": "Claude Sonnet 3.5 v2", + "description": "New Claude 3.5 Sonnet delivers better-than-Opus capabilities, faster-than-Sonnet speeds, at the same Sonnet prices. Sonnet is particularly good at:\n\n- Coding: Scores ~49% on SWE-Bench Verified, higher than the last best score, and without any fancy prompt scaffolding\n- Data science: Augments human data science expertise; navigates unstructured data while using multiple tools for insights\n- Visual processing: excelling at interpreting charts, graphs, and images, accurately transcribing text to derive insights beyond just the text alone\n- Agentic tasks: exceptional tool use, making it great at agentic tasks (i.e. complex, multi-step problem solving tasks that require engaging with other systems)\n\n#multimodal", + "capabilities": ["function-call", "file-input", "image-recognition", "web-search", "computer-use"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 15 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.3 + } + }, + "family": "claude-sonnet", + "ownedBy": "anthropic", + "openWeights": false + }, + { + "id": "claude-3-opus", + "name": "Claude Opus 3", + "capabilities": ["function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 15 + }, + "output": { + "currency": "USD", + "perMillionTokens": 75 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 1.5 + } + }, + "family": "claude-opus", + "ownedBy": "anthropic", + "openWeights": false + }, + { + "id": "claude-3-haiku", + "name": "Claude Haiku 3", + "description": "Claude 3 Haiku is Anthropic's fastest and most compact model for\nnear-instant responsiveness. Quick and accurate targeted performance.\n\nSee the launch announcement and benchmark results [here](https://www.anthropic.com/news/claude-3-haiku)\n\n#multimodal", + "capabilities": ["function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.25 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.25 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.03 + } + }, + "family": "claude-haiku", + "ownedBy": "anthropic", + "openWeights": false + }, + { + "id": "claude-opus-4", + "name": "Claude Opus 4", + "description": "Claude Opus 4 is benchmarked as the world’s best coding model, at time of release, bringing sustained performance on complex, long-running tasks and agent workflows. It sets new benchmarks in software engineering, achieving leading results on SWE-bench (72.5%) and Terminal-bench (43.2%). Opus 4 supports extended, agentic workflows, handling thousands of task steps continuously for hours without degradation. \n\nRead more at the [blog post here](https://www.anthropic.com/news/claude-4)", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition", "web-search", "computer-use"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 32000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 15 + }, + "output": { + "currency": "USD", + "perMillionTokens": 75 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 1.5 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 32000 + } + }, + "family": "claude-opus", + "ownedBy": "anthropic", + "openWeights": false + }, + { + "id": "hermes-4", + "name": "Hermes-4-70B", + "description": "Hermes 4 70B is a hybrid reasoning model from Nous Research, built on Meta-Llama-3.1-70B. It introduces the same hybrid mode as the larger 405B release, allowing the model to either respond directly or generate explicit ... reasoning traces before answering. Users can control the reasoning behaviour with the `reasoning` `enabled` boolean. [Learn more in our docs](https://openrouter.ai/docs/use-cases/reasoning-tokens#enable-reasoning-with-default-config)\n\nThis 70B variant is trained with the expanded post-training corpus (~60B tokens) emphasizing verified reasoning data, leading to improvements in mathematics, coding, STEM, logic, and structured outputs while maintaining general assistant performance. It supports JSON mode, schema adherence, function calling, and tool use, and is designed for greater steerability with reduced refusal rates.", + "capabilities": ["reasoning", "function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.13 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.4 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.013 + } + }, + "family": "nousresearch", + "ownedBy": "nousresearch", + "openWeights": true + }, + { + "id": "e5-mistral-instruct", + "name": "e5-mistral-7b-instruct", + "capabilities": ["embedding"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 32768, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.01 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "family": "text-embedding", + "ownedBy": "nebius", + "openWeights": true + }, + { + "id": "llama-3_1-nemotron-ultra-v1", + "name": "Llama-3.1-Nemotron-Ultra-253B-v1", + "description": "Llama-3.1-Nemotron-Ultra-253B is a 253 billion parameter reasoning-focused language model optimized for efficiency that excels at math, coding, and general instruction-following tasks while running on a single 8xH100 node.", + "capabilities": ["function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.6 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.8 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.06 + } + }, + "ownedBy": "meta", + "openWeights": true + }, + { + "id": "bge-en-icl", + "name": "BGE-ICL", + "capabilities": ["embedding"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 32768, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.01 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "family": "text-embedding", + "ownedBy": "baai", + "openWeights": true + }, + { + "id": "bge-multilingual-gemma2", + "name": "bge-multilingual-gemma2", + "capabilities": ["embedding"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 8192, + "maxOutputTokens": 3072, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.01 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "family": "text-embedding", + "ownedBy": "baai", + "openWeights": true + }, + { + "id": "gemma-2-it-fast", + "name": "Gemma-2-9b-it (Fast)", + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 8192, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.03 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.09 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.003 + } + }, + "ownedBy": "google", + "openWeights": true + }, + { + "id": "gemma-3-it-fast", + "name": "Gemma-3-27b-it (Fast)", + "capabilities": ["function-call", "structured-output", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.6 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.02 + } + }, + "ownedBy": "google", + "openWeights": true + }, + { + "id": "qwen3-fast", + "name": "Qwen3-32B (Fast)", + "capabilities": ["function-call", "structured-output", "reasoning"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.6 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.02 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 38912 + } + }, + "ownedBy": "alibaba", + "openWeights": true + }, + { + "id": "qwen3-a3b-thinking-2507", + "name": "Qwen3-30B-A3B-Thinking-2507", + "description": "Qwen3-30B-A3B-Thinking-2507 is a 30B parameter Mixture-of-Experts reasoning model optimized for complex tasks requiring extended multi-step thinking. The model is designed specifically for “thinking mode,” where internal reasoning traces are separated from final answers.\n\nCompared to earlier Qwen3-30B releases, this version improves performance across logical reasoning, mathematics, science, coding, and multilingual benchmarks. It also demonstrates stronger instruction following, tool use, and alignment with human preferences. With higher reasoning efficiency and extended output budgets, it is best suited for advanced research, competitive problem solving, and agentic applications requiring structured long-context reasoning.", + "capabilities": ["reasoning", "function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 16384, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.1 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.3 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.01 + } + }, + "reasoning": { + "supportedEfforts": ["low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 38912 + }, + "interleaved": true + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": true + }, + { + "id": "qwen3-a3b-instruct-2507", + "name": "Qwen3-30B-A3B-Instruct-2507", + "description": "Qwen3-30B-A3B-Instruct-2507 is a 30.5B-parameter mixture-of-experts language model from Qwen, with 3.3B active parameters per inference. It operates in non-thinking mode and is designed for high-quality instruction following, multilingual understanding, and agentic tool use. Post-trained on instruction data, it demonstrates competitive performance across reasoning (AIME, ZebraLogic), coding (MultiPL-E, LiveCodeBench), and alignment (IFEval, WritingBench) benchmarks. It outperforms its non-instruct variant on subjective and open-ended tasks while retaining strong factual and coding performance.", + "capabilities": ["function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.1 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.3 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.01 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": true + }, + { + "id": "qwen2-5-coder-fast", + "name": "Qwen2.5-Coder-7B (Fast)", + "capabilities": ["function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.03 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.09 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.003 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": true + }, + { + "id": "llama-3-1-instruct-fast", + "name": "Meta-Llama-3.1-8B-Instruct (Fast)", + "capabilities": ["function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.03 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.09 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.003 + } + }, + "ownedBy": "meta", + "openWeights": true + }, + { + "id": "llama-3-3-instruct-fast", + "name": "Llama-3.3-70B-Instruct (Fast)", + "capabilities": ["function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.25 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.75 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.025 + } + }, + "ownedBy": "meta", + "openWeights": true + }, + { + "id": "glm-4-7-fp8", + "name": "GLM-4.7 (FP8)", + "capabilities": ["function-call", "structured-output", "reasoning"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.4 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.04 + } + }, + "reasoning": { + "supportedEfforts": ["none"], + "interleaved": true + }, + "ownedBy": "zhipu", + "openWeights": false + }, + { + "id": "deepseek-r1-0528-fast", + "name": "DeepSeek R1 0528 Fast", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 6 + } + }, + "family": "deepseek", + "ownedBy": "deepseek", + "openWeights": true + }, + { + "id": "deepseek-v3-0324-fast", + "name": "DeepSeek-V3-0324 (Fast)", + "capabilities": ["function-call", "structured-output", "reasoning"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.75 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2.25 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.075 + } + }, + "reasoning": { + "supportedEfforts": ["none"] + }, + "ownedBy": "deepseek", + "openWeights": true + }, + { + "id": "flux-dev", + "name": "FLUX.1-dev", + "capabilities": ["image-generation"], + "inputModalities": ["text"], + "outputModalities": ["image"], + "contextWindow": 77, + "ownedBy": "bfl", + "openWeights": true + }, + { + "id": "flux-schnell", + "name": "FLUX.1-schnell", + "capabilities": ["image-generation"], + "inputModalities": ["text"], + "outputModalities": ["image"], + "contextWindow": 77, + "ownedBy": "bfl", + "openWeights": true + }, + { + "id": "qwen-plus-character", + "name": "Qwen Plus Character", + "capabilities": ["function-call", "reasoning"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 32768, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.115 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.287 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 0, + "max": 81920 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": false + }, + { + "id": "qwen-math-plus", + "name": "Qwen Math Plus", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 4096, + "maxOutputTokens": 3072, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.574 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.721 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": false + }, + { + "id": "qwen-doc-turbo", + "name": "Qwen Doc Turbo", + "capabilities": ["function-call", "file-input"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.087 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.144 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": false + }, + { + "id": "qwen-deep-research", + "name": "Qwen Deep Research", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 1000000, + "maxOutputTokens": 32768, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 7.742 + }, + "output": { + "currency": "USD", + "perMillionTokens": 23.367 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": false + }, + { + "id": "qwen-long", + "name": "Qwen Long", + "capabilities": ["function-call", "file-input"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 10000000, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.072 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.287 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": false + }, + { + "id": "qwen2-5-math-instruct", + "name": "Qwen2.5-Math 72B Instruct", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 4096, + "maxOutputTokens": 3072, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.574 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.721 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": true + }, + { + "id": "moonshot-kimi-k2-instruct", + "name": "Moonshot Kimi K2 Instruct", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.574 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2.294 + } + }, + "family": "kimi", + "ownedBy": "moonshot", + "openWeights": true + }, + { + "id": "tongyi-intent-detect-v3", + "name": "Tongyi Intent Detect V3", + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 8192, + "maxOutputTokens": 1024, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.058 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.144 + } + }, + "family": "yi", + "ownedBy": "alibaba-cn", + "openWeights": false + }, + { + "id": "qwen-math-turbo", + "name": "Qwen Math Turbo", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 4096, + "maxOutputTokens": 3072, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.287 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.861 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": false + }, + { + "id": "deepseek-r1-distill-qwen-1", + "name": "DeepSeek R1 Distill Qwen 1.5B", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 32768, + "maxOutputTokens": 16384, + "family": "qwen", + "ownedBy": "deepseek", + "openWeights": false + }, + { + "id": "claude-sonnet-4-6@default", + "name": "Claude Sonnet 4.6", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition", "web-search", "computer-use"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 15 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.3 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 64000 + } + }, + "family": "claude-sonnet", + "ownedBy": "anthropic", + "openWeights": false + }, + { + "id": "claude-opus-4-5@20251101", + "name": "Claude Opus 4.5", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition", "web-search", "computer-use"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 25 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.5 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 64000 + } + }, + "family": "claude-opus", + "ownedBy": "anthropic", + "openWeights": false + }, + { + "id": "claude-3-5-sonnet@20241022", + "name": "Claude Sonnet 3.5 v2", + "capabilities": ["function-call", "file-input", "image-recognition", "web-search", "computer-use"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 15 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.3 + } + }, + "family": "claude-sonnet", + "ownedBy": "anthropic", + "openWeights": false + }, + { + "id": "claude-3-5-haiku@20241022", + "name": "Claude Haiku 3.5", + "capabilities": ["function-call", "file-input", "image-recognition", "web-search"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.8 + }, + "output": { + "currency": "USD", + "perMillionTokens": 4 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.08 + } + }, + "family": "claude-haiku", + "ownedBy": "anthropic", + "openWeights": false + }, + { + "id": "claude-sonnet-4@20250514", + "name": "Claude Sonnet 4", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition", "web-search", "computer-use"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 15 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.3 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 64000 + } + }, + "family": "claude-sonnet", + "ownedBy": "anthropic", + "openWeights": false + }, + { + "id": "claude-sonnet-4-5@20250929", + "name": "Claude Sonnet 4.5", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition", "web-search", "computer-use"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 15 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.3 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 64000 + } + }, + "family": "claude-sonnet", + "ownedBy": "anthropic", + "openWeights": false + }, + { + "id": "claude-opus-4-1@20250805", + "name": "Claude Opus 4.1", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition", "web-search", "computer-use"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 32000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 15 + }, + "output": { + "currency": "USD", + "perMillionTokens": 75 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 1.5 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 32000 + } + }, + "family": "claude-opus", + "ownedBy": "anthropic", + "openWeights": false + }, + { + "id": "claude-haiku-4-5@20251001", + "name": "Claude Haiku 4.5", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition", "web-search", "computer-use"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1 + }, + "output": { + "currency": "USD", + "perMillionTokens": 5 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.1 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 64000 + } + }, + "family": "claude-haiku", + "ownedBy": "anthropic", + "openWeights": false + }, + { + "id": "claude-3-7-sonnet@20250219", + "name": "Claude Sonnet 3.7", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition", "web-search", "computer-use"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 15 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.3 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 64000 + } + }, + "family": "claude-sonnet", + "ownedBy": "anthropic", + "openWeights": false + }, + { + "id": "claude-opus-4-6@default", + "name": "Claude Opus 4.6", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition", "web-search", "computer-use"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 1000000, + "maxOutputTokens": 128000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 25 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.5 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 32000 + } + }, + "family": "claude-opus", + "ownedBy": "anthropic", + "openWeights": false + }, + { + "id": "claude-opus-4@20250514", + "name": "Claude Opus 4", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition", "web-search", "computer-use"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 32000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 15 + }, + "output": { + "currency": "USD", + "perMillionTokens": 75 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 1.5 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 32000 + } + }, + "family": "claude-opus", + "ownedBy": "anthropic", + "openWeights": false + }, + { + "id": "baichuan-m2", + "name": "baichuan-m2-32b", + "capabilities": ["reasoning"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 131072, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.07 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.07 + } + }, + "family": "baichuan", + "ownedBy": "baichuan", + "openWeights": true + }, + { + "id": "hermes-2-pro-llama-3", + "name": "Hermes 2 Pro Llama 3 8B", + "description": "Hermes 2 Pro is an upgraded, retrained version of Nous Hermes 2, consisting of an updated and cleaned version of the OpenHermes 2.5 Dataset, as well as a newly introduced Function Calling and JSON Mode dataset developed in-house.", + "capabilities": ["structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 8192, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.14 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.14 + } + }, + "family": "llama", + "ownedBy": "nousresearch", + "openWeights": true + }, + { + "id": "paddleocr-vl", + "name": "PaddleOCR-VL", + "description": "PaddleOCR-VL is an advanced and efficient document parsing model specifically designed for element recognition within documents. Its core component, PaddleOCR-VL-0.9B, is a compact yet powerful vision-language model (VLM) composed of a NaViT-style dynamic resolution visual encoder and the ERNIE-4.5-0.3B language model, enabling precise element recognition. This model supports 109 languages and excels at recognizing complex elements such as text, tables, formulas, and charts while maintaining extremely low resource consumption. Through comprehensive evaluations on widely used public benchmarks and internal benchmarks, PaddleOCR-VL achieves state-of-the-art (SOTA) performance in both page-level document parsing and element-level recognition. It significantly outperforms existing pipeline-based solutions, multimodal document parsing approaches, and advanced general-purpose multimodal large models, while also offering faster inference speed.", + "capabilities": ["file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 16384, + "maxOutputTokens": 16384, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.02 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.02 + } + }, + "ownedBy": "novita-ai", + "openWeights": true + }, + { + "id": "kat-coder", + "name": "KAT-Coder-Pro V1(Free)", + "capabilities": ["function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 256000, + "maxOutputTokens": 32000, + "ownedBy": "streamlake", + "openWeights": true + }, + { + "id": "kat-coder-pro", + "name": "Kat Coder Pro", + "description": "KAT-Coder-Pro V1 is KwaiKAT's most advanced agentic coding model in the KAT-Coder series. Designed specifically for agentic coding tasks, it excels in real-world software engineering scenarios, achieving 73.4% solve rate on the SWE-Bench Verified benchmark. \n\nThe model has been optimized for tool-use capability, multi-turn interaction, instruction following, generalization, and comprehensive capabilities through a multi-stage training process, including mid-training, supervised fine-tuning (SFT), reinforcement fine-tuning (RFT), and scalable agentic RL.", + "capabilities": ["function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 256000, + "maxOutputTokens": 128000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.2 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.06 + } + }, + "family": "kat-coder", + "ownedBy": "streamlake", + "openWeights": true + }, + { + "id": "deepseek-ocr-2", + "name": "deepseek/deepseek-ocr-2", + "capabilities": ["file-input", "image-recognition", "function-call"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 8192, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.03 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.03 + } + }, + "ownedBy": "deepseek", + "openWeights": true + }, + { + "id": "deepseek-prover-v2", + "name": "Deepseek Prover V2 671B", + "description": "Provided by chutes.ai\nDeepSeek Prover V2 is a 671B parameter model, speculated to be geared towards logic and mathematics. Likely an upgrade from DeepSeek-Prover-V1.5 Not much is known about the model yet, as DeepSeek released it on Hugging Face without an announcement or description.", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 160000, + "maxOutputTokens": 160000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.7 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2.5 + } + }, + "ownedBy": "deepseek", + "openWeights": true + }, + { + "id": "deepseek-r1-0528-qwen3", + "name": "DeepSeek R1 0528 Qwen3 8B", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 32000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.06 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.09 + } + }, + "family": "qwen", + "ownedBy": "deepseek", + "openWeights": true + }, + { + "id": "deepseek-r1-turbo", + "name": "DeepSeek R1 (Turbo)\t", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 64000, + "maxOutputTokens": 16000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.7 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2.5 + } + }, + "ownedBy": "deepseek", + "openWeights": true + }, + { + "id": "deepseek-ocr", + "name": "DeepSeek-OCR", + "description": "DeepSeek-OCR is a vision-language model launched by DeepSeek AI, focusing on optical character recognition (OCR) and “contextual optical compression.” The model is designed to explore the limits of compressing contextual information from images, efficiently processing documents and converting them into structured text formats such as Markdown. The model requires an image as input.", + "capabilities": ["structured-output", "file-input", "image-recognition", "function-call"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 8192, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.03 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.03 + } + }, + "ownedBy": "deepseek", + "openWeights": true + }, + { + "id": "deepseek-v3-turbo", + "name": "DeepSeek V3 (Turbo)\t", + "capabilities": ["function-call", "reasoning"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 64000, + "maxOutputTokens": 16000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.4 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.3 + } + }, + "reasoning": { + "supportedEfforts": ["none"] + }, + "ownedBy": "deepseek", + "openWeights": true + }, + { + "id": "l3-lunaris", + "name": "Sao10k L3 8B Lunaris\t", + "description": "Lunaris 8B is a versatile generalist and roleplaying model based on Llama 3. It's a strategic merge of multiple models, designed to balance creativity with improved logic and general knowledge.\n\nCreated by [Sao10k](https://huggingface.co/Sao10k), this model aims to offer an improved experience over Stheno v3.2, with enhanced creativity and logical reasoning.\n\nFor best results, use with Llama 3 Instruct context template, temperature 1.4, and min_p 0.1.", + "capabilities": ["structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 8192, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.05 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.05 + } + }, + "ownedBy": "novita-ai", + "openWeights": true + }, + { + "id": "l3-stheno-v3-2", + "name": "L3 8B Stheno V3.2", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 8192, + "maxOutputTokens": 32000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.05 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.05 + } + }, + "ownedBy": "novita-ai", + "openWeights": true + }, + { + "id": "l31-euryale-v2-2", + "name": "L31 70B Euryale V2.2", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 8192, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.48 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.48 + } + }, + "ownedBy": "novita-ai", + "openWeights": true + }, + { + "id": "l3-euryale-v2-1", + "name": "L3 70B Euryale V2.1\t", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 8192, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.48 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.48 + } + }, + "ownedBy": "novita-ai", + "openWeights": true + }, + { + "id": "r1v4-lite", + "name": "Skywork R1V4-Lite", + "capabilities": ["structured-output", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 262144, + "maxOutputTokens": 65536, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.6 + } + }, + "family": "skywork", + "ownedBy": "novita-ai", + "openWeights": true + }, + { + "id": "wizardlm-2-8x22b", + "name": "Wizardlm 2 8x22B", + "description": "WizardLM-2 8x22B is Microsoft AI's most advanced Wizard model. It demonstrates highly competitive performance compared to leading proprietary models, and it consistently outperforms all existing state-of-the-art opensource models.\n\nIt is an instruct finetune of [Mixtral 8x22B](/models/mistralai/mixtral-8x22b).\n\nTo read more about the model release, [click here](https://wizardlm.github.io/WizardLM2/).\n\n#moe", + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 65535, + "maxOutputTokens": 8000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.62 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.62 + } + }, + "ownedBy": "novita-ai", + "openWeights": true + }, + { + "id": "mythomax-l2", + "name": "Mythomax L2 13B", + "description": "One of the highest performing and most popular fine-tunes of Llama 2 13B, with rich descriptions and roleplay. #merge", + "capabilities": ["structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 4096, + "maxOutputTokens": 3200, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.09 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.09 + } + }, + "ownedBy": "novita-ai", + "openWeights": true + }, + { + "id": "ernie-4-5-vl-a3b", + "name": "ERNIE-4.5-VL-28B-A3B-Thinking", + "description": "A powerful multimodal Mixture-of-Experts chat model featuring 28B total parameters with 3B activated per token, delivering exceptional text and vision understanding through its innovative heterogeneous MoE structure with modality-isolated routing. Built with scaling-efficient infrastructure for high-throughput training and inference, the model leverages advanced post-training techniques including SFT, DPO, and UPO for optimized performance, while supporting an impressive 131K context length and RLVR alignment for superior cross-modal reasoning and generation capabilities.", + "capabilities": [ + "reasoning", + "function-call", + "structured-output", + "file-input", + "image-recognition", + "video-recognition" + ], + "inputModalities": ["text", "image", "video"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 65536, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.39 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.39 + } + }, + "ownedBy": "baidu", + "openWeights": true + }, + { + "id": "ernie-4-5-a3b", + "name": "ERNIE 4.5 21B A3B", + "description": "A sophisticated text-based Mixture-of-Experts (MoE) model featuring 21B total parameters with 3B activated per token, delivering exceptional multimodal understanding and generation through heterogeneous MoE structures and modality-isolated routing. Supporting an extensive 131K token context length, the model achieves efficient inference via multi-expert parallel collaboration and quantization, while advanced post-training techniques including SFT, DPO, and UPO ensure optimized performance across diverse applications with specialized routing and balancing losses for superior task handling.", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 120000, + "maxOutputTokens": 8000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.07 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.28 + } + }, + "family": "ernie", + "ownedBy": "baidu", + "openWeights": true + }, + { + "id": "qwen3-omni-a3b", + "name": "Qwen3 Omni 30B A3B Thinking", + "capabilities": [ + "reasoning", + "function-call", + "structured-output", + "file-input", + "image-recognition", + "audio-recognition", + "video-recognition" + ], + "inputModalities": ["text", "audio", "video", "image"], + "outputModalities": ["text"], + "contextWindow": 65536, + "maxOutputTokens": 16384, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.25 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.97 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 38912 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": true + }, + { + "id": "qwen3-omni-a3b-instruct", + "name": "Qwen3 Omni 30B A3B Instruct", + "capabilities": [ + "function-call", + "structured-output", + "file-input", + "image-recognition", + "audio-recognition", + "audio-generation", + "video-recognition" + ], + "inputModalities": ["text", "video", "audio", "image"], + "outputModalities": ["text", "audio"], + "contextWindow": 65536, + "maxOutputTokens": 16384, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.25 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.97 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": true + }, + { + "id": "qwen-2-5-instruct", + "name": "Qwen 2.5 72B Instruct", + "description": "Qwen2.5 7B is the latest series of Qwen large language models. Qwen2.5 brings the following improvements upon Qwen2:\n\n- Significantly more knowledge and has greatly improved capabilities in coding and mathematics, thanks to our specialized expert models in these domains.\n\n- Significant improvements in instruction following, generating long texts (over 8K tokens), understanding structured data (e.g, tables), and generating structured outputs especially JSON. More resilient to the diversity of system prompts, enhancing role-play implementation and condition-setting for chatbots.\n\n- Long-context Support up to 128K tokens and can generate up to 8K tokens.\n\n- Multilingual support for over 29 languages, including Chinese, English, French, Spanish, Portuguese, German, Italian, Russian, Japanese, Korean, Vietnamese, Thai, Arabic, and more.\n\nUsage of this model is subject to [Tongyi Qianwen LICENSE AGREEMENT](https://huggingface.co/Qwen/Qwen1.5-110B-Chat/blob/main/LICENSE).", + "capabilities": ["function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 32000, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.38 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.4 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": true + }, + { + "id": "qwen3-vl-a22b-instruct", + "name": "Qwen3 VL 235B A22B Instruct", + "description": "Qwen3-VL-235B-A22B Instruct is an open-weight multimodal model that unifies strong text generation with visual understanding across images and video. The Instruct model targets general vision-language use (VQA, document parsing, chart/table extraction, multilingual OCR). The series emphasizes robust perception (recognition of diverse real-world and synthetic categories), spatial understanding (2D/3D grounding), and long-form visual comprehension, with competitive results on public multimodal benchmarks for both perception and reasoning.\n\nBeyond analysis, Qwen3-VL supports agentic interaction and tool use: it can follow complex instructions over multi-image, multi-turn dialogues; align text to video timelines for precise temporal queries; and operate GUI elements for automation tasks. The models also enable visual coding workflows—turning sketches or mockups into code and assisting with UI debugging—while maintaining strong text-only performance comparable to the flagship Qwen3 language models. This makes Qwen3-VL suitable for production scenarios spanning document AI, multilingual OCR, software/UI assistance, spatial/embodied tasks, and research on vision-language agents.", + "capabilities": ["function-call", "structured-output", "file-input", "image-recognition", "video-recognition"], + "inputModalities": ["text", "image", "video"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 32768, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.5 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.11 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": true + }, + { + "id": "llama-3-instruct", + "name": "Llama3 70B Instruct", + "description": "Meta's latest class of model (Llama 3) launched with a variety of sizes & flavors. This 70B instruct-tuned version was optimized for high quality dialogue usecases.\n\nIt has demonstrated strong performance compared to leading closed-source models in human evaluations.\n\nTo read more about the model release, [click here](https://ai.meta.com/blog/meta-llama-3/). Usage of this model is subject to [Meta's Acceptable Use Policy](https://llama.meta.com/llama3/use-policy/).", + "capabilities": ["structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 8192, + "maxOutputTokens": 8000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.51 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.74 + } + }, + "family": "llama", + "ownedBy": "meta", + "openWeights": true + }, + { + "id": "autoglm-phone-multilingual", + "name": "AutoGLM-Phone-9B-Multilingual", + "capabilities": ["file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 65536, + "maxOutputTokens": 65536, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.035 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.138 + } + }, + "ownedBy": "novita-ai", + "openWeights": true + }, + { + "id": "grok-41-fast", + "name": "Grok 4.1 Fast", + "capabilities": [ + "reasoning", + "function-call", + "structured-output", + "file-input", + "image-recognition", + "web-search" + ], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 256000, + "maxOutputTokens": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.25 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.125 + } + }, + "family": "grok", + "ownedBy": "xai", + "openWeights": false + }, + { + "id": "claude-opus-45", + "name": "Claude Opus 4.5", + "capabilities": [ + "reasoning", + "function-call", + "structured-output", + "file-input", + "image-recognition", + "computer-use" + ], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 198000, + "maxOutputTokens": 49500, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 6 + }, + "output": { + "currency": "USD", + "perMillionTokens": 30 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.6 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"] + }, + "family": "claude-opus", + "ownedBy": "anthropic", + "openWeights": false + }, + { + "id": "mistral-31", + "name": "Venice Medium", + "capabilities": ["function-call", "structured-output", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 32000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2 + } + }, + "family": "mistral", + "ownedBy": "mistral", + "openWeights": true + }, + { + "id": "venice-uncensored", + "name": "Venice Uncensored 1.1", + "capabilities": ["structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 32000, + "maxOutputTokens": 8000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.9 + } + }, + "family": "venice", + "ownedBy": "venice", + "openWeights": true + }, + { + "id": "gpt-52", + "name": "GPT-5.2", + "capabilities": ["reasoning", "function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 256000, + "maxOutputTokens": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2.19 + }, + "output": { + "currency": "USD", + "perMillionTokens": 17.5 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.219 + } + }, + "reasoning": { + "supportedEfforts": ["minimal", "low", "medium", "high"] + }, + "family": "gpt", + "ownedBy": "openai", + "openWeights": false + }, + { + "id": "minimax-m25", + "name": "MiniMax M2.5", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 198000, + "maxOutputTokens": 32000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.4 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.6 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.04 + } + }, + "reasoning": { + "supportedEfforts": [], + "interleaved": true + }, + "family": "minimax", + "ownedBy": "minimax", + "openWeights": true + }, + { + "id": "claude-sonnet-45", + "name": "Claude Sonnet 4.5", + "capabilities": [ + "reasoning", + "function-call", + "structured-output", + "file-input", + "image-recognition", + "computer-use" + ], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 198000, + "maxOutputTokens": 49500, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 3.75 + }, + "output": { + "currency": "USD", + "perMillionTokens": 18.75 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.375 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "interleaved": true + }, + "family": "claude-sonnet", + "ownedBy": "anthropic", + "openWeights": false + }, + { + "id": "google-gemma-3-it", + "name": "Google Gemma 3 27B Instruct", + "capabilities": ["function-call", "structured-output", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 198000, + "maxOutputTokens": 49500, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.12 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.2 + } + }, + "family": "gemma", + "ownedBy": "venice", + "openWeights": true + }, + { + "id": "hermes-3-llama-3-1", + "name": "Hermes 3 Llama 3.1 405b", + "description": "Hermes 3 is a generalist language model with many improvements over [Hermes 2](/models/nousresearch/nous-hermes-2-mistral-7b-dpo), including advanced agentic capabilities, much better roleplaying, reasoning, multi-turn conversation, long context coherence, and improvements across the board.\n\nHermes 3 70B is a competitive, if not superior finetune of the [Llama-3.1 70B foundation model](/models/meta-llama/llama-3.1-70b-instruct), focused on aligning LLMs to the user, with powerful steering capabilities and control given to the end user.\n\nThe Hermes 3 series builds and expands on the Hermes 2 set of capabilities, including more powerful and reliable function calling and structured output capabilities, generalist assistant capabilities, and improved code generation skills.", + "capabilities": ["reasoning"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 32000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.1 + }, + "output": { + "currency": "USD", + "perMillionTokens": 3 + } + }, + "family": "hermes", + "ownedBy": "nousresearch", + "openWeights": true + }, + { + "id": "olafangensan-glm-4-7-flash-heretic", + "name": "GLM 4.7 Flash Heretic", + "capabilities": ["reasoning", "function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 32000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.14 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.8 + } + }, + "family": "glm-flash", + "ownedBy": "venice", + "openWeights": true + }, + { + "id": "minimax-m21", + "name": "MiniMax M2.1", + "capabilities": ["reasoning", "function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 198000, + "maxOutputTokens": 49500, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.4 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.6 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.04 + } + }, + "reasoning": { + "supportedEfforts": [], + "interleaved": true + }, + "family": "minimax", + "ownedBy": "minimax", + "openWeights": true + }, + { + "id": "qwen3-next", + "name": "Qwen 3 Next 80b", + "capabilities": ["function-call", "structured-output", "reasoning"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 256000, + "maxOutputTokens": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.35 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.9 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 38912 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": true + }, + { + "id": "gpt-52-codex", + "name": "GPT-5.2 Codex", + "capabilities": ["reasoning", "function-call", "structured-output", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 256000, + "maxOutputTokens": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2.19 + }, + "output": { + "currency": "USD", + "perMillionTokens": 17.5 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.219 + } + }, + "reasoning": { + "supportedEfforts": ["minimal", "low", "medium", "high"] + }, + "family": "gpt-codex", + "ownedBy": "openai", + "openWeights": false + }, + { + "id": "ring-flash-2-0", + "name": "inclusionAI/Ring-flash-2.0", + "description": "Ring-flash-2.0 is a high-performance thinking model deeply optimized based on the Ling-flash-2.0-base. It uses a mixture-of-experts (MoE) architecture with a total of 100 billion parameters, but only activates 6.1 billion parameters per inference. The model employs the original Icepop algorithm to solve the instability issues of large MoE models during reinforcement learning (RL) training, enabling its complex reasoning capabilities to continuously improve over long training cycles. Ring-flash-2.0 has achieved significant breakthroughs on multiple high-difficulty benchmarks, including mathematics competitions, code generation, and logical reasoning. Its performance not only surpasses top dense models under 40 billion parameters but also rivals larger open-source MoE models and closed-source high-performance thinking models. Although the model focuses on complex reasoning, it also performs exceptionally well on creative writing tasks. Furthermore, thanks to its efficient architecture, Ring-flash-2.0 delivers high performance with low-latency inference, significantly reducing deployment costs in high-concurrency scenarios.", + "capabilities": ["reasoning", "function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131000, + "maxOutputTokens": 131000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.14 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.57 + } + }, + "family": "ring", + "ownedBy": "bailing", + "openWeights": false + }, + { + "id": "ling-flash-2-0", + "name": "inclusionAI/Ling-flash-2.0", + "description": "Ling-flash-2.0 is a language model from inclusionAI with a total of 100 billion parameters, of which 6.1 billion are activated per token (4.8 billion non-embedding). As part of the Ling 2.0 architecture series, it is designed as a lightweight yet powerful Mixture-of-Experts (MoE) model. It aims to deliver performance comparable to or even exceeding that of 40B-level dense models and other larger MoE models, but with a significantly smaller active parameter count. The model represents a strategy focused on achieving high performance and efficiency through extreme architectural design and training methods.", + "capabilities": ["function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131000, + "maxOutputTokens": 131000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.14 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.57 + } + }, + "family": "ling", + "ownedBy": "bailing", + "openWeights": false + }, + { + "id": "ling-mini-2-0", + "name": "inclusionAI/Ling-mini-2.0", + "description": "Ling-mini-2.0 is a small-sized, high-performance large language model based on the MoE architecture. It has a total of 16 billion parameters, but only activates 1.4 billion parameters per token (non-embedding 789 million), achieving extremely high generation speed. Thanks to the efficient MoE design and large-scale high-quality training data, despite activating only 1.4 billion parameters, Ling-mini-2.0 still demonstrates top-tier performance on downstream tasks comparable to dense LLMs under 10 billion parameters and even larger-scale MoE models.", + "capabilities": ["function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131000, + "maxOutputTokens": 131000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.07 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.28 + } + }, + "family": "ling", + "ownedBy": "bailing", + "openWeights": false + }, + { + "id": "kat-dev", + "name": "Kwaipilot/KAT-Dev", + "description": "KAT-Dev (32B) is an open-source 32B parameter model specifically designed for software engineering tasks. It achieved a 62.4% resolution rate on the SWE-Bench Verified benchmark, ranking fifth among all open-source models of various scales. The model is optimized through multiple stages, including intermediate training, supervised fine-tuning (SFT) and reinforcement fine-tuning (RFT), as well as large-scale agent reinforcement learning (RL). Based on Qwen3-32B, its training process lays the foundation for subsequent fine-tuning and reinforcement learning stages by enhancing fundamental abilities such as tool usage, multi-turn interaction, and instruction following. During the fine-tuning phase, the model not only learns eight carefully curated task types and programming scenarios but also innovatively introduces a reinforcement fine-tuning (RFT) stage guided by human engineer-annotated “teacher trajectories.” The final agent reinforcement learning phase addresses scalability challenges through multi-level prefix caching, entropy-based trajectory pruning, and efficient architecture.", + "capabilities": ["function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 128000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.6 + } + }, + "family": "kat-coder", + "ownedBy": "streamlake", + "openWeights": false + }, + { + "id": "hunyuan-a13b-instruct", + "name": "tencent/Hunyuan-A13B-Instruct", + "description": "Hunyuan-A13B is a 13B active parameter Mixture-of-Experts (MoE) language model developed by Tencent, with a total parameter count of 80B and support for reasoning via Chain-of-Thought. It offers competitive benchmark performance across mathematics, science, coding, and multi-turn reasoning tasks, while maintaining high inference efficiency via Grouped Query Attention (GQA) and quantization support (FP8, GPTQ, etc.).", + "capabilities": ["function-call", "structured-output", "reasoning"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131000, + "maxOutputTokens": 131000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.14 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.57 + } + }, + "reasoning": { + "supportedEfforts": ["none"] + }, + "family": "hunyuan", + "ownedBy": "tencent", + "openWeights": false + }, + { + "id": "hunyuan-mt", + "name": "tencent/Hunyuan-MT-7B", + "description": "Hunyuan-MT-7B is a lightweight translation model with 7 billion parameters, designed to translate source text into target languages. The model supports translation among 33 languages as well as 5 Chinese minority languages. In the WMT25 International Machine Translation Competition, Hunyuan-MT-7B achieved first place in 30 out of 31 language categories it participated in, demonstrating its exceptional translation capabilities. For translation scenarios, Tencent Hunyuan proposed a complete training paradigm from pre-training to supervised fine-tuning, followed by translation reinforcement and ensemble reinforcement, enabling it to achieve industry-leading performance among models of similar scale. The model is computationally efficient, easy to deploy, and suitable for various application scenarios.", + "capabilities": ["function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 33000, + "maxOutputTokens": 33000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.2 + } + }, + "family": "hunyuan", + "ownedBy": "tencent", + "openWeights": false + }, + { + "id": "paddleocr-vl-1-5", + "name": "PaddlePaddle/PaddleOCR-VL-1.5", + "capabilities": ["file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 16384, + "maxOutputTokens": 16384, + "ownedBy": "siliconflow-cn", + "openWeights": true + }, + { + "id": "glm-z1-0414", + "name": "THUDM/GLM-Z1-32B-0414", + "description": "GLM-Z1-32B-0414 is a reasoning-focused AI model built on GLM-4-32B-0414. It has been enhanced through cold-start methods and reinforcement learning, with a strong emphasis on math, coding, and logic tasks. Despite having only 32B parameters, it performs comparably to the 671B DeepSeek-R1 on some benchmarks. It excels in complex reasoning tasks, as shown in evaluations like AIME 24/25, LiveCodeBench, and GPQA.", + "capabilities": ["reasoning", "function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131000, + "maxOutputTokens": 131000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.14 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.57 + } + }, + "family": "glm-z", + "ownedBy": "zhipu", + "openWeights": false + }, + { + "id": "glm-4-0414", + "name": "THUDM/GLM-4-9B-0414", + "description": "GLM-4-32B-0414 is a next-generation open-source model with 32 billion parameters, delivering performance comparable to OpenAI’s GPT series and DeepSeek V3/R1. It supports smooth local deployment.\n\nThe base model was pre-trained on 15T of high-quality data, including a large amount of reasoning-focused synthetic content, setting the stage for advanced reinforcement learning.\n\nIn the post-training phase, techniques like human preference alignment, rejection sampling, and reinforcement learning were used to improve the model’s ability to follow instructions, generate code, and handle function calls—core skills needed for agent-style tasks.\n\nGLM-4-32B-0414 has shown strong results in engineering code, artifact generation, function calling, search-based QA, and report writing—sometimes matching or even surpassing larger models like GPT-4o and DeepSeek-V3 (671B) on specific benchmarks.", + "capabilities": ["function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 33000, + "maxOutputTokens": 33000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.086 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.086 + } + }, + "family": "glm", + "ownedBy": "zhipu", + "openWeights": false + }, + { + "id": "pangu-pro-moe", + "name": "ascend-tribe/pangu-pro-moe", + "capabilities": ["reasoning", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 128000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.6 + } + }, + "family": "pangu", + "ownedBy": "siliconflow-cn", + "openWeights": false + }, + { + "id": "ernie-4-5-a47b", + "name": "baidu/ERNIE-4.5-300B-A47B", + "description": "ERNIE-4.5-300B-A47B is a 300B parameter Mixture-of-Experts (MoE) language model developed by Baidu as part of the ERNIE 4.5 series. It activates 47B parameters per token and supports text generation in both English and Chinese. Optimized for high-throughput inference and efficient scaling, it uses a heterogeneous MoE structure with advanced routing and quantization strategies, including FP8 and 2-bit formats. This version is fine-tuned for language-only tasks and supports reasoning, tool parameters, and extended context lengths up to 131k tokens. Suitable for general-purpose LLM applications with high reasoning and throughput demands.", + "capabilities": ["function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131000, + "maxOutputTokens": 131000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.28 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.1 + } + }, + "family": "ernie", + "ownedBy": "baidu", + "openWeights": false + }, + { + "id": "seed-oss-instruct", + "name": "ByteDance-Seed/Seed-OSS-36B-Instruct", + "description": "Seed-OSS is a series of open-source large language models developed by ByteDance's Seed team, designed specifically for powerful long-context processing, reasoning, agents, and general capabilities. Among this series, Seed-OSS-36B-Instruct is an instruction-tuned model with 36 billion parameters that natively supports ultra-long context lengths, enabling it to process massive documents or complex codebases in a single pass. This model is specially optimized for reasoning, code generation, and agent tasks (such as tool usage), while maintaining balanced and excellent general capabilities. A notable feature of this model is the \"Thinking Budget\" functionality, which allows users to flexibly adjust the inference length as needed, thereby effectively improving inference efficiency in practical applications.", + "capabilities": ["function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 262000, + "maxOutputTokens": 262000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.21 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.57 + } + }, + "family": "seed", + "ownedBy": "bytedance", + "openWeights": false + }, + { + "id": "qwen2-5-instruct-128k", + "name": "Qwen/Qwen2.5-72B-Instruct-128K", + "capabilities": ["function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131000, + "maxOutputTokens": 4000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.59 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.59 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": false + }, + { + "id": "qwen3-omni-a3b-captioner", + "name": "Qwen/Qwen3-Omni-30B-A3B-Captioner", + "capabilities": [ + "function-call", + "structured-output", + "file-input", + "audio-recognition", + "image-recognition", + "reasoning" + ], + "inputModalities": ["audio"], + "outputModalities": ["text"], + "contextWindow": 66000, + "maxOutputTokens": 66000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.1 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.4 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 38912 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": false + }, + { + "id": "deepseek-vl2", + "name": "deepseek-ai/deepseek-vl2", + "capabilities": ["function-call", "structured-output", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 4000, + "maxOutputTokens": 4000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.15 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.15 + } + }, + "family": "deepseek", + "ownedBy": "deepseek", + "openWeights": false + }, + { + "id": "hermes-4-3", + "name": "Hermes 4.3 36B", + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 32768, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.1 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.39 + } + }, + "family": "nousresearch", + "ownedBy": "nousresearch", + "openWeights": true + }, + { + "id": "hermes-4-fp8", + "name": "Hermes 4 405B FP8 TEE", + "capabilities": ["reasoning", "function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 65536, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.2 + } + }, + "family": "nousresearch", + "ownedBy": "nousresearch", + "openWeights": true + }, + { + "id": "deephermes-3-mistral-preview", + "name": "DeepHermes 3 Mistral 24B Preview", + "capabilities": ["function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 32768, + "maxOutputTokens": 32768, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.02 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.1 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.01 + } + }, + "family": "nousresearch", + "ownedBy": "nousresearch", + "openWeights": true + }, + { + "id": "dots.ocr", + "name": "dots.ocr", + "capabilities": ["structured-output", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 131072, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.01 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.01 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.005 + } + }, + "family": "rednote", + "ownedBy": "chutes", + "openWeights": true + }, + { + "id": "kimi-k2-thinking", + "name": "Kimi K2 Thinking TEE", + "description": "Kimi K2 Thinking is Moonshot AI’s most advanced open reasoning model to date, extending the K2 series into agentic, long-horizon reasoning. Built on the trillion-parameter Mixture-of-Experts (MoE) architecture introduced in Kimi K2, it activates 32 billion parameters per forward pass and supports 256 k-token context windows. The model is optimized for persistent step-by-step thought, dynamic tool invocation, and complex reasoning workflows that span hundreds of turns. It interleaves step-by-step reasoning with tool use, enabling autonomous research, coding, and writing that can persist for hundreds of sequential actions without drift.\n\nIt sets new open-source benchmarks on HLE, BrowseComp, SWE-Multilingual, and LiveCodeBench, while maintaining stable multi-agent behavior through 200–300 tool calls. Built on a large-scale MoE architecture with MuonClip optimization, it combines strong reasoning depth with high inference efficiency for demanding agentic and analytical tasks.", + "capabilities": ["reasoning", "function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 262144, + "maxOutputTokens": 65535, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.4 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.75 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.14100000000000001 + } + }, + "reasoning": { + "supportedEfforts": [], + "interleaved": true + }, + "family": "kimi-thinking", + "ownedBy": "moonshot", + "openWeights": true + }, + { + "id": "nemotron-3-nano-a3b-bf16", + "name": "NVIDIA Nemotron 3 Nano 30B A3B BF16", + "capabilities": ["function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 262144, + "maxOutputTokens": 262144, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.06 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.24 + } + }, + "family": "nemotron", + "ownedBy": "nvidia", + "openWeights": true + }, + { + "id": "tng-r1t-chimera-turbo", + "name": "TNG R1T Chimera Turbo", + "capabilities": ["reasoning", "function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 163840, + "maxOutputTokens": 65536, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.22 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.6 + } + }, + "ownedBy": "chutes", + "openWeights": true + }, + { + "id": "deepseek-r1t-chimera", + "name": "DeepSeek R1T Chimera", + "description": "Provided by chutes.ai\nDeepSeek-R1T-Chimera merges DeepSeek-R1’s reasoning strengths with DeepSeek-V3 (0324)’s token-efficiency improvements into a MoE Transformer optimized for general text generation. It integrates pretrained weights from both models and is released under the MIT license for research and commercial use.\n", + "capabilities": ["reasoning", "structured-output", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 163840, + "maxOutputTokens": 163840, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.2 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.15 + } + }, + "family": "tngtech", + "ownedBy": "deepseek", + "openWeights": true + }, + { + "id": "deepseek-tng-r1t2-chimera", + "name": "DeepSeek TNG R1T2 Chimera", + "capabilities": ["reasoning", "function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 163840, + "maxOutputTokens": 163840, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.25 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.85 + } + }, + "family": "tngtech", + "ownedBy": "deepseek", + "openWeights": true + }, + { + "id": "tng-r1t-chimera", + "name": "TNG R1T Chimera TEE", + "capabilities": ["reasoning", "function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 163840, + "maxOutputTokens": 65536, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.25 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.85 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.125 + } + }, + "family": "tngtech", + "ownedBy": "chutes", + "openWeights": true + }, + { + "id": "internvl3", + "name": "InternVL3 78B TEE", + "description": "The InternVL3 series is an advanced multimodal large language model (MLLM). Compared to InternVL 2.5, InternVL3 demonstrates stronger multimodal perception and reasoning capabilities. \n\nIn addition, InternVL3 is benchmarked against the Qwen2.5 Chat models, whose pre-trained base models serve as the initialization for its language component. Benefiting from Native Multimodal Pre-Training, the InternVL3 series surpasses the Qwen2.5 series in overall text performance.", + "capabilities": ["structured-output", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 32768, + "maxOutputTokens": 32768, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.1 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.39 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.075 + } + }, + "family": "opengvlab", + "ownedBy": "intern", + "openWeights": true + }, + { + "id": "mistral-small-3-2-instruct-2506", + "name": "Mistral Small 3.2 24B Instruct 2506", + "capabilities": ["function-call", "structured-output", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 131072, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.06 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.18 + } + }, + "family": "chutesai", + "ownedBy": "mistral", + "openWeights": true + }, + { + "id": "mistral-nemo-instruct-2407", + "name": "Mistral Nemo Instruct 2407", + "capabilities": ["structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 131072, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.02 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.04 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.01 + } + }, + "family": "unsloth", + "ownedBy": "mistral", + "openWeights": true + }, + { + "id": "mistral-small-instruct-2501", + "name": "Mistral Small 24B Instruct 2501", + "description": "Mistral Small 3 is a 24B-parameter language model optimized for low-latency performance across common AI tasks. Released under the Apache 2.0 license, it features both pre-trained and instruction-tuned versions designed for efficient local deployment.\n\nThe model achieves 81% accuracy on the MMLU benchmark and performs competitively with larger models like Llama 3.3 70B and Qwen 32B, while operating at three times the speed on equivalent hardware. [Read the blog post about the model here.](https://mistral.ai/news/mistral-small-3/)", + "capabilities": ["function-call", "structured-output", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 32768, + "maxOutputTokens": 32768, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.03 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.11 + } + }, + "family": "unsloth", + "ownedBy": "mistral", + "openWeights": true + }, + { + "id": "qwen3guard-gen", + "name": "Qwen3Guard Gen 0.6B", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 32768, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.01 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.01 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.005 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": true + }, + { + "id": "qwen3-coder-a35b-instruct-fp8", + "name": "Qwen3 Coder 480B A35B Instruct FP8 TEE", + "capabilities": ["function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 262144, + "maxOutputTokens": 262144, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.22 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.95 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.11 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": true + }, + { + "id": "mirothinker-v1-5", + "name": "MiroThinker V1.5 235B", + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 262144, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.2 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.15 + } + }, + "ownedBy": "chutes", + "openWeights": true + }, + { + "id": "glm-4-6-fp8", + "name": "GLM 4.6 FP8", + "capabilities": ["reasoning", "function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 202752, + "maxOutputTokens": 65535, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.2 + } + }, + "reasoning": { + "supportedEfforts": ["none"], + "interleaved": true + }, + "ownedBy": "zhipu", + "openWeights": true + }, + { + "id": "glm-4-5-fp8", + "name": "GLM 4.5 FP8", + "capabilities": ["reasoning", "function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 65536, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.2 + } + }, + "reasoning": { + "supportedEfforts": ["none"], + "interleaved": true + }, + "family": "glm", + "ownedBy": "zhipu", + "openWeights": true + }, + { + "id": "deepseek-v3-2-speciale", + "name": "DeepSeek V3.2 Speciale TEE", + "description": "DeepSeek-V3.2-Speciale is a high-compute variant of DeepSeek-V3.2 optimized for maximum reasoning and agentic performance. It builds on DeepSeek Sparse Attention (DSA) for efficient long-context processing, then scales post-training reinforcement learning to push capability beyond the base model. Reported evaluations place Speciale ahead of GPT-5 on difficult reasoning workloads, with proficiency comparable to Gemini-3.0-Pro, while retaining strong coding and tool-use reliability. Like V3.2, it benefits from a large-scale agentic task synthesis pipeline that improves compliance and generalization in interactive environments.", + "capabilities": ["reasoning", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 163840, + "maxOutputTokens": 65536, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.27 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.41 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.135 + } + }, + "reasoning": { + "supportedEfforts": ["none"], + "interleaved": true + }, + "family": "deepseek", + "ownedBy": "deepseek", + "openWeights": true + }, + { + "id": "k2-5", + "name": "Kimi K2.5", + "capabilities": ["reasoning", "function-call", "structured-output", "image-recognition", "video-recognition"], + "inputModalities": ["text", "image", "video"], + "outputModalities": ["text"], + "contextWindow": 262144, + "maxOutputTokens": 32768, + "family": "kimi-thinking", + "ownedBy": "kimi-for-coding", + "openWeights": true + }, + { + "id": "nova-pro-v1", + "name": "Nova Pro 1.0", + "description": "Amazon Nova Pro 1.0 is a capable multimodal model from Amazon focused on providing a combination of accuracy, speed, and cost for a wide range of tasks. As of December 2024, it achieves state-of-the-art performance on key benchmarks including visual question answering (TextVQA) and video understanding (VATEX).\n\nAmazon Nova Pro demonstrates strong capabilities in processing both visual and textual information and at analyzing financial documents.\n\n**NOTE**: Video input is not supported at this time.", + "capabilities": ["function-call", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 300000, + "maxOutputTokens": 5000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.016 + }, + "output": { + "currency": "USD", + "perMillionTokens": 4.061 + } + }, + "family": "nova-pro", + "ownedBy": "amazon", + "openWeights": false + }, + { + "id": "claude-4-5-sonnet", + "name": "Claude 4.5 Sonnet", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 200000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 3.259 + }, + "output": { + "currency": "USD", + "perMillionTokens": 16.296 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.30000000000000004 + } + }, + "family": "claude-sonnet", + "ownedBy": "anthropic", + "openWeights": false + }, + { + "id": "devstral-small-2512", + "name": "Devstral Small 2 2512", + "capabilities": ["function-call", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 262000, + "maxOutputTokens": 262000, + "ownedBy": "mistral", + "openWeights": true + }, + { + "id": "jais-chat", + "name": "JAIS 30b Chat", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 8192, + "maxOutputTokens": 2048, + "family": "jais", + "ownedBy": "github-models", + "openWeights": true + }, + { + "id": "command-r", + "name": "Cohere Command R", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.64 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.92 + } + }, + "family": "command-r", + "ownedBy": "cohere", + "openWeights": false + }, + { + "id": "command-r-plus", + "name": "Cohere Command R+", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 3.84 + }, + "output": { + "currency": "USD", + "perMillionTokens": 19.2 + } + }, + "family": "command-r", + "ownedBy": "cohere", + "openWeights": false + }, + { + "id": "codestral-2501", + "name": "Codestral 25.01", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 32000, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.9 + } + }, + "family": "codestral", + "ownedBy": "mistral", + "openWeights": false + }, + { + "id": "mistral-small-2503", + "name": "Mistral Small 3.1", + "capabilities": ["reasoning", "function-call", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 32768, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.1 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.3 + } + }, + "family": "mistral-small", + "ownedBy": "mistral", + "openWeights": false + }, + { + "id": "phi-3-mini-4k-instruct", + "name": "Phi-3-mini instruct (4k)", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 4096, + "maxOutputTokens": 1024, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.13 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.52 + } + }, + "family": "phi", + "ownedBy": "microsoft", + "openWeights": true + }, + { + "id": "phi-4", + "name": "Phi-4", + "description": "[Microsoft Research](/microsoft) Phi-4 is designed to perform well in complex reasoning tasks and can operate efficiently in situations with limited memory or where quick responses are needed. \n\nAt 14 billion parameters, it was trained on a mix of high-quality synthetic datasets, data from curated websites, and academic materials. It has undergone careful improvement to follow instructions accurately and maintain strong safety standards. It works best with English language inputs.\n\nFor more information, please see [Phi-4 Technical Report](https://arxiv.org/pdf/2412.08905)\n", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 16000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.125 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.5 + } + }, + "family": "phi", + "ownedBy": "microsoft", + "openWeights": true + }, + { + "id": "phi-4-mini-reasoning", + "name": "Phi-4-mini-reasoning", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.075 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.3 + } + }, + "family": "phi", + "ownedBy": "microsoft", + "openWeights": true + }, + { + "id": "phi-3-5-mini-instruct", + "name": "Phi-3.5-mini instruct (128k)", + "description": "Phi-3.5-mini is a lightweight, state-of-the-art open model built upon the dataset used for Phi-3—which includes synthetic data and carefully curated publicly available websites—focusing on very high-quality, reasoning-intensive data. This model is part of the Phi-3 model family and supports a context length of 128K tokens.", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.13 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.52 + } + }, + "family": "phi", + "ownedBy": "microsoft", + "openWeights": true + }, + { + "id": "phi-3-mini-128k-instruct", + "name": "Phi-3-mini instruct (128k)", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.13 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.52 + } + }, + "family": "phi", + "ownedBy": "microsoft", + "openWeights": true + }, + { + "id": "phi-4-reasoning", + "name": "Phi-4-Reasoning", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.125 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.5 + } + }, + "family": "phi", + "ownedBy": "microsoft", + "openWeights": true + }, + { + "id": "mai-ds-r1", + "name": "MAI-DS-R1", + "description": "MAI-DS-R1 is a refined version of DeepSeek-R1 by Microsoft AI, designed to improve responsiveness to previously blocked topics while enhancing safety. It integrates 110k Tulu-3 SFT samples and 350k multilingual safety-alignment examples. The model retains strong reasoning and coding abilities, surpasses R1-1776 in handling sensitive queries, and reduces harmful content leakage. Based on a transformer MoE architecture, it suits general-purpose tasks—excluding legal, medical, or autonomous systems.", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 65536, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.35 + }, + "output": { + "currency": "USD", + "perMillionTokens": 5.4 + } + }, + "family": "mai", + "ownedBy": "github-models", + "openWeights": false + }, + { + "id": "o1-preview", + "name": "OpenAI o1-preview", + "description": "The latest and most powerful inference model from OpenAI; AiHubMix uses both OpenAI and Microsoft Azure OpenAI channels simultaneously to achieve high-concurrency load balancing.", + "capabilities": ["reasoning"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 32768, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 16.5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 66 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 8.25 + } + }, + "reasoning": { + "supportedEfforts": ["low", "medium", "high"] + }, + "family": "o", + "ownedBy": "openai", + "openWeights": false + }, + { + "id": "o1-mini", + "name": "OpenAI o1-mini", + "description": "o1-mini is faster and 80% cheaper, and is competitive with o1-preview on coding tasks. AiHubMix uses both OpenAI and Microsoft Azure OpenAI channels simultaneously.", + "capabilities": ["reasoning"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 65536, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.1 + }, + "output": { + "currency": "USD", + "perMillionTokens": 4.4 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.55 + } + }, + "reasoning": { + "supportedEfforts": ["low", "medium", "high"] + }, + "family": "o-mini", + "ownedBy": "openai", + "openWeights": false + }, + { + "id": "jamba-1-5-large", + "name": "AI21 Jamba 1.5 Large", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 256000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2.2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 8.8 + } + }, + "family": "jamba", + "ownedBy": "ai21", + "openWeights": false + }, + { + "id": "jamba-1-5-mini", + "name": "AI21 Jamba 1.5 Mini", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 256000, + "maxOutputTokens": 4096, + "family": "jamba", + "ownedBy": "ai21", + "openWeights": false + }, + { + "id": "rnj-1-instruct", + "name": "Rnj-1 Instruct", + "description": "Rnj-1 is an 8B-parameter, dense, open-weight model family developed by Essential AI and trained from scratch with a focus on programming, math, and scientific reasoning. The model demonstrates strong performance across multiple programming languages, tool-use workflows, and agentic execution environments (e.g., mini-SWE-agent). ", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 32768, + "maxOutputTokens": 32768, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.15 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.15 + } + }, + "family": "rnj", + "ownedBy": "essentialai", + "openWeights": true + }, + { + "id": "llama-3-3-instruct-turbo", + "name": "Llama 3.3 70B", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 131072, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.88 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.88 + } + }, + "family": "llama", + "ownedBy": "meta", + "openWeights": true + }, + { + "id": "qwen3-coder-next-fp8", + "name": "Qwen3 Coder Next FP8", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 262144, + "maxOutputTokens": 262144, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.2 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": true + }, + { + "id": "qwen3-a22b-instruct-2507-tput", + "name": "Qwen3 235B A22B Instruct 2507 FP8", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 262144, + "maxOutputTokens": 262144, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.6 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": true + }, + { + "id": "gpt-4", + "name": "GPT-4", + "description": "OpenAI's flagship model, GPT-4 is a large-scale multimodal language model capable of solving difficult problems with greater accuracy than previous models due to its broader general knowledge and advanced reasoning capabilities. Training data: up to Sep 2021.", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 8192, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 60 + }, + "output": { + "currency": "USD", + "perMillionTokens": 120 + } + }, + "family": "gpt", + "ownedBy": "openai", + "openWeights": false + }, + { + "id": "embed-v-4-0", + "name": "Embed v4", + "capabilities": ["file-input", "image-recognition", "embedding"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 1536, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.12 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "family": "cohere-embed", + "ownedBy": "cohere", + "openWeights": true + }, + { + "id": "embed-v3-multilingual", + "name": "Embed v3 Multilingual", + "capabilities": ["embedding"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 512, + "maxOutputTokens": 1024, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.1 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "family": "cohere-embed", + "ownedBy": "cohere", + "openWeights": true + }, + { + "id": "phi-4-mini", + "name": "Phi-4-mini", + "description": "Phi-4-mini-reasoning is a lightweight open model designed for advanced mathematical reasoning and logic-intensive problem-solving. It is particularly well-suited for tasks such as formal proofs, symbolic computation, and solving multi-step word problems. With its efficient architecture, the model balances high-quality reasoning performance with cost-effective deployment, making it ideal for educational applications, embedded tutoring, and lightweight edge or mobile systems.\n\nPhi-4-mini-reasoning supports a 128K token context length, enabling it to process and reason over long mathematical problems and proofs. Built on synthetic and high-quality math datasets, the model leverages advanced fine-tuning techniques such as supervised fine-tuning and preference modeling to enhance reasoning capabilities. Its training incorporates safety and alignment protocols, ensuring robust and reliable performance across supported use cases.", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.075 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.3 + } + }, + "family": "phi", + "ownedBy": "microsoft", + "openWeights": true + }, + { + "id": "gpt-4-32k", + "name": "GPT-4 32K", + "description": "The smartest version of GPT-4; OpenAI no longer offers it officially. All the 32k versions on this site are provided by Microsoft, deployed on Azure OpenAI by the official Microsoft service.", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 32768, + "maxOutputTokens": 32768, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 60 + }, + "output": { + "currency": "USD", + "perMillionTokens": 120 + } + }, + "family": "gpt", + "ownedBy": "openai", + "openWeights": false + }, + { + "id": "gpt-3-5-turbo-0125", + "name": "GPT-3.5 Turbo 0125", + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 16384, + "maxOutputTokens": 16384, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.5 + } + }, + "family": "gpt", + "ownedBy": "openai", + "openWeights": false + }, + { + "id": "gpt-3-5-turbo-0613", + "name": "GPT-3.5 Turbo 0613", + "description": "GPT-3.5 Turbo is OpenAI's fastest model. It can understand and generate natural language or code, and is optimized for chat and traditional completion tasks.\n\nTraining data up to Sep 2021.", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 16384, + "maxOutputTokens": 16384, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 4 + } + }, + "family": "gpt", + "ownedBy": "openai", + "openWeights": false + }, + { + "id": "model-router", + "name": "Model Router", + "capabilities": ["function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 16384, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.14 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "family": "model-router", + "ownedBy": "azure", + "openWeights": false + }, + { + "id": "gpt-3-5-turbo-0301", + "name": "GPT-3.5 Turbo 0301", + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 4096, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2 + } + }, + "family": "gpt", + "ownedBy": "openai", + "openWeights": false + }, + { + "id": "phi-4-multimodal", + "name": "Phi-4-multimodal", + "capabilities": ["file-input", "image-recognition", "audio-recognition"], + "inputModalities": ["text", "image", "audio"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.08 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.32 + } + }, + "family": "phi", + "ownedBy": "microsoft", + "openWeights": true + }, + { + "id": "gpt-5-1-chat", + "name": "GPT-5.1 Chat", + "description": "GPT-5.1 Chat (AKA Instant is the fast, lightweight member of the 5.1 family, optimized for low-latency chat while retaining strong general intelligence. It uses adaptive reasoning to selectively “think” on harder queries, improving accuracy on math, coding, and multi-step tasks without slowing down typical conversations. The model is warmer and more conversational by default, with better instruction following and more stable short-form reasoning. GPT-5.1 Chat is designed for high-throughput, interactive workloads where responsiveness and consistency matter more than deep deliberation.\n", + "capabilities": [ + "reasoning", + "function-call", + "file-input", + "image-recognition", + "image-generation", + "audio-recognition", + "audio-generation", + "web-search" + ], + "inputModalities": ["text", "image", "audio"], + "outputModalities": ["text", "image", "audio"], + "contextWindow": 128000, + "maxOutputTokens": 16384, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.25 + }, + "output": { + "currency": "USD", + "perMillionTokens": 10 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.125 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"] + }, + "family": "gpt-codex", + "ownedBy": "openai", + "openWeights": false + }, + { + "id": "embed-v3-english", + "name": "Embed v3 English", + "capabilities": ["embedding"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 512, + "maxOutputTokens": 1024, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.1 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "family": "cohere-embed", + "ownedBy": "cohere", + "openWeights": true + }, + { + "id": "gpt-4-turbo-vision", + "name": "GPT-4 Turbo Vision", + "capabilities": ["function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 10 + }, + "output": { + "currency": "USD", + "perMillionTokens": 30 + } + }, + "family": "gpt", + "ownedBy": "openai", + "openWeights": false + }, + { + "id": "phi-4-reasoning-plus", + "name": "Phi-4-reasoning-plus", + "capabilities": ["reasoning"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 32000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.125 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.5 + } + }, + "family": "phi", + "ownedBy": "microsoft", + "openWeights": true + }, + { + "id": "gpt-3-5-turbo-1106", + "name": "GPT-3.5 Turbo 1106", + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 16384, + "maxOutputTokens": 16384, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2 + } + }, + "family": "gpt", + "ownedBy": "openai", + "openWeights": false + }, + { + "id": "deepseek-v3-1-nex-n1", + "name": "nex-agi/DeepSeek-V3.1-Nex-N1", + "description": "DeepSeek V3.1 Nex-N1 is the flagship release of the Nex-N1 series — a post-trained model designed to highlight agent autonomy, tool use, and real-world productivity. \n\nNex-N1 demonstrates competitive performance across all evaluation scenarios, showing particularly strong results in practical coding and HTML generation tasks.", + "capabilities": ["reasoning", "function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131000, + "maxOutputTokens": 131000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2 + } + }, + "reasoning": { + "supportedEfforts": ["none"] + }, + "family": "deepseek", + "ownedBy": "deepseek", + "openWeights": false + }, + { + "id": "llama-prompt-guard-2-86m", + "name": "Meta Llama Prompt Guard 2 86M", + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 512, + "maxOutputTokens": 2, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.01 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.01 + } + }, + "family": "llama", + "ownedBy": "meta", + "openWeights": false + }, + { + "id": "claude-4-5-haiku", + "name": "Anthropic: Claude 4.5 Haiku", + "capabilities": ["function-call", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1 + }, + "output": { + "currency": "USD", + "perMillionTokens": 5 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.09999999999999999 + } + }, + "family": "claude-haiku", + "ownedBy": "anthropic", + "openWeights": false + }, + { + "id": "gpt-4-1-mini-2025-04-14", + "name": "OpenAI GPT-4.1 Mini", + "capabilities": ["function-call", "image-recognition", "web-search"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 1047576, + "maxOutputTokens": 32768, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.39999999999999997 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.5999999999999999 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.09999999999999999 + } + }, + "family": "gpt-mini", + "ownedBy": "openai", + "openWeights": false + }, + { + "id": "llama-prompt-guard-2-22m", + "name": "Meta Llama Prompt Guard 2 22M", + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 512, + "maxOutputTokens": 2, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.01 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.01 + } + }, + "family": "llama", + "ownedBy": "meta", + "openWeights": false + }, + { + "id": "claude-3-5-sonnet-v2", + "name": "Anthropic: Claude 3.5 Sonnet v2", + "capabilities": ["function-call", "image-recognition", "web-search", "computer-use"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 15 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.30000000000000004 + } + }, + "family": "claude-sonnet", + "ownedBy": "anthropic", + "openWeights": false + }, + { + "id": "sonar-deep-research", + "name": "Perplexity Sonar Deep Research", + "description": "Sonar Deep Research is a research-focused model designed for multi-step retrieval, synthesis, and reasoning across complex topics. It autonomously searches, reads, and evaluates sources, refining its approach as it gathers information. This enables comprehensive report generation across domains like finance, technology, health, and current events.\n\nNotes on Pricing ([Source](https://docs.perplexity.ai/guides/pricing#detailed-pricing-breakdown-for-sonar-deep-research)) \n- Input tokens comprise of Prompt tokens (user prompt) + Citation tokens (these are processed tokens from running searches)\n- Deep Research runs multiple searches to conduct exhaustive research. Searches are priced at $5/1000 searches. A request that does 30 searches will cost $0.15 in this step.\n- Reasoning is a distinct step in Deep Research since it does extensive automated reasoning through all the material it gathers during its research phase. Reasoning tokens here are a bit different than the CoTs in the answer - these are tokens that we use to reason through the research material prior to generating the outputs via the CoTs. Reasoning tokens are priced at $3/1M tokens", + "capabilities": ["reasoning", "web-search"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 127000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 8 + } + }, + "reasoning": { + "supportedEfforts": ["low", "medium", "high"] + }, + "family": "sonar-deep-research", + "ownedBy": "perplexity", + "openWeights": false + }, + { + "id": "kimi-k2-0711", + "name": "Kimi K2 (07/11)", + "description": "Kimi-K2 is a MoE architecture foundational model with extremely powerful coding and agent capabilities, featuring a total of 1 trillion parameters and activating 32 billion parameters. In benchmark performance tests across major categories such as general knowledge reasoning, programming, mathematics, and agents, the K2 model outperforms other mainstream open-source models.\nThe Kimi-K2 model supports a context length of 128k tokens.\nIt does not support visual capabilities.", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 16384, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.5700000000000001 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2.3 + } + }, + "family": "kimi", + "ownedBy": "moonshot", + "openWeights": false + }, + { + "id": "codex-mini-latest", + "name": "OpenAI Codex Mini Latest", + "description": "Only supports v1/responses API calls.https://docs.aihubmix.com/en/api/Responses-API\ncodex-mini-latest is a fine-tuned version of o4-mini specifically for use in Codex CLI. For direct use in the API, we recommend starting with gpt-4.1.", + "capabilities": ["function-call", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 100000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 6 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.375 + } + }, + "family": "gpt-codex-mini", + "ownedBy": "helicone", + "openWeights": false + }, + { + "id": "claude-4-5-opus", + "name": "Anthropic: Claude Opus 4.5", + "capabilities": ["reasoning", "function-call", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 25 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.5 + } + }, + "family": "claude-opus", + "ownedBy": "anthropic", + "openWeights": false + }, + { + "id": "claude-3-haiku-20240307", + "name": "Anthropic: Claude 3 Haiku", + "capabilities": ["function-call", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.25 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.25 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.03 + } + }, + "family": "claude-haiku", + "ownedBy": "anthropic", + "openWeights": false + }, + { + "id": "gemini-3-pro", + "name": "Gemini 3 Pro", + "capabilities": [ + "reasoning", + "function-call", + "structured-output", + "file-input", + "image-recognition", + "audio-recognition", + "video-recognition", + "web-search" + ], + "inputModalities": ["text", "image", "video", "audio"], + "outputModalities": ["text"], + "contextWindow": 1048576, + "maxOutputTokens": 65536, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 12 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.2 + } + }, + "reasoning": { + "supportedEfforts": ["low", "high"], + "thinkingTokenLimits": { + "min": 128, + "max": 32768 + } + }, + "family": "gemini-pro", + "ownedBy": "google", + "openWeights": false + }, + { + "id": "gemini-3-1-pro", + "name": "Gemini 3.1 Pro Preview", + "capabilities": [ + "reasoning", + "function-call", + "structured-output", + "file-input", + "image-recognition", + "audio-recognition", + "video-recognition" + ], + "inputModalities": ["text", "image", "video", "audio"], + "outputModalities": ["text"], + "contextWindow": 1048576, + "maxOutputTokens": 65536, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 12 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.2 + } + }, + "reasoning": { + "supportedEfforts": ["low", "medium", "high"], + "thinkingTokenLimits": { + "min": 128, + "max": 32768 + } + }, + "family": "gemini-pro", + "ownedBy": "google", + "openWeights": false + }, + { + "id": "big-pickle", + "name": "Big Pickle", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 128000, + "family": "big-pickle", + "ownedBy": "opencode", + "openWeights": false + }, + { + "id": "grok-code", + "name": "Grok Code Fast 1", + "capabilities": ["reasoning", "function-call", "file-input", "web-search"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 256000, + "maxOutputTokens": 256000, + "family": "grok", + "ownedBy": "xai", + "openWeights": false + }, + { + "id": "minimax-m2-5-highspeed", + "name": "MiniMax-M2.5-highspeed", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 204800, + "maxOutputTokens": 131072, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.6 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2.4 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.06 + } + }, + "reasoning": { + "supportedEfforts": [], + "interleaved": true + }, + "family": "minimax", + "ownedBy": "minimax", + "openWeights": true + }, + { + "id": "gemini-flash-lite-latest", + "name": "Gemini Flash-Lite Latest", + "capabilities": [ + "reasoning", + "function-call", + "structured-output", + "file-input", + "image-recognition", + "audio-recognition", + "video-recognition" + ], + "inputModalities": ["text", "image", "audio", "video"], + "outputModalities": ["text"], + "contextWindow": 1048576, + "maxOutputTokens": 65536, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.1 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.4 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.025 + } + }, + "family": "gemini-flash-lite", + "ownedBy": "google", + "openWeights": false + }, + { + "id": "gemini-flash-latest", + "name": "Gemini Flash Latest", + "capabilities": [ + "reasoning", + "function-call", + "structured-output", + "file-input", + "image-recognition", + "audio-recognition", + "video-recognition" + ], + "inputModalities": ["text", "image", "audio", "video"], + "outputModalities": ["text"], + "contextWindow": 1048576, + "maxOutputTokens": 65536, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2.5 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.075 + } + }, + "family": "gemini-flash", + "ownedBy": "google", + "openWeights": false + }, + { + "id": "gemini-2-5-pro-preview-05-06", + "name": "Gemini 2.5 Pro Preview 05-06", + "description": "Gemini 2.5 Pro is Google’s state-of-the-art AI model designed for advanced reasoning, coding, mathematics, and scientific tasks. It employs “thinking” capabilities, enabling it to reason through responses with enhanced accuracy and nuanced context handling. Gemini 2.5 Pro achieves top-tier performance on multiple benchmarks, including first-place positioning on the LMArena leaderboard, reflecting superior human-preference alignment and complex problem-solving abilities.", + "capabilities": [ + "reasoning", + "function-call", + "structured-output", + "file-input", + "image-recognition", + "audio-recognition", + "video-recognition", + "web-search" + ], + "inputModalities": ["text", "image", "audio", "video"], + "outputModalities": ["text"], + "contextWindow": 1048576, + "maxOutputTokens": 65536, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.25 + }, + "output": { + "currency": "USD", + "perMillionTokens": 10 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.31 + } + }, + "reasoning": { + "supportedEfforts": ["low", "medium", "high"], + "thinkingTokenLimits": { + "min": 128, + "max": 32768 + } + }, + "family": "gemini-pro", + "ownedBy": "google", + "openWeights": false + }, + { + "id": "gemini-2-5-flash-preview-tts", + "name": "Gemini 2.5 Flash Preview TTS", + "description": "Gemini 2.5 Flash Preview TTS is a lightweight, low-latency text-to-speech model designed for real-time voice generation. It produces natural, expressive speech with accurate control over tone, style, and pacing, while dynamically adjusting speaking speed based on context and instructions. The model also maintains consistent and distinguishable voices across multi-turn and multi-speaker conversations, making it well-suited for interactive and conversational applications that require stable, high-quality audio output.", + "capabilities": ["audio-generation", "function-call", "image-recognition", "web-search", "reasoning"], + "inputModalities": ["text"], + "outputModalities": ["audio"], + "contextWindow": 8000, + "maxOutputTokens": 16000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 10 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 0, + "max": 24576 + } + }, + "family": "gemini-flash", + "ownedBy": "google", + "openWeights": false + }, + { + "id": "gemini-live-2-5-flash-preview-native-audio", + "name": "Gemini Live 2.5 Flash Preview Native Audio", + "capabilities": ["reasoning", "function-call", "audio-recognition", "audio-generation", "video-recognition"], + "inputModalities": ["text", "audio", "video"], + "outputModalities": ["text", "audio"], + "contextWindow": 131072, + "maxOutputTokens": 65536, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2 + } + }, + "family": "gemini-flash", + "ownedBy": "google", + "openWeights": false + }, + { + "id": "gemini-3-1-pro-preview-customtools", + "name": "Gemini 3.1 Pro Preview Custom Tools", + "description": "gemini-3.1-pro-preview-customtools\n\nFor users who build applications mixing bash and custom tools, the Gemini 3.1 Pro preview provides a separate endpoint accessible via the API call gemini-3.1-pro-preview-customtools. This endpoint is better at prioritizing your custom tools (for example, view_file or search_code).\n\nPlease note that while gemini-3.1-pro-preview-customtools is optimized for agent workflows that use custom tools and Bash, you may experience quality fluctuations in some use cases that cannot benefit from these tools.", + "capabilities": [ + "reasoning", + "function-call", + "structured-output", + "file-input", + "image-recognition", + "audio-recognition", + "video-recognition" + ], + "inputModalities": ["text", "image", "video", "audio"], + "outputModalities": ["text"], + "contextWindow": 1048576, + "maxOutputTokens": 65536, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 12 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.2 + } + }, + "reasoning": { + "supportedEfforts": ["low", "medium", "high"], + "thinkingTokenLimits": { + "min": 128, + "max": 32768 + } + }, + "family": "gemini-pro", + "ownedBy": "google", + "openWeights": false + }, + { + "id": "gemini-live-2-5-flash", + "name": "Gemini Live 2.5 Flash", + "capabilities": [ + "reasoning", + "function-call", + "file-input", + "image-recognition", + "audio-recognition", + "audio-generation", + "video-recognition" + ], + "inputModalities": ["text", "image", "audio", "video"], + "outputModalities": ["text", "audio"], + "contextWindow": 128000, + "maxOutputTokens": 8000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2 + } + }, + "family": "gemini-flash", + "ownedBy": "google", + "openWeights": false + }, + { + "id": "gemini-2-5-flash-preview-04-17", + "name": "Gemini 2.5 Flash Preview 04-17", + "capabilities": [ + "reasoning", + "function-call", + "file-input", + "image-recognition", + "audio-recognition", + "video-recognition", + "web-search" + ], + "inputModalities": ["text", "image", "audio", "video"], + "outputModalities": ["text"], + "contextWindow": 1048576, + "maxOutputTokens": 65536, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.15 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.6 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.0375 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 0, + "max": 24576 + } + }, + "family": "gemini-flash", + "ownedBy": "google", + "openWeights": false + }, + { + "id": "gemini-2-5-pro-preview-tts", + "name": "Gemini 2.5 Pro Preview TTS", + "description": "Gemini 2.5 Pro Preview TTS is a high-fidelity text-to-speech model designed for premium voice experiences and complex speech generation scenarios. It delivers highly natural and expressive audio with precise control over tone, style, and emotional nuance, while maintaining smooth, context-aware pacing across long-form content. The model excels in multi-speaker and dialogue-heavy use cases, preserving consistent character voices and conversational coherence, making it well-suited for narration, storytelling, and advanced conversational AI applications.", + "capabilities": ["audio-generation", "function-call", "image-recognition", "web-search", "reasoning"], + "inputModalities": ["text"], + "outputModalities": ["audio"], + "contextWindow": 8000, + "maxOutputTokens": 16000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1 + }, + "output": { + "currency": "USD", + "perMillionTokens": 20 + } + }, + "reasoning": { + "supportedEfforts": ["low", "medium", "high"], + "thinkingTokenLimits": { + "min": 128, + "max": 32768 + } + }, + "family": "gemini-flash", + "ownedBy": "google", + "openWeights": false + }, + { + "id": "gemini-1-5-flash", + "name": "Gemini 1.5 Flash", + "capabilities": ["function-call", "file-input", "image-recognition", "audio-recognition", "video-recognition"], + "inputModalities": ["text", "image", "audio", "video"], + "outputModalities": ["text"], + "contextWindow": 1000000, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.075 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.3 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.01875 + } + }, + "family": "gemini-flash", + "ownedBy": "google", + "openWeights": false + }, + { + "id": "gemini-1-5-pro", + "name": "Gemini 1.5 Pro", + "capabilities": ["function-call", "file-input", "image-recognition", "audio-recognition", "video-recognition"], + "inputModalities": ["text", "image", "audio", "video"], + "outputModalities": ["text"], + "contextWindow": 1000000, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.25 + }, + "output": { + "currency": "USD", + "perMillionTokens": 5 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.3125 + } + }, + "family": "gemini-pro", + "ownedBy": "google", + "openWeights": false + }, + { + "id": "gpt-oss-maas", + "name": "GPT OSS 120B", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 32768, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.09 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.36 + } + }, + "reasoning": { + "supportedEfforts": ["low", "medium", "high"] + }, + "family": "gpt-oss", + "ownedBy": "openai", + "openWeights": true + }, + { + "id": "qwen3-a22b-instruct-2507-maas", + "name": "Qwen3 235B A22B Instruct", + "capabilities": ["reasoning", "function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 262144, + "maxOutputTokens": 16384, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.22 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.88 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": true + }, + { + "id": "llama-4-maverick-128e-instruct-maas", + "name": "Llama 4 Maverick 17B 128E Instruct", + "capabilities": ["function-call", "structured-output", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 524288, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.35 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.15 + } + }, + "family": "llama", + "ownedBy": "meta", + "openWeights": true + }, + { + "id": "llama-3-3-instruct-maas", + "name": "Llama 3.3 70B Instruct", + "capabilities": ["function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.72 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.72 + } + }, + "family": "llama", + "ownedBy": "meta", + "openWeights": true + }, + { + "id": "glm-4-7-maas", + "name": "GLM-4.7", + "capabilities": ["reasoning", "function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 128000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.6 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2.2 + } + }, + "reasoning": { + "supportedEfforts": ["none"], + "interleaved": true + }, + "family": "glm", + "ownedBy": "zhipu", + "openWeights": true + }, + { + "id": "glm-5-maas", + "name": "GLM-5", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 204800, + "maxOutputTokens": 131072, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1 + }, + "output": { + "currency": "USD", + "perMillionTokens": 3.2 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.1 + } + }, + "reasoning": { + "supportedEfforts": ["none", "auto"] + }, + "family": "glm", + "ownedBy": "zhipu", + "openWeights": true + }, + { + "id": "deepseek-v3-1-maas", + "name": "DeepSeek V3.1", + "capabilities": ["reasoning", "function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 163840, + "maxOutputTokens": 32768, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.6 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.7 + } + }, + "reasoning": { + "supportedEfforts": ["none"] + }, + "family": "deepseek", + "ownedBy": "deepseek", + "openWeights": true + }, + { + "id": "granite-4-0-h-micro", + "name": "IBM Granite 4.0 H Micro", + "description": "Granite-4.0-H-Micro is a 3B parameter from the Granite 4 family of models. These models are the latest in a series of models released by IBM. They are fine-tuned for long context tool calling. ", + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 16384, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.017 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.11 + } + }, + "family": "granite", + "ownedBy": "cloudflare-workers-ai", + "openWeights": false + }, + { + "id": "bart-large-cnn", + "name": "BART Large CNN", + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 16384, + "family": "bart", + "ownedBy": "cloudflare-workers-ai", + "openWeights": false + }, + { + "id": "mistral-instruct-v0-1", + "name": "Mistral 7B Instruct v0.1", + "description": "A 7.3B parameter model that outperforms Llama 2 13B on all benchmarks, with optimizations for speed and context length.", + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 16384, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.11 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.19 + } + }, + "family": "mistral", + "ownedBy": "mistral", + "openWeights": false + }, + { + "id": "distilbert-sst-2-int8", + "name": "DistilBERT SST-2 INT8", + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 16384, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.026 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "family": "distilbert", + "ownedBy": "cloudflare-workers-ai", + "openWeights": false + }, + { + "id": "melotts", + "name": "MyShell MeloTTS", + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 16384, + "family": "melotts", + "ownedBy": "cloudflare-workers-ai", + "openWeights": false + }, + { + "id": "plamo-embedding", + "name": "PLaMo Embedding 1B", + "capabilities": ["embedding"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 16384, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.019 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "family": "plamo", + "ownedBy": "cloudflare-workers-ai", + "openWeights": false + }, + { + "id": "indictrans2-en-indic", + "name": "IndicTrans2 EN-Indic 1B", + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 16384, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.34 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.34 + } + }, + "family": "indictrans", + "ownedBy": "cloudflare-workers-ai", + "openWeights": false + }, + { + "id": "smart-turn-v2", + "name": "Pipecat Smart Turn v2", + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 16384, + "family": "smart-turn", + "ownedBy": "cloudflare-workers-ai", + "openWeights": false + }, + { + "id": "mistral-small-3-1-instruct", + "name": "Mistral Small 3.1 24B Instruct", + "description": "Mistral Small 3.1 24B Instruct is an upgraded variant of Mistral Small 3 (2501), featuring 24 billion parameters with advanced multimodal capabilities. It provides state-of-the-art performance in text-based reasoning and vision tasks, including image analysis, programming, mathematical reasoning, and multilingual support across dozens of languages. Equipped with an extensive 128k token context window and optimized for efficient local inference, it supports use cases such as conversational agents, function calling, long-document comprehension, and privacy-sensitive deployments. The updated version is [Mistral Small 3.2](mistralai/mistral-small-3.2-24b-instruct)", + "capabilities": ["function-call", "structured-output", "file-input", "image-recognition"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 16384, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.35 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.56 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.015 + } + }, + "family": "mistral-small", + "ownedBy": "mistral", + "openWeights": false + }, + { + "id": "aura-2-es", + "name": "Deepgram Aura 2 (ES)", + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 16384, + "family": "aura", + "ownedBy": "cloudflare-workers-ai", + "openWeights": false + }, + { + "id": "aura-2-en", + "name": "Deepgram Aura 2 (EN)", + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 16384, + "family": "aura", + "ownedBy": "cloudflare-workers-ai", + "openWeights": false + }, + { + "id": "nova-3", + "name": "Deepgram Nova 3", + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 16384, + "family": "nova", + "ownedBy": "amazon", + "openWeights": false + }, + { + "id": "gemma-sea-lion-v4-it", + "name": "Gemma SEA-LION v4 27B IT", + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 16384, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.35 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.56 + } + }, + "family": "gemma", + "ownedBy": "google", + "openWeights": false + }, + { + "id": "llama-3-1-instruct-fp8", + "name": "Llama 3.1 8B Instruct FP8", + "capabilities": ["function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 16384, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.15 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.29 + } + }, + "family": "llama", + "ownedBy": "meta", + "openWeights": false + }, + { + "id": "llama-2-chat-fp16", + "name": "Llama 2 7B Chat FP16", + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 16384, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.56 + }, + "output": { + "currency": "USD", + "perMillionTokens": 6.67 + } + }, + "family": "llama", + "ownedBy": "meta", + "openWeights": false + }, + { + "id": "m2m100", + "name": "M2M100 1.2B", + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 16384, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.34 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.34 + } + }, + "family": "m2m", + "ownedBy": "cloudflare-workers-ai", + "openWeights": false + }, + { + "id": "llama-3-3-instruct-fp8-fast", + "name": "Llama 3.3 70B Instruct FP8 Fast", + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 16384, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.29 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2.25 + } + }, + "family": "llama", + "ownedBy": "meta", + "openWeights": false + }, + { + "id": "llama-3-instruct-awq", + "name": "Llama 3 8B Instruct AWQ", + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 16384, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.12 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.27 + } + }, + "family": "llama", + "ownedBy": "meta", + "openWeights": false + }, + { + "id": "llama-3-1-instruct-awq", + "name": "Llama 3.1 8B Instruct AWQ", + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 16384, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.12 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.27 + } + }, + "family": "llama", + "ownedBy": "meta", + "openWeights": false + }, + { + "id": "bge-m3", + "name": "BGE M3", + "description": "The bge-m3 embedding model encodes sentences, paragraphs, and long documents into a 1024-dimensional dense vector space, delivering high-quality semantic embeddings optimized for multilingual retrieval, semantic search, and large-context applications.", + "capabilities": ["embedding"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 16384, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.012 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "family": "bge", + "ownedBy": "baai", + "openWeights": false + }, + { + "id": "bge-base-en-v1-5", + "name": "BGE Base EN v1.5", + "description": "The bge-base-en-v1.5 embedding model converts English sentences and paragraphs into 768-dimensional dense vectors, delivering efficient, high-quality semantic embeddings optimized for retrieval, semantic search, and document-matching workflows. This version (v1.5) features improved similarity-score distribution and stronger retrieval performance out of the box.", + "capabilities": ["embedding"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 16384, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.067 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "family": "bge", + "ownedBy": "baai", + "openWeights": false + }, + { + "id": "bge-large-en-v1-5", + "name": "BGE Large EN v1.5", + "description": "The bge-large-en-v1.5 embedding model maps English sentences, paragraphs, and documents into a 1024-dimensional dense vector space, delivering high-fidelity semantic embeddings optimized for semantic search, document retrieval, and downstream NLP tasks in English.", + "capabilities": ["embedding"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 16384, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "family": "bge", + "ownedBy": "baai", + "openWeights": false + }, + { + "id": "bge-reranker-base", + "name": "BGE Reranker Base", + "capabilities": ["embedding", "rerank"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 16384, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.0031 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "family": "bge", + "ownedBy": "baai", + "openWeights": false + }, + { + "id": "bge-small-en-v1-5", + "name": "BGE Small EN v1.5", + "capabilities": ["embedding"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 16384, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.02 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "family": "bge", + "ownedBy": "baai", + "openWeights": false + }, + { + "id": "mercury-coder", + "name": "Mercury Coder", + "description": "Mercury Coder is the first diffusion large language model (dLLM). Applying a breakthrough discrete diffusion approach, the model runs 5-10x faster than even speed optimized models like Claude 3.5 Haiku and GPT-4o Mini while matching their performance. Mercury Coder's speed means that developers can stay in the flow while coding, enjoying rapid chat-based iteration and responsive code completion suggestions. On Copilot Arena, Mercury Coder ranks 1st in speed and ties for 2nd in quality. Read more in the [blog post here](https://www.inceptionlabs.ai/blog/introducing-mercury).", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 16384, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.25 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.25 + } + }, + "family": "mercury", + "ownedBy": "inception", + "openWeights": false + }, + { + "id": "mercury", + "name": "Mercury", + "description": "Mercury is the first diffusion large language model (dLLM). Applying a breakthrough discrete diffusion approach, the model runs 5-10x faster than even speed optimized models like GPT-4.1 Nano and Claude 3.5 Haiku while matching their performance. Mercury's speed enables developers to provide responsive user experiences, including with voice agents, search interfaces, and chatbots. Read more in the [blog post]\n(https://www.inceptionlabs.ai/blog/introducing-mercury) here. ", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 16384, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.25 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.25 + } + }, + "family": "mercury", + "ownedBy": "inception", + "openWeights": false + }, + { + "id": "claude-3-sonnet", + "name": "Claude Sonnet 3", + "capabilities": ["function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 15 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.3 + } + }, + "family": "claude-sonnet", + "ownedBy": "anthropic", + "openWeights": false + }, + { + "id": "gpt-5-3-codex-spark", + "name": "GPT-5.3 Codex Spark", + "capabilities": [ + "reasoning", + "function-call", + "structured-output", + "file-input", + "image-recognition", + "web-search" + ], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 32000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.75 + }, + "output": { + "currency": "USD", + "perMillionTokens": 14 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.175 + } + }, + "family": "gpt-codex-spark", + "ownedBy": "openai", + "openWeights": false + }, + { + "id": "o1-pro", + "name": "o1-pro", + "description": "The o1 series of models are trained with reinforcement learning to think before they answer and perform complex reasoning. The o1-pro model uses more compute to think harder and provide consistently better answers.", + "capabilities": ["reasoning", "function-call", "structured-output", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 100000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 150 + }, + "output": { + "currency": "USD", + "perMillionTokens": 600 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 170 + } + }, + "reasoning": { + "supportedEfforts": ["low", "medium", "high"] + }, + "family": "o-pro", + "ownedBy": "openai", + "openWeights": false + }, + { + "id": "gpt-4o-2024-05-13", + "name": "GPT-4o (2024-05-13)", + "description": "GPT-4o (\"o\" for \"omni\") is OpenAI's latest AI model, supporting both text and image inputs with text outputs. It maintains the intelligence level of [GPT-4 Turbo](/models/openai/gpt-4-turbo) while being twice as fast and 50% more cost-effective. GPT-4o also offers improved performance in processing non-English languages and enhanced visual capabilities.\n\nFor benchmarking against other models, it was briefly called [\"im-also-a-good-gpt2-chatbot\"](https://twitter.com/LiamFedus/status/1790064963966370209)\n\n#multimodal", + "capabilities": ["function-call", "structured-output", "file-input", "image-recognition", "web-search"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 15 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 5 + } + }, + "family": "gpt", + "ownedBy": "openai", + "openWeights": false + }, + { + "id": "gpt-4o-2024-08-06", + "name": "GPT-4o (2024-08-06)", + "description": "The 2024-08-06 version of GPT-4o offers improved performance in structured outputs, with the ability to supply a JSON schema in the respone_format. Read more [here](https://openai.com/index/introducing-structured-outputs-in-the-api/).\n\nGPT-4o (\"o\" for \"omni\") is OpenAI's latest AI model, supporting both text and image inputs with text outputs. It maintains the intelligence level of [GPT-4 Turbo](/models/openai/gpt-4-turbo) while being twice as fast and 50% more cost-effective. GPT-4o also offers improved performance in processing non-English languages and enhanced visual capabilities.\n\nFor benchmarking against other models, it was briefly called [\"im-also-a-good-gpt2-chatbot\"](https://twitter.com/LiamFedus/status/1790064963966370209)", + "capabilities": ["function-call", "structured-output", "file-input", "image-recognition", "web-search"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 16384, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2.5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 10 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 1.25 + } + }, + "family": "gpt", + "ownedBy": "openai", + "openWeights": false + }, + { + "id": "gpt-5-3-codex", + "name": "GPT-5.3 Codex", + "capabilities": [ + "reasoning", + "function-call", + "structured-output", + "file-input", + "image-recognition", + "web-search" + ], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 400000, + "maxOutputTokens": 128000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.75 + }, + "output": { + "currency": "USD", + "perMillionTokens": 14 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.175 + } + }, + "family": "gpt-codex", + "ownedBy": "openai", + "openWeights": false + }, + { + "id": "o4-mini-deep-research", + "name": "o4-mini-deep-research", + "description": "o4-mini-deep-research is OpenAI's faster, more affordable deep research model—ideal for tackling complex, multi-step research tasks.\n\nNote: This model always uses the 'web_search' tool which adds additional cost.", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition", "web-search"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 100000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 8 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.5 + } + }, + "reasoning": { + "supportedEfforts": ["medium"] + }, + "family": "o-mini", + "ownedBy": "openai", + "openWeights": false + }, + { + "id": "kimi-dev", + "name": "Kimi Dev 72b (free)", + "description": "Kimi-Dev-72B is a new generation open-source programming large model that achieved a leading performance of 60.4% on SWE-bench Verified. Through large-scale reinforcement learning optimization, it can automatically fix code in real Docker environments, receiving rewards only when passing the complete test suite, thereby ensuring the correctness and robustness of solutions and aligning more closely with real software development standards.", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 131072, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.32 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.28 + } + }, + "family": "kimi", + "ownedBy": "moonshot", + "openWeights": true + }, + { + "id": "glm-z1", + "name": "GLM Z1 32B (free)", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 32768, + "maxOutputTokens": 32768, + "family": "glm-z", + "ownedBy": "zhipu", + "openWeights": true + }, + { + "id": "deephermes-3-llama-3-preview", + "name": "DeepHermes 3 Llama 3 8B Preview", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 8192, + "family": "llama", + "ownedBy": "nousresearch", + "openWeights": true + }, + { + "id": "grok-3-beta", + "name": "Grok 3 Beta", + "description": "Grok 3 is the latest model from xAI. It's their flagship model that excels at enterprise use cases like data extraction, coding, and text summarization. Possesses deep domain knowledge in finance, healthcare, law, and science.\n\nExcels in structured tasks and benchmarks like GPQA, LCB, and MMLU-Pro where it outperforms Grok 3 Mini even on high thinking. \n\nNote: That there are two xAI endpoints for this model. By default when using this model we will always route you to the base endpoint. If you want the fast endpoint you can add `provider: { sort: throughput}`, to sort by throughput instead. \n", + "capabilities": ["function-call", "web-search"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 15 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.75 + } + }, + "family": "grok", + "ownedBy": "xai", + "openWeights": false + }, + { + "id": "grok-3-mini-beta", + "name": "Grok 3 Mini Beta", + "description": "Grok 3 Mini is a lightweight, smaller thinking model. Unlike traditional models that generate answers immediately, Grok 3 Mini thinks before responding. It’s ideal for reasoning-heavy tasks that don’t demand extensive domain knowledge, and shines in math-specific and quantitative use cases, such as solving challenging puzzles or math problems.\n\nTransparent \"thinking\" traces accessible. Defaults to low reasoning, can boost with setting `reasoning: { effort: \"high\" }`\n\nNote: That there are two xAI endpoints for this model. By default when using this model we will always route you to the base endpoint. If you want the fast endpoint you can add `provider: { sort: throughput}`, to sort by throughput instead. \n", + "capabilities": ["reasoning", "function-call", "web-search"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.5 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.075 + } + }, + "reasoning": { + "supportedEfforts": ["low", "high"] + }, + "family": "grok", + "ownedBy": "xai", + "openWeights": false + }, + { + "id": "dolphin-mistral-venice-edition", + "name": "Uncensored (free)", + "description": "Venice Uncensored Dolphin Mistral 24B Venice Edition is a fine-tuned variant of Mistral-Small-24B-Instruct-2501, developed by dphn.ai in collaboration with Venice.ai. This model is designed as an “uncensored” instruct-tuned LLM, preserving user control over alignment, system prompts, and behavior. Intended for advanced and unrestricted use cases, Venice Uncensored emphasizes steerability and transparent behavior, removing default safety and alignment layers typically found in mainstream assistant models.", + "capabilities": ["structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 32768, + "maxOutputTokens": 32768, + "family": "mistral", + "ownedBy": "dolphinai", + "openWeights": true + }, + { + "id": "dolphin3-0-mistral", + "name": "Dolphin3.0 Mistral 24B", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 32768, + "maxOutputTokens": 8192, + "family": "mistral", + "ownedBy": "dolphinai", + "openWeights": true + }, + { + "id": "dolphin3-0-r1-mistral", + "name": "Dolphin3.0 R1 Mistral 24B", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 32768, + "maxOutputTokens": 8192, + "family": "mistral", + "ownedBy": "dolphinai", + "openWeights": true + }, + { + "id": "riverflow-v2-max-preview", + "name": "Riverflow V2 Max Preview", + "capabilities": ["image-recognition", "image-generation"], + "inputModalities": ["text", "image"], + "outputModalities": ["image"], + "contextWindow": 8192, + "maxOutputTokens": 8192, + "family": "sourceful", + "ownedBy": "sourceful", + "openWeights": true + }, + { + "id": "riverflow-v2-fast-preview", + "name": "Riverflow V2 Fast Preview", + "capabilities": ["image-recognition", "image-generation"], + "inputModalities": ["text", "image"], + "outputModalities": ["image"], + "contextWindow": 8192, + "maxOutputTokens": 8192, + "family": "sourceful", + "ownedBy": "sourceful", + "openWeights": true + }, + { + "id": "riverflow-v2-standard-preview", + "name": "Riverflow V2 Standard Preview", + "capabilities": ["image-recognition", "image-generation"], + "inputModalities": ["text", "image"], + "outputModalities": ["image"], + "contextWindow": 8192, + "maxOutputTokens": 8192, + "family": "sourceful", + "ownedBy": "sourceful", + "openWeights": true + }, + { + "id": "deepseek-chat-v3-1", + "name": "DeepSeek-V3.1", + "description": "DeepSeek-V3.1 is a large hybrid reasoning model (671B parameters, 37B active) that supports both thinking and non-thinking modes via prompt templates. It extends the DeepSeek-V3 base with a two-phase long-context training process, reaching up to 128K tokens, and uses FP8 microscaling for efficient inference. Users can control the reasoning behaviour with the `reasoning` `enabled` boolean. [Learn more in our docs](https://openrouter.ai/docs/use-cases/reasoning-tokens#enable-reasoning-with-default-config)\n\nThe model improves tool use, code generation, and reasoning efficiency, achieving performance comparable to DeepSeek-R1 on difficult benchmarks while responding more quickly. It supports structured tool calling, code agents, and search agents, making it suitable for research, coding, and agentic workflows. \n\nIt succeeds the [DeepSeek V3-0324](/deepseek/deepseek-chat-v3-0324) model and performs well on a variety of tasks.", + "capabilities": ["reasoning", "function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 163840, + "maxOutputTokens": 163840, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.8 + } + }, + "reasoning": { + "supportedEfforts": ["none"] + }, + "family": "deepseek", + "ownedBy": "deepseek", + "openWeights": true + }, + { + "id": "deepseek-v3-base", + "name": "DeepSeek V3 Base (free)", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 163840, + "maxOutputTokens": 163840, + "reasoning": { + "supportedEfforts": ["none"] + }, + "family": "deepseek", + "ownedBy": "deepseek", + "openWeights": true + }, + { + "id": "deepseek-chat-v3-0324", + "name": "DeepSeek V3 0324", + "description": "DeepSeek V3, a 685B-parameter, mixture-of-experts model, is the latest iteration of the flagship chat model family from the DeepSeek team.\n\nIt succeeds the [DeepSeek V3](/deepseek/deepseek-chat-v3) model and performs really well on a variety of tasks.", + "capabilities": ["structured-output", "reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 16384, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.19 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.87 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.095 + } + }, + "reasoning": { + "supportedEfforts": ["none"] + }, + "family": "deepseek", + "ownedBy": "deepseek", + "openWeights": true + }, + { + "id": "qwerky", + "name": "Qwerky 72B", + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 32768, + "maxOutputTokens": 8192, + "family": "qwerky", + "ownedBy": "openrouter", + "openWeights": true + }, + { + "id": "deepseek-r1t2-chimera", + "name": "DeepSeek R1T2 Chimera (free)", + "description": "DeepSeek-TNG-R1T2-Chimera is the second-generation Chimera model from TNG Tech. It is a 671 B-parameter mixture-of-experts text-generation model assembled from DeepSeek-AI’s R1-0528, R1, and V3-0324 checkpoints with an Assembly-of-Experts merge. The tri-parent design yields strong reasoning performance while running roughly 20 % faster than the original R1 and more than 2× faster than R1-0528 under vLLM, giving a favorable cost-to-intelligence trade-off. The checkpoint supports contexts up to 60 k tokens in standard use (tested to ~130 k) and maintains consistent token behaviour, making it suitable for long-context analysis, dialogue and other open-ended generation tasks.", + "capabilities": ["reasoning", "structured-output", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 163840, + "maxOutputTokens": 163840, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.25 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.85 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.125 + } + }, + "family": "deepseek-thinking", + "ownedBy": "deepseek", + "openWeights": true + }, + { + "id": "molmo-2", + "name": "Molmo2 8B (free)", + "description": "Molmo2-8B is an open vision-language model developed by the Allen Institute for AI (Ai2) as part of the Molmo2 family, supporting image, video, and multi-image understanding and grounding. It is based on Qwen3-8B and uses SigLIP 2 as its vision backbone, outperforming other open-weight, open-data models on short videos, counting, and captioning, while remaining competitive on long-video tasks.", + "capabilities": ["reasoning", "image-recognition", "video-recognition"], + "inputModalities": ["text", "image", "video"], + "outputModalities": ["text"], + "contextWindow": 36864, + "maxOutputTokens": 36864, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.2 + } + }, + "family": "allenai", + "ownedBy": "ai2", + "openWeights": true + }, + { + "id": "seedream-4-5", + "name": "Seedream 4.5", + "capabilities": ["image-recognition", "image-generation"], + "inputModalities": ["image", "text"], + "outputModalities": ["image"], + "contextWindow": 4096, + "maxOutputTokens": 4096, + "family": "seed", + "ownedBy": "bytedance", + "openWeights": true + }, + { + "id": "lfm-2-5-thinking", + "name": "LFM2.5-1.2B-Thinking (free)", + "description": "LFM2.5-1.2B-Thinking is a lightweight reasoning-focused model optimized for agentic tasks, data extraction, and RAG—while still running comfortably on edge devices. It supports long context (up to 32K tokens) and is designed to provide higher-quality “thinking” responses in a small 1.2B model.", + "capabilities": ["reasoning"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 32768, + "family": "liquid", + "ownedBy": "liquidai", + "openWeights": true + }, + { + "id": "lfm-2-5-instruct", + "name": "LFM2.5-1.2B-Instruct (free)", + "description": "LFM2.5-1.2B-Instruct is a compact, high-performance instruction-tuned model built for fast on-device AI. It delivers strong chat quality in a 1.2B parameter footprint, with efficient edge inference and broad runtime support.", + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 32768, + "family": "liquid", + "ownedBy": "liquidai", + "openWeights": true + }, + { + "id": "minimax-01", + "name": "MiniMax-01", + "description": "MiniMax-01 is a combines MiniMax-Text-01 for text generation and MiniMax-VL-01 for image understanding. It has 456 billion parameters, with 45.9 billion parameters activated per inference, and can handle a context of up to 4 million tokens.\n\nThe text model adopts a hybrid architecture that combines Lightning Attention, Softmax Attention, and Mixture-of-Experts (MoE). The image model adopts the “ViT-MLP-LLM” framework and is trained on top of the text model.\n\nTo read more about the release, see: https://www.minimaxi.com/en/news/minimax-01-series-2", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 1000000, + "maxOutputTokens": 1000000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.1 + } + }, + "family": "minimax", + "ownedBy": "minimax", + "openWeights": true + }, + { + "id": "gemini-2-0-flash-exp", + "name": "Gemini 2.0 Flash Experimental (free)", + "description": "https://doc.aihubmix.com/en/api/Gemini%20%E5%9B%BE%E7%89%87%E7%94%9F%E6%88%90%E5%92%8C%E7%BC%96%E8%BE%91\nInstructions:\n\nNeed to add parameters to experience new features: \"modalities\":[\"text\",\"image\"]\nImages are passed and output in Base64 encoding\nAs an experimental model, it's recommended to explicitly specify \"output image\", otherwise it might only output text\nDefault height for output images is 1024px\nPython calls require the latest OpenAI SDK, run pip install -U openai first", + "capabilities": ["function-call", "file-input", "image-recognition", "web-search"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 1048576, + "maxOutputTokens": 1048576, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.02 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.08 + } + }, + "family": "gemini-flash", + "ownedBy": "google", + "openWeights": false + }, + { + "id": "gpt-5-image", + "name": "GPT-5 Image", + "description": "[GPT-5](https://openrouter.ai/openai/gpt-5) Image combines OpenAI's GPT-5 model with state-of-the-art image generation capabilities. It offers major improvements in reasoning, code quality, and user experience while incorporating GPT Image 1's superior instruction following, text rendering, and detailed image editing.", + "capabilities": [ + "reasoning", + "function-call", + "structured-output", + "file-input", + "image-recognition", + "image-generation", + "web-search" + ], + "inputModalities": ["text", "image"], + "outputModalities": ["text", "image"], + "contextWindow": 400000, + "maxOutputTokens": 128000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 10 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 1.25 + } + }, + "reasoning": { + "supportedEfforts": ["minimal", "low", "medium", "high"] + }, + "family": "gpt", + "ownedBy": "openai", + "openWeights": false + }, + { + "id": "sherlock-think-alpha", + "name": "Sherlock Think Alpha", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 1840000, + "family": "sherlock", + "ownedBy": "openrouter", + "openWeights": false + }, + { + "id": "sherlock-dash-alpha", + "name": "Sherlock Dash Alpha", + "capabilities": ["function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 1840000, + "family": "sherlock", + "ownedBy": "openrouter", + "openWeights": false + }, + { + "id": "aurora-alpha", + "name": "Aurora Alpha", + "capabilities": ["reasoning", "function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 50000, + "ownedBy": "openrouter", + "openWeights": false + }, + { + "id": "qwen-2-5-coder-instruct", + "name": "Qwen2.5 Coder 32B Instruct", + "description": "Qwen2.5-Coder is the latest series of Code-Specific Qwen large language models (formerly known as CodeQwen). Qwen2.5-Coder brings the following improvements upon CodeQwen1.5:\n\n- Significantly improvements in **code generation**, **code reasoning** and **code fixing**. \n- A more comprehensive foundation for real-world applications such as **Code Agents**. Not only enhancing coding capabilities but also maintaining its strengths in mathematics and general competencies.\n\nTo read more about its evaluation results, check out [Qwen 2.5 Coder's blog](https://qwenlm.github.io/blog/qwen2.5-coder-family/).", + "capabilities": ["structured-output", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 32768, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.03 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.11 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.015 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": true + }, + { + "id": "qwen3-5-plus-02-15", + "name": "Qwen3.5 Plus 2026-02-15", + "description": "The Qwen3.5 native vision-language series Plus models are built on a hybrid architecture that integrates linear attention mechanisms with sparse mixture-of-experts models, achieving higher inference efficiency. In a variety of task evaluations, the 3.5 series consistently demonstrates performance on par with state-of-the-art leading models. Compared to the 3 series, these models show a leap forward in both pure-text and multimodal capabilities.", + "capabilities": [ + "reasoning", + "function-call", + "structured-output", + "file-input", + "image-recognition", + "video-recognition" + ], + "inputModalities": ["text", "image", "video"], + "outputModalities": ["text"], + "contextWindow": 1000000, + "maxOutputTokens": 65536, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.4 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2.4 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 38912 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": false + }, + { + "id": "qwen-2-5-vl-instruct", + "name": "Qwen2.5-VL 7B Instruct (free)", + "description": "Qwen2.5 VL 7B is a multimodal LLM from the Qwen Team with the following key enhancements:\n\n- SoTA understanding of images of various resolution & ratio: Qwen2.5-VL achieves state-of-the-art performance on visual understanding benchmarks, including MathVista, DocVQA, RealWorldQA, MTVQA, etc.\n\n- Understanding videos of 20min+: Qwen2.5-VL can understand videos over 20 minutes for high-quality video-based question answering, dialog, content creation, etc.\n\n- Agent that can operate your mobiles, robots, etc.: with the abilities of complex reasoning and decision making, Qwen2.5-VL can be integrated with devices like mobile phones, robots, etc., for automatic operation based on visual environment and text instructions.\n\n- Multilingual Support: to serve global users, besides English and Chinese, Qwen2.5-VL now supports the understanding of texts in different languages inside images, including most European languages, Japanese, Korean, Arabic, Vietnamese, etc.\n\nFor more details, see this [blog post](https://qwenlm.github.io/blog/qwen2-vl/) and [GitHub repo](https://github.com/QwenLM/Qwen2-VL).\n\nUsage of this model is subject to [Tongyi Qianwen LICENSE AGREEMENT](https://huggingface.co/Qwen/Qwen1.5-110B-Chat/blob/main/LICENSE).", + "capabilities": ["function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 32768, + "maxOutputTokens": 32768, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.2 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": true + }, + { + "id": "qwen3-a22b-07-25", + "name": "Qwen3 235B A22B Instruct 2507 (free)", + "capabilities": ["function-call", "reasoning"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 262144, + "maxOutputTokens": 131072, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.15 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.85 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 38912 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": true + }, + { + "id": "codestral-2508", + "name": "Codestral 2508", + "description": "Mistral's cutting-edge language model for coding released end of July 2025. Codestral specializes in low-latency, high-frequency tasks such as fill-in-the-middle (FIM), code correction and test generation.\n\n[Blog Post](https://mistral.ai/news/codestral-25-08)", + "capabilities": ["function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 256000, + "maxOutputTokens": 256000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.9 + } + }, + "family": "codestral", + "ownedBy": "mistral", + "openWeights": true + }, + { + "id": "mistral-instruct", + "name": "Mistral 7B Instruct (free)", + "description": "A high-performing, industry-standard 7.3B parameter model, with optimizations for speed and context length.\n\n*Mistral 7B Instruct has multiple version variants, and this is intended to be the latest version.*", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 32768, + "maxOutputTokens": 32768, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.2 + } + }, + "family": "mistral", + "ownedBy": "mistral", + "openWeights": true + }, + { + "id": "mistral-small-3-2-instruct", + "name": "Mistral Small 3.2 24B Instruct", + "description": "Mistral-Small-3.2-24B-Instruct-2506 is an updated 24B parameter model from Mistral optimized for instruction following, repetition reduction, and improved function calling. Compared to the 3.1 release, version 3.2 significantly improves accuracy on WildBench and Arena Hard, reduces infinite generations, and delivers gains in tool use and structured output tasks.\n\nIt supports image and text inputs with structured outputs, function/tool calling, and strong performance across coding (HumanEval+, MBPP), STEM (MMLU, MATH, GPQA), and vision benchmarks (ChartQA, DocVQA).", + "capabilities": ["function-call", "structured-output", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 96000, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.06 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.18 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.03 + } + }, + "family": "mistral-small", + "ownedBy": "mistral", + "openWeights": true + }, + { + "id": "mistral-medium-3", + "name": "Mistral Medium 3", + "description": "Mistral Medium 3 is a high-performance enterprise-grade language model designed to deliver frontier-level capabilities at significantly reduced operational cost. It balances state-of-the-art reasoning and multimodal performance with 8× lower cost compared to traditional large models, making it suitable for scalable deployments across professional and industrial use cases.\n\nThe model excels in domains such as coding, STEM reasoning, and enterprise adaptation. It supports hybrid, on-prem, and in-VPC deployments and is optimized for integration into custom workflows. Mistral Medium 3 offers competitive accuracy relative to larger models like Claude Sonnet 3.5/3.7, Llama 4 Maverick, and Command R+, while maintaining broad compatibility across cloud environments.", + "capabilities": ["function-call", "structured-output", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 131072, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.4 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2 + } + }, + "family": "mistral-medium", + "ownedBy": "mistral", + "openWeights": false + }, + { + "id": "mistral-medium-3-1", + "name": "Mistral Medium 3.1", + "description": "Mistral Medium 3.1 is an updated version of Mistral Medium 3, which is a high-performance enterprise-grade language model designed to deliver frontier-level capabilities at significantly reduced operational cost. It balances state-of-the-art reasoning and multimodal performance with 8× lower cost compared to traditional large models, making it suitable for scalable deployments across professional and industrial use cases.\n\nThe model excels in domains such as coding, STEM reasoning, and enterprise adaptation. It supports hybrid, on-prem, and in-VPC deployments and is optimized for integration into custom workflows. Mistral Medium 3.1 offers competitive accuracy relative to larger models like Claude Sonnet 3.5/3.7, Llama 4 Maverick, and Command R+, while maintaining broad compatibility across cloud environments.", + "capabilities": ["function-call", "structured-output", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 262144, + "maxOutputTokens": 262144, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.4 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2 + } + }, + "family": "mistral-medium", + "ownedBy": "mistral", + "openWeights": false + }, + { + "id": "reka-flash-3", + "name": "Reka Flash 3", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 32768, + "maxOutputTokens": 8192, + "family": "reka", + "ownedBy": "openrouter", + "openWeights": true + }, + { + "id": "sarvam-m", + "name": "Sarvam-M (free)", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 32768, + "maxOutputTokens": 32768, + "family": "sarvam", + "ownedBy": "openrouter", + "openWeights": true + }, + { + "id": "flux.2-klein", + "name": "FLUX.2 Klein 4B", + "capabilities": ["image-recognition", "image-generation"], + "inputModalities": ["image", "text"], + "outputModalities": ["image"], + "contextWindow": 40960, + "maxOutputTokens": 40960, + "family": "flux", + "ownedBy": "bfl", + "openWeights": true + }, + { + "id": "flux.2-max", + "name": "FLUX.2 Max", + "capabilities": ["image-recognition", "image-generation"], + "inputModalities": ["image", "text"], + "outputModalities": ["image"], + "contextWindow": 46864, + "maxOutputTokens": 46864, + "family": "flux", + "ownedBy": "bfl", + "openWeights": false + }, + { + "id": "flux.2-pro", + "name": "FLUX.2 Pro", + "capabilities": ["image-recognition", "image-generation"], + "inputModalities": ["image", "text"], + "outputModalities": ["image"], + "contextWindow": 46864, + "maxOutputTokens": 46864, + "family": "flux", + "ownedBy": "bfl", + "openWeights": false + }, + { + "id": "flux.2-flex", + "name": "FLUX.2 Flex", + "capabilities": ["image-recognition", "image-generation"], + "inputModalities": ["image", "text"], + "outputModalities": ["image"], + "contextWindow": 67344, + "maxOutputTokens": 67344, + "family": "flux", + "ownedBy": "bfl", + "openWeights": false + }, + { + "id": "step-3", + "name": "Step-3", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition"], + "inputModalities": ["image", "text"], + "outputModalities": ["text"], + "contextWindow": 65536, + "maxOutputTokens": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.21 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.57 + } + }, + "ownedBy": "stepfun", + "openWeights": false + }, + { + "id": "minimax-m2-5-lightning", + "name": "MiniMax: MiniMax M2.5 highspeed", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 204800, + "maxOutputTokens": 131072, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.6 + }, + "output": { + "currency": "USD", + "perMillionTokens": 4.8 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.06 + } + }, + "reasoning": { + "supportedEfforts": [], + "interleaved": true + }, + "ownedBy": "minimax", + "openWeights": false + }, + { + "id": "doubao-seed-code", + "name": "Doubao-Seed-Code", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 256000, + "maxOutputTokens": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.17 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.12 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.03 + } + }, + "reasoning": { + "supportedEfforts": ["none", "high"] + }, + "ownedBy": "bytedance", + "openWeights": false + }, + { + "id": "doubao-seed-2-0-mini", + "name": "Doubao-Seed-2.0-mini", + "description": "Doubao 2.0 series is designed for low-latency, high-concurrency, and cost-sensitive scenarios, emphasizing fast responses and flexible inference deployment. Model performance is comparable to Doubao-Seed-1.6. It supports a 256k context window, four levels of thinking length, and multimodal understanding, making it suitable for lightweight tasks that prioritize cost and speed.", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition", "video-recognition"], + "inputModalities": ["text", "image", "video"], + "outputModalities": ["text"], + "contextWindow": 256000, + "maxOutputTokens": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.03 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.28 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.01 + } + }, + "reasoning": { + "supportedEfforts": ["minimal", "low", "medium", "high"] + }, + "ownedBy": "bytedance", + "openWeights": false + }, + { + "id": "doubao-seed-2-0-pro", + "name": "Doubao-Seed-2.0-pro", + "description": "Doubao flagship all-purpose general model, targeting complex reasoning and long-chain task execution scenarios in the Agent era. It emphasizes multimodal understanding, long-context reasoning, structured generation, and tool-augmented execution. It excels at handling complex instructions and multi-constraint execution, reliably addressing multi-step complex planning, intricate image-text reasoning, video content understanding, and high-difficulty analysis scenarios.", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition", "video-recognition"], + "inputModalities": ["text", "image", "video"], + "outputModalities": ["text"], + "contextWindow": 256000, + "maxOutputTokens": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.45 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2.24 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.09 + } + }, + "reasoning": { + "supportedEfforts": ["minimal", "low", "medium", "high"] + }, + "ownedBy": "bytedance", + "openWeights": false + }, + { + "id": "doubao-seed-1-8", + "name": "Doubao-Seed-1.8", + "description": "Doubao's strongest multimodal Agent model Seed1.8 has powerful multimodal capabilities, supports image and text input, and can efficiently and accurately complete tasks in scenarios such as information retrieval, code generation, GUI interaction, and complex workflows, meeting increasingly diverse technical demands.", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition", "video-recognition"], + "inputModalities": ["text", "image", "video"], + "outputModalities": ["text"], + "contextWindow": 256000, + "maxOutputTokens": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.11 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.28 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.02 + } + }, + "reasoning": { + "supportedEfforts": ["minimal", "low", "medium", "high"] + }, + "ownedBy": "bytedance", + "openWeights": false + }, + { + "id": "doubao-seed-2-0-lite", + "name": "Doubao-Seed-2.0-lite", + "description": "The Doubao 2.0 series is a balanced family of models targeted at high-frequency enterprise scenarios, balancing performance and cost, with overall capabilities surpassing the previous generation Doubao-Seed-1.8. They are suited for production tasks such as unstructured information processing, content creation, search and recommendation, and data analysis. The models support long-context processing, multi-source information fusion, multi-step instruction execution, and high-fidelity structured output. While ensuring stable performance, they significantly optimize costs.", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition", "video-recognition"], + "inputModalities": ["text", "image", "video"], + "outputModalities": ["text"], + "contextWindow": 256000, + "maxOutputTokens": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.09 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.51 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.02 + } + }, + "reasoning": { + "supportedEfforts": ["minimal", "low", "medium", "high"] + }, + "ownedBy": "bytedance", + "openWeights": false + }, + { + "id": "ernie-5-0-thinking-preview", + "name": "ERNIE 5.0", + "description": "The new generation Wenxin model, Wenxin 5.0, is a native full-modal large model that adopts native full-modal unified modeling technology, jointly modeling text, images, audio, and video, possessing comprehensive full-modal capabilities. Wenxin 5.0's basic abilities are comprehensively upgraded, performing excellently on benchmark test sets, especially in multimodal understanding, instruction compliance, creative writing, factual accuracy, intelligent agent planning, and tool application.", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition", "video-recognition"], + "inputModalities": ["text", "image", "video"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.84 + }, + "output": { + "currency": "USD", + "perMillionTokens": 3.37 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.822 + } + }, + "ownedBy": "baidu", + "openWeights": false + }, + { + "id": "mixtral-8x7b-instruct-v0-1", + "name": "Mixtral-8x7B-Instruct-v0.1", + "capabilities": ["structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 32768, + "maxOutputTokens": 32768, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.7 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.7 + } + }, + "ownedBy": "mistral", + "openWeights": true + }, + { + "id": "mistral-instruct-v0-3", + "name": "Mistral-7B-Instruct-v0.3", + "description": "A high-performing, industry-standard 7.3B parameter model, with optimizations for speed and context length.\n\nAn improved version of [Mistral 7B Instruct v0.2](/models/mistralai/mistral-7b-instruct-v0.2), with the following changes:\n\n- Extended vocabulary to 32768\n- Supports v3 Tokenizer\n- Supports function calling\n\nNOTE: Support for function calling depends on the provider.", + "capabilities": ["function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 65536, + "maxOutputTokens": 65536, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.11 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.11 + } + }, + "ownedBy": "mistral", + "openWeights": true + }, + { + "id": "llama-3_3-instruct", + "name": "Meta-Llama-3_3-70B-Instruct", + "capabilities": ["function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 131072, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.74 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.74 + } + }, + "ownedBy": "meta", + "openWeights": true + }, + { + "id": "v0-1-5-lg", + "name": "v0-1.5-lg", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 512000, + "maxOutputTokens": 32000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 15 + }, + "output": { + "currency": "USD", + "perMillionTokens": 75 + } + }, + "family": "v0", + "ownedBy": "v0", + "openWeights": false + }, + { + "id": "qwen3-a22b-instruct", + "name": "Qwen3-235B-A22B-Instruct", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 256000, + "maxOutputTokens": 64000, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": true + }, + { + "id": "kimi-k2-5-nvfp4", + "name": "Kimi K2.5 (NVFP4)", + "capabilities": ["reasoning", "function-call", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 262144, + "maxOutputTokens": 65536, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.55 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2.19 + } + }, + "reasoning": { + "supportedEfforts": ["none", "auto"] + }, + "family": "kimi", + "ownedBy": "moonshot", + "openWeights": true + }, + { + "id": "qwen3-coder-a35b-instruct-turbo", + "name": "Qwen3 Coder 480B A35B Instruct Turbo", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 262144, + "maxOutputTokens": 66536, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.2 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": true + }, + { + "id": "claude-4-opus", + "name": "Claude Opus 4", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 32000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 16.5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 82.5 + } + }, + "family": "claude-opus", + "ownedBy": "anthropic", + "openWeights": false + }, + { + "id": "claude-3-7-sonnet-latest", + "name": "Claude Sonnet 3.7 (Latest)", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition", "web-search", "computer-use"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 3.3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 16.5 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.33 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 64000 + } + }, + "family": "claude-sonnet", + "ownedBy": "anthropic", + "openWeights": false + }, + { + "id": "palmyra-x5", + "name": "Writer: Palmyra X5", + "description": "Palmyra X5 is Writer's most advanced model, purpose-built for building and scaling AI agents across the enterprise. It delivers industry-leading speed and efficiency on context windows up to 1 million tokens, powered by a novel transformer architecture and hybrid attention mechanisms. This enables faster inference and expanded memory for processing large volumes of enterprise data, critical for scaling AI agents.", + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 1040000, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.6 + }, + "output": { + "currency": "USD", + "perMillionTokens": 6 + } + }, + "ownedBy": "google", + "openWeights": false + }, + { + "id": "gemini-2-5-pro-preview", + "name": "Google: Gemini 2.5 Pro Preview 06-05", + "description": "Gemini 2.5 Pro is Google’s state-of-the-art AI model designed for advanced reasoning, coding, mathematics, and scientific tasks. It employs “thinking” capabilities, enabling it to reason through responses with enhanced accuracy and nuanced context handling. Gemini 2.5 Pro achieves top-tier performance on multiple benchmarks, including first-place positioning on the LMArena leaderboard, reflecting superior human-preference alignment and complex problem-solving abilities.\n", + "capabilities": [ + "reasoning", + "function-call", + "file-input", + "image-recognition", + "audio-recognition", + "web-search" + ], + "inputModalities": ["image", "text", "audio"], + "outputModalities": ["text"], + "contextWindow": 1048576, + "maxOutputTokens": 65536, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.25 + }, + "output": { + "currency": "USD", + "perMillionTokens": 10 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.125 + } + }, + "reasoning": { + "supportedEfforts": ["low", "medium", "high"], + "thinkingTokenLimits": { + "min": 128, + "max": 32768 + } + }, + "ownedBy": "google", + "openWeights": false + }, + { + "id": "gemini-2-0-flash-lite-001", + "name": "Google: Gemini 2.0 Flash Lite", + "description": "Gemini 2.0 Flash Lite offers a significantly faster time to first token (TTFT) compared to [Gemini Flash 1.5](/google/gemini-flash-1.5), while maintaining quality on par with larger models like [Gemini Pro 1.5](/google/gemini-pro-1.5), all at extremely economical token prices.", + "capabilities": [ + "function-call", + "file-input", + "image-recognition", + "audio-recognition", + "video-recognition", + "web-search" + ], + "inputModalities": ["text", "image", "audio", "video"], + "outputModalities": ["text"], + "contextWindow": 1048576, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.075 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.3 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.076 + } + }, + "ownedBy": "google", + "openWeights": false + }, + { + "id": "auto", + "name": "Kilo: Auto", + "description": "Your prompt will be processed by a meta-model and routed to one of dozens of models (see below), optimizing for the best possible output.\n\nTo see which model was used, visit [Activity](/activity), or read the `model` attribute of the response. Your response will be priced at the same rate as the routed model.\n\nLearn more, including how to customize the models for routing, in our [docs](/docs/guides/routing/routers/auto-router).\n\nRequests will be routed to the following models:\n- [anthropic/claude-haiku-4.5](/anthropic/claude-haiku-4.5)\n- [anthropic/claude-opus-4.6](/anthropic/claude-opus-4.6)\n- [anthropic/claude-sonnet-4.5](/anthropic/claude-sonnet-4.5)\n- [deepseek/deepseek-r1](/deepseek/deepseek-r1)\n- [google/gemini-2.5-flash-lite](/google/gemini-2.5-flash-lite)\n- [google/gemini-3-flash-preview](/google/gemini-3-flash-preview)\n- [google/gemini-3-pro-preview](/google/gemini-3-pro-preview)\n- [meta-llama/llama-3.3-70b-instruct](/meta-llama/llama-3.3-70b-instruct)\n- [mistralai/codestral-2508](/mistralai/codestral-2508)\n- [mistralai/mistral-large](/mistralai/mistral-large)\n- [mistralai/mistral-medium-3.1](/mistralai/mistral-medium-3.1)\n- [mistralai/mistral-small-3.2-24b-instruct-2506](/mistralai/mistral-small-3.2-24b-instruct-2506)\n- [moonshotai/kimi-k2-thinking](/moonshotai/kimi-k2-thinking)\n- [moonshotai/kimi-k2.5](/moonshotai/kimi-k2.5)\n- [openai/gpt-5](/openai/gpt-5)\n- [openai/gpt-5-mini](/openai/gpt-5-mini)\n- [openai/gpt-5-nano](/openai/gpt-5-nano)\n- [openai/gpt-5.1](/openai/gpt-5.1)\n- [openai/gpt-5.2](/openai/gpt-5.2)\n- [openai/gpt-5.2-pro](/openai/gpt-5.2-pro)\n- [openai/gpt-oss-120b](/openai/gpt-oss-120b)\n- [perplexity/sonar](/perplexity/sonar)\n- [qwen/qwen3-235b-a22b](/qwen/qwen3-235b-a22b)\n- [x-ai/grok-3](/x-ai/grok-3)\n- [x-ai/grok-3-mini](/x-ai/grok-3-mini)\n- [x-ai/grok-4](/x-ai/grok-4)", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1 + } + }, + "family": "auto", + "ownedBy": "kilo", + "openWeights": false + }, + { + "id": "qwen3-a22b-2507", + "name": "Qwen: Qwen3 235B A22B Instruct 2507", + "description": "Qwen3-235B-A22B-Instruct-2507 is a multilingual, instruction-tuned mixture-of-experts language model based on the Qwen3-235B architecture, with 22B active parameters per forward pass. It is optimized for general-purpose text generation, including instruction following, logical reasoning, math, code, and tool usage. The model supports a native 262K context length and does not implement \"thinking mode\" ( blocks).\n\nCompared to its base variant, this version delivers significant gains in knowledge coverage, long-context reasoning, coding benchmarks, and alignment with open-ended tasks. It is particularly strong on multilingual understanding, math reasoning (e.g., AIME, HMMT), and alignment evaluations like Arena-Hard and WritingBench.", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 262144, + "maxOutputTokens": 52429, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.071 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.1 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 38912 + } + }, + "ownedBy": "alibaba", + "openWeights": true + }, + { + "id": "devstral", + "name": "Mistral: Devstral Medium", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 26215, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.4 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2 + } + }, + "ownedBy": "mistral", + "openWeights": true + }, + { + "id": "mistral-large", + "name": "Mistral Large", + "description": "This is Mistral AI's flagship model, Mistral Large 2 (version `mistral-large-2407`). It's a proprietary weights-available model and excels at reasoning, code, JSON, chat, and more. Read the launch announcement [here](https://mistral.ai/news/mistral-large-2407/).\n\nIt supports dozens of languages including French, German, Spanish, Italian, Portuguese, Arabic, Hindi, Russian, Chinese, Japanese, and Korean, along with 80+ coding languages including Python, Java, C, C++, JavaScript, and Bash. Its long context window allows precise information recall from large documents.", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 25600, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 6 + } + }, + "ownedBy": "mistral", + "openWeights": true + }, + { + "id": "llama-3_3-nemotron-super-v1_5", + "name": "Llama 3 3 Nemotron Super 49B V1 5", + "capabilities": ["function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2 + } + }, + "family": "llama", + "ownedBy": "meta", + "openWeights": true + }, + { + "id": "minimax-m2-5-official", + "name": "MiniMax M2.5", + "capabilities": ["reasoning", "function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 204800, + "maxOutputTokens": 131072, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.2 + } + }, + "reasoning": { + "supportedEfforts": [], + "interleaved": true + }, + "family": "minimax", + "ownedBy": "minimax", + "openWeights": false + }, + { + "id": "glm-5-original", + "name": "GLM 5 Original Thinking", + "capabilities": ["reasoning", "function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 128000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.8 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2.56 + } + }, + "reasoning": { + "supportedEfforts": ["none", "auto"] + }, + "family": "glm", + "ownedBy": "zhipu", + "openWeights": true + }, + { + "id": "multilingual-e5-large", + "name": "Multilingual-E5-large", + "description": "The multilingual-e5-large embedding model encodes sentences, paragraphs, and documents across over 90 languages into a 1024-dimensional dense vector space, delivering robust semantic embeddings optimized for multilingual retrieval, cross-language similarity, and large-scale data search.", + "capabilities": ["embedding"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 512, + "maxOutputTokens": 1024, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.02 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "family": "text-embedding", + "ownedBy": "berget", + "openWeights": true + }, + { + "id": "bge-reranker-v2-m3", + "name": "bge-reranker-v2-m3", + "description": "BAAI/bge-reranker-v2-m3 is a lightweight multilingual reranking model. It is developed based on the bge-m3 model, offering strong multilingual capabilities, easy deployment, and fast inference. The model takes a query and documents as input and directly outputs similarity scores instead of embedding vectors. It is suitable for multilingual scenarios and performs particularly well in both Chinese and English processing.", + "capabilities": ["embedding", "rerank"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 512, + "maxOutputTokens": 512, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.1 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.1 + } + }, + "family": "bge", + "ownedBy": "baai", + "openWeights": true + }, + { + "id": "mistral-nemo-instruct-2407-fp8", + "name": "Mistral Nemo", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.49 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.71 + } + }, + "family": "mistral", + "ownedBy": "mistral", + "openWeights": true + }, + { + "id": "llama-3-3-instruct-fp8-dynamic", + "name": "Llama 3.3 70B", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.49 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.71 + } + }, + "family": "llama", + "ownedBy": "meta", + "openWeights": true + }, + { + "id": "qwen3-vl-embedding", + "name": "Qwen3-VL Embedding 8B", + "capabilities": ["file-input", "image-recognition", "function-call", "embedding", "reasoning"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 32000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.09 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.09 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 38912 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": true + }, + { + "id": "qwen3-vl-a22b-instruct-fp8", + "name": "Qwen3-VL 235B", + "capabilities": ["function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 218000, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.64 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.91 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": true + }, + { + "id": "mistral-nemo-instruct", + "name": "Mistral Nemo 12B Instruct", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 16000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.038 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.1 + } + }, + "family": "mistral-nemo", + "ownedBy": "mistral", + "openWeights": true + }, + { + "id": "osmosis-structure", + "name": "Osmosis Structure 0.6B", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 4000, + "maxOutputTokens": 2048, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.1 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.5 + } + }, + "family": "osmosis", + "ownedBy": "inference", + "openWeights": true + }, + { + "id": "qwen-2-5-vision-instruct", + "name": "Qwen 2.5 7B Vision Instruct", + "capabilities": ["function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 125000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.2 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": true + }, + { + "id": "gemini-3-0-flash-preview", + "name": "Gemini 3.0 Flash Preview", + "capabilities": [ + "reasoning", + "function-call", + "file-input", + "image-recognition", + "audio-recognition", + "video-recognition" + ], + "inputModalities": ["text", "image", "audio", "video"], + "outputModalities": ["text"], + "contextWindow": 1000000, + "maxOutputTokens": 64000, + "ownedBy": "google", + "openWeights": false + }, + { + "id": "qwen-max-2025-01-25", + "name": "Qwen2.5-Max-2025-01-25", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4096, + "ownedBy": "alibaba", + "openWeights": false + }, + { + "id": "claude-4-1-opus", + "name": "Claude 4.1 Opus", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 32000, + "ownedBy": "anthropic", + "openWeights": false + }, + { + "id": "doubao-1-5-thinking-pro", + "name": "Doubao 1.5 Thinking Pro", + "description": "Doubao-1.5 is a brand-new deep thinking model that excels in specialized fields such as mathematics, programming, scientific reasoning, and general tasks like creative writing. It achieves or approaches the top-tier industry level on multiple authoritative benchmarks including AIME 2024, Codeforces, and GPQA. It supports a 128k context window and 16k output.", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 16000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.62 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2.48 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.62 + } + }, + "reasoning": { + "supportedEfforts": ["none", "high"] + }, + "ownedBy": "bytedance", + "openWeights": false + }, + { + "id": "kling-v2-6", + "name": "Kling-V2 6", + "capabilities": ["file-input", "image-recognition", "video-recognition", "video-generation"], + "inputModalities": ["text", "image", "video"], + "outputModalities": ["video"], + "contextWindow": 99999999, + "maxOutputTokens": 99999999, + "ownedBy": "kling", + "openWeights": false + }, + { + "id": "gemini-3-0-pro-preview", + "name": "Gemini 3.0 Pro Preview", + "capabilities": [ + "reasoning", + "function-call", + "file-input", + "image-recognition", + "audio-recognition", + "video-recognition" + ], + "inputModalities": ["text", "image", "video", "audio"], + "outputModalities": ["text"], + "contextWindow": 1000000, + "maxOutputTokens": 64000, + "ownedBy": "google", + "openWeights": false + }, + { + "id": "doubao-seed-1-6-flash", + "name": "Doubao-Seed 1.6 Flash", + "description": "Doubao-Seed-1.6-flash is an extremely fast multimodal deep thinking model, with TPOT requiring only 10ms. It supports both text and visual understanding, with its text comprehension skills surpassing the previous generation lite model and its visual understanding on par with competitor's pro series models. It supports a 256k context window and an output length of up to 16k tokens.", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition", "video-recognition"], + "inputModalities": ["text", "image", "video"], + "outputModalities": ["text"], + "contextWindow": 256000, + "maxOutputTokens": 32000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.044 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.44 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.0088 + } + }, + "ownedBy": "bytedance", + "openWeights": false + }, + { + "id": "claude-4-0-opus", + "name": "Claude 4.0 Opus", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 32000, + "ownedBy": "anthropic", + "openWeights": false + }, + { + "id": "doubao-seed-1-6", + "name": "Doubao-Seed 1.6", + "description": "The Doubao-Seed-1.6-thinking model has significantly enhanced reasoning capabilities. Compared with Doubao-1.5-thinking-pro, it has further improvements in fundamental abilities such as coding, mathematics, and logical reasoning, and now also supports visual understanding. It supports a 256k context window, with output length supporting up to 16k tokens.", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition", "video-recognition"], + "inputModalities": ["text", "image", "video"], + "outputModalities": ["text"], + "contextWindow": 256000, + "maxOutputTokens": 32000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.18 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.8 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.036 + } + }, + "reasoning": { + "supportedEfforts": ["none", "high"] + }, + "ownedBy": "bytedance", + "openWeights": false + }, + { + "id": "doubao-1-5-pro-32k", + "name": "Doubao 1.5 Pro 32k", + "description": "Doubao-1.5-pro, a brand-new generation of flagship model, features comprehensive performance upgrades and excels in knowledge, coding, reasoning, and other aspects. It supports a 32k context window and an output length of up to 12k tokens.", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 12000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.134 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.335 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.0268 + } + }, + "ownedBy": "bytedance", + "openWeights": false + }, + { + "id": "gemini-3-0-pro-image-preview", + "name": "Gemini 3.0 Pro Image Preview", + "capabilities": ["file-input", "image-recognition", "image-generation", "function-call"], + "inputModalities": ["text", "image"], + "outputModalities": ["text", "image"], + "contextWindow": 32768, + "maxOutputTokens": 8192, + "ownedBy": "google", + "openWeights": false + }, + { + "id": "claude-4-0-sonnet", + "name": "Claude 4.0 Sonnet", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 64000, + "ownedBy": "anthropic", + "openWeights": false + }, + { + "id": "doubao-1-5-vision-pro", + "name": "Doubao 1.5 Vision Pro", + "capabilities": ["file-input", "image-recognition", "video-recognition"], + "inputModalities": ["text", "image", "video"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 16000, + "ownedBy": "bytedance", + "openWeights": false + }, + { + "id": "qwen-vl-max-2025-01-25", + "name": "Qwen VL-MAX-2025-01-25", + "capabilities": ["function-call", "file-input", "image-recognition", "audio-recognition", "video-recognition"], + "inputModalities": ["text", "image", "audio", "video"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4096, + "ownedBy": "alibaba", + "openWeights": false + }, + { + "id": "deepseek-math-v2", + "name": "Deepseek/Deepseek-Math-V2", + "description": "The mathematical reasoning of large language models has shifted from pursuing correct answers to ensuring rigorous processes. Research proposes a new paradigm of \"self-verification,\" training specialized verifiers to evaluate proof steps and using this to train generators for self-error correction. The two co-evolve, pushing the boundaries of capability. Ultimately, the model achieves gold medal level in top competitions like the IMO, demonstrating the great potential of deep reasoning.", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 160000, + "maxOutputTokens": 160000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.492 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.968 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.0984 + } + }, + "ownedBy": "deepseek", + "openWeights": false + }, + { + "id": "deepseek-v3-2-251201", + "name": "Deepseek/DeepSeek-V3.2", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 32000, + "reasoning": { + "supportedEfforts": ["none"] + }, + "ownedBy": "deepseek", + "openWeights": false + }, + { + "id": "gelab-zero-preview", + "name": "Stepfun-Ai/Gelab Zero 4b Preview", + "capabilities": ["function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 8192, + "maxOutputTokens": 4096, + "ownedBy": "qiniu-ai", + "openWeights": false + }, + { + "id": "autoglm-phone", + "name": "Z-Ai/Autoglm Phone 9b", + "capabilities": ["function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 12800, + "maxOutputTokens": 4096, + "ownedBy": "qiniu-ai", + "openWeights": false + }, + { + "id": "qwen3-a3b-2507", + "name": "Qwen3 30B A3B 2507", + "capabilities": ["function-call", "reasoning"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 262144, + "maxOutputTokens": 16384, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 38912 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": true + }, + { + "id": "k-exaone-a23b", + "name": "K EXAONE 236B A23B", + "capabilities": ["reasoning", "function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 262144, + "maxOutputTokens": 262144, + "family": "exaone", + "ownedBy": "friendli", + "openWeights": true + }, + { + "id": "exaone-4-0-1", + "name": "EXAONE 4.0.1 32B", + "capabilities": ["reasoning", "function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 131072, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.6 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1 + } + }, + "family": "exaone", + "ownedBy": "friendli", + "openWeights": true + }, + { + "id": "anthropic--claude-4-5-opus", + "name": "anthropic--claude-4.5-opus", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 25 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.5 + } + }, + "family": "claude-opus", + "ownedBy": "sap-ai-core", + "openWeights": false + }, + { + "id": "anthropic--claude-3-5-sonnet", + "name": "anthropic--claude-3.5-sonnet", + "capabilities": ["function-call", "file-input", "image-recognition", "web-search", "computer-use"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 15 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.3 + } + }, + "family": "claude-sonnet", + "ownedBy": "sap-ai-core", + "openWeights": false + }, + { + "id": "anthropic--claude-4-5-haiku", + "name": "anthropic--claude-4.5-haiku", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1 + }, + "output": { + "currency": "USD", + "perMillionTokens": 5 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.1 + } + }, + "family": "claude-haiku", + "ownedBy": "sap-ai-core", + "openWeights": false + }, + { + "id": "anthropic--claude-4-opus", + "name": "anthropic--claude-4-opus", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 32000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 15 + }, + "output": { + "currency": "USD", + "perMillionTokens": 75 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 1.5 + } + }, + "family": "claude-opus", + "ownedBy": "sap-ai-core", + "openWeights": false + }, + { + "id": "anthropic--claude-3-haiku", + "name": "anthropic--claude-3-haiku", + "capabilities": ["function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.25 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.25 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.03 + } + }, + "family": "claude-haiku", + "ownedBy": "sap-ai-core", + "openWeights": false + }, + { + "id": "anthropic--claude-3-sonnet", + "name": "anthropic--claude-3-sonnet", + "capabilities": ["function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 15 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.3 + } + }, + "family": "claude-sonnet", + "ownedBy": "sap-ai-core", + "openWeights": false + }, + { + "id": "anthropic--claude-3-7-sonnet", + "name": "anthropic--claude-3.7-sonnet", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition", "web-search", "computer-use"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 15 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.3 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 64000 + } + }, + "family": "claude-sonnet", + "ownedBy": "sap-ai-core", + "openWeights": false + }, + { + "id": "anthropic--claude-4-5-sonnet", + "name": "anthropic--claude-4.5-sonnet", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 15 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.3 + } + }, + "family": "claude-sonnet", + "ownedBy": "sap-ai-core", + "openWeights": false + }, + { + "id": "anthropic--claude-3-opus", + "name": "anthropic--claude-3-opus", + "capabilities": ["function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 15 + }, + "output": { + "currency": "USD", + "perMillionTokens": 75 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 1.5 + } + }, + "family": "claude-opus", + "ownedBy": "sap-ai-core", + "openWeights": false + }, + { + "id": "anthropic--claude-4-sonnet", + "name": "anthropic--claude-4-sonnet", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 15 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.3 + } + }, + "family": "claude-sonnet", + "ownedBy": "sap-ai-core", + "openWeights": false + }, + { + "id": "claude-opus-4-0", + "name": "Claude Opus 4 (latest)", + "description": "Alias \nclaude-opus-4-20250514", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition", "web-search", "computer-use"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 32000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 15 + }, + "output": { + "currency": "USD", + "perMillionTokens": 75 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 1.5 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 32000 + } + }, + "family": "claude-opus", + "ownedBy": "anthropic", + "openWeights": false + }, + { + "id": "claude-3-5-sonnet-20241022", + "name": "Claude Sonnet 3.5 v2", + "capabilities": ["function-call", "file-input", "image-recognition", "web-search", "computer-use"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 15 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.3 + } + }, + "family": "claude-sonnet", + "ownedBy": "anthropic", + "openWeights": false, + "alias": ["claude-3-5-sonnet", "claude-3-5-sonnet-latest"] + }, + { + "id": "claude-3-5-haiku-latest", + "name": "Claude Haiku 3.5 (latest)", + "capabilities": ["function-call", "file-input", "image-recognition", "web-search"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.8 + }, + "output": { + "currency": "USD", + "perMillionTokens": 4 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.08 + } + }, + "family": "claude-haiku", + "ownedBy": "anthropic", + "openWeights": false + }, + { + "id": "claude-3-opus-20240229", + "name": "Claude Opus 3", + "description": "Claude’s previous generation strongest model", + "capabilities": ["function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 15 + }, + "output": { + "currency": "USD", + "perMillionTokens": 75 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 1.5 + } + }, + "family": "claude-opus", + "ownedBy": "anthropic", + "openWeights": false + }, + { + "id": "claude-3-5-haiku-20241022", + "name": "Claude Haiku 3.5", + "capabilities": ["function-call", "file-input", "image-recognition", "web-search"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.8 + }, + "output": { + "currency": "USD", + "perMillionTokens": 4 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.08 + } + }, + "family": "claude-haiku", + "ownedBy": "anthropic", + "openWeights": false, + "alias": ["claude-3-5-haiku", "claude-3-5-haiku-latest"] + }, + { + "id": "claude-sonnet-4-0", + "name": "Claude Sonnet 4 (latest)", + "description": "Claude Sonnet 4 is a significant upgrade to Sonnet 3.7, delivering superior performance in coding and reasoning with enhanced precision and control. Achieving a state-of-the-art 72.7% on SWE-bench, the model expertly balances advanced capability with computational efficiency. Key improvements include more reliable codebase navigation and complex instruction following, making it ideal for a wide range of applications, from routine coding to complex software development projects.", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition", "web-search", "computer-use"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 15 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.3 + }, + "cacheWrite": { + "currency": "USD", + "perMillionTokens": 4.125 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 64000 + } + }, + "family": "claude-sonnet", + "ownedBy": "anthropic", + "openWeights": false + }, + { + "id": "claude-3-sonnet-20240229", + "name": "Claude Sonnet 3", + "capabilities": ["function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 15 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.3 + } + }, + "family": "claude-sonnet", + "ownedBy": "anthropic", + "openWeights": false + }, + { + "id": "deepseek-v3-2-fast", + "name": "DeepSeek-V3.2-Fast", + "description": "SophNet's exclusively developed DeepSeek V3.2 Fast is the high-TPS, high-speed version of DeepSeek V3.2, achieving up to 100t/s with faster response!", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 128000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.1 + }, + "output": { + "currency": "USD", + "perMillionTokens": 3.29 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 1.096 + } + }, + "reasoning": { + "supportedEfforts": ["none"] + }, + "family": "deepseek", + "ownedBy": "deepseek", + "openWeights": true + }, + { + "id": "qwen3-max-2026-01-23", + "name": "Qwen3 Max", + "description": "The snapshot version of the Tongyi Qianwen 3 series Max model is from January 23, 2026. By default, it does not require thinking, but thinking mode can be enabled through the enable_thinking parameter, as detailed in the code example. (After enabling thinking by passing parameters, it becomes: Qwen3-Max-Thinking). This model has a total parameter count exceeding one trillion (1T) and a pre-training data volume of up to 36T Tokens, making it the largest and most powerful reasoning model from Alibaba to date.", + "capabilities": ["function-call", "reasoning"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 262144, + "maxOutputTokens": 65536, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.34 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.37 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.34246 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 0, + "max": 81920 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": false + }, + { + "id": "duo-chat-gpt-5-1", + "name": "Agentic Chat (GPT-5.1)", + "capabilities": [ + "reasoning", + "function-call", + "structured-output", + "file-input", + "image-recognition", + "web-search" + ], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 400000, + "maxOutputTokens": 128000, + "family": "gpt", + "ownedBy": "gitlab", + "openWeights": false + }, + { + "id": "duo-chat-sonnet-4-6", + "name": "Agentic Chat (Claude Sonnet 4.6)", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 64000, + "family": "claude-sonnet", + "ownedBy": "gitlab", + "openWeights": false + }, + { + "id": "duo-chat-opus-4-5", + "name": "Agentic Chat (Claude Opus 4.5)", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 64000, + "family": "claude-opus", + "ownedBy": "gitlab", + "openWeights": false + }, + { + "id": "duo-chat-sonnet-4-5", + "name": "Agentic Chat (Claude Sonnet 4.5)", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 64000, + "family": "claude-sonnet", + "ownedBy": "gitlab", + "openWeights": false + }, + { + "id": "duo-chat-gpt-5-mini", + "name": "Agentic Chat (GPT-5 Mini)", + "capabilities": [ + "reasoning", + "function-call", + "structured-output", + "file-input", + "image-recognition", + "web-search" + ], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 400000, + "maxOutputTokens": 128000, + "family": "gpt-mini", + "ownedBy": "gitlab", + "openWeights": false + }, + { + "id": "duo-chat-opus-4-6", + "name": "Agentic Chat (Claude Opus 4.6)", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 64000, + "family": "claude-opus", + "ownedBy": "gitlab", + "openWeights": false + }, + { + "id": "duo-chat-gpt-5-2", + "name": "Agentic Chat (GPT-5.2)", + "capabilities": [ + "reasoning", + "function-call", + "structured-output", + "file-input", + "image-recognition", + "web-search" + ], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 400000, + "maxOutputTokens": 128000, + "family": "gpt", + "ownedBy": "gitlab", + "openWeights": false + }, + { + "id": "duo-chat-gpt-5-2-codex", + "name": "Agentic Chat (GPT-5.2 Codex)", + "capabilities": [ + "reasoning", + "function-call", + "structured-output", + "file-input", + "image-recognition", + "web-search" + ], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 400000, + "maxOutputTokens": 128000, + "family": "gpt-codex", + "ownedBy": "gitlab", + "openWeights": false + }, + { + "id": "duo-chat-gpt-5-codex", + "name": "Agentic Chat (GPT-5 Codex)", + "capabilities": ["reasoning", "function-call", "structured-output", "image-recognition", "web-search"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 400000, + "maxOutputTokens": 128000, + "family": "gpt-codex", + "ownedBy": "gitlab", + "openWeights": false + }, + { + "id": "duo-chat-haiku-4-5", + "name": "Agentic Chat (Claude Haiku 4.5)", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 64000, + "family": "claude-haiku", + "ownedBy": "gitlab", + "openWeights": false + }, + { + "id": "magistral-small-2506", + "name": "Magistral Small 2506", + "capabilities": ["function-call", "reasoning"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.5 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.25 + } + }, + "family": "magistral-small", + "ownedBy": "mistral", + "openWeights": false + }, + { + "id": "mistral-large-instruct-2411", + "name": "Mistral Large Instruct 2411", + "capabilities": ["function-call", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 6 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 1 + } + }, + "family": "mistral-large", + "ownedBy": "mistral", + "openWeights": false + }, + { + "id": "qwen3-coder-a35b-instruct-int4-mixed-ar", + "name": "Qwen 3 Coder 480B", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 106000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.22 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.95 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.11 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": true + }, + { + "id": "llama-4-scout-16e-instruct-fp8", + "name": "Llama-4-Scout-17B-16E-Instruct-FP8", + "capabilities": ["function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4096, + "family": "llama", + "ownedBy": "meta", + "openWeights": true + }, + { + "id": "cerebras-llama-4-scout-16e-instruct", + "name": "Cerebras-Llama-4-Scout-17B-16E-Instruct", + "capabilities": ["function-call", "file-input", "image-recognition"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4096, + "family": "llama", + "ownedBy": "llama", + "openWeights": true + }, + { + "id": "cerebras-llama-4-maverick-128e-instruct", + "name": "Cerebras-Llama-4-Maverick-17B-128E-Instruct", + "capabilities": ["function-call", "file-input", "image-recognition"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4096, + "family": "llama", + "ownedBy": "llama", + "openWeights": true + }, + { + "id": "pixtral-2409", + "name": "Pixtral 12B 2409", + "capabilities": ["function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.2 + } + }, + "family": "pixtral", + "ownedBy": "mistral", + "openWeights": true + }, + { + "id": "us.anthropic.claude-sonnet-4-5-20250929-v1:0", + "name": "Claude Sonnet 4.5 (US)", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition", "web-search", "computer-use"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 15 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.3 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 64000 + } + }, + "family": "claude-sonnet", + "ownedBy": "amazon-bedrock", + "openWeights": false + }, + { + "id": "cohere.command-r-plus-v1:0", + "name": "Command R+", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 15 + } + }, + "family": "command-r", + "ownedBy": "amazon-bedrock", + "openWeights": true + }, + { + "id": "anthropic.claude-v2", + "name": "Claude 2", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 100000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 8 + }, + "output": { + "currency": "USD", + "perMillionTokens": 24 + } + }, + "family": "claude", + "ownedBy": "amazon-bedrock", + "openWeights": false + }, + { + "id": "anthropic.claude-3-7-sonnet-20250219-v1:0", + "name": "Claude Sonnet 3.7", + "capabilities": ["function-call", "file-input", "image-recognition", "reasoning", "web-search", "computer-use"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 15 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.3 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 64000 + } + }, + "family": "claude-sonnet", + "ownedBy": "amazon-bedrock", + "openWeights": false + }, + { + "id": "anthropic.claude-sonnet-4-20250514-v1:0", + "name": "Claude Sonnet 4", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition", "web-search", "computer-use"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 15 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.3 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 64000 + } + }, + "family": "claude-sonnet", + "ownedBy": "amazon-bedrock", + "openWeights": false + }, + { + "id": "qwen.qwen3-coder-a3b-v1:0", + "name": "Qwen3 Coder 30B A3B Instruct", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 262144, + "maxOutputTokens": 131072, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.15 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.6 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": false + }, + { + "id": "google.gemma-3-it", + "name": "Gemma 3 4B IT", + "capabilities": ["function-call", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.04 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.08 + } + }, + "family": "gemma", + "ownedBy": "amazon-bedrock", + "openWeights": false + }, + { + "id": "minimax.minimax-m2", + "name": "MiniMax M2", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 204608, + "maxOutputTokens": 128000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.2 + } + }, + "family": "minimax", + "ownedBy": "minimax", + "openWeights": true + }, + { + "id": "zai.glm-4-7", + "name": "GLM-4.7", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 204800, + "maxOutputTokens": 131072, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.6 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2.2 + } + }, + "family": "glm", + "ownedBy": "amazon-bedrock", + "openWeights": true + }, + { + "id": "meta.llama3-2-instruct-v1:0", + "name": "Llama 3.2 11B Instruct", + "capabilities": ["function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.16 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.16 + } + }, + "family": "llama", + "ownedBy": "amazon-bedrock", + "openWeights": true + }, + { + "id": "qwen.qwen3-next-a3b", + "name": "Qwen/Qwen3-Next-80B-A3B-Instruct", + "capabilities": ["function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 262000, + "maxOutputTokens": 262000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.14 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.4 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": false + }, + { + "id": "anthropic.claude-3-haiku-20240307-v1:0", + "name": "Claude Haiku 3", + "capabilities": ["function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.25 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.25 + } + }, + "family": "claude-haiku", + "ownedBy": "amazon-bedrock", + "openWeights": false + }, + { + "id": "qwen.qwen3-vl-a22b", + "name": "Qwen/Qwen3-VL-235B-A22B-Instruct", + "capabilities": ["function-call", "structured-output", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 262000, + "maxOutputTokens": 262000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.5 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": false + }, + { + "id": "us.anthropic.claude-opus-4-6-v1", + "name": "Claude Opus 4.6 (US)", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition", "web-search", "computer-use"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 1000000, + "maxOutputTokens": 128000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 25 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.5 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 32000 + } + }, + "family": "claude-opus", + "ownedBy": "amazon-bedrock", + "openWeights": false + }, + { + "id": "anthropic.claude-v2:1", + "name": "Claude 2.1", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 8 + }, + "output": { + "currency": "USD", + "perMillionTokens": 24 + } + }, + "family": "claude", + "ownedBy": "amazon-bedrock", + "openWeights": false + }, + { + "id": "deepseek.v3-v1:0", + "name": "DeepSeek-V3.1", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 163840, + "maxOutputTokens": 81920, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.58 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.68 + } + }, + "family": "deepseek", + "ownedBy": "deepseek", + "openWeights": true + }, + { + "id": "anthropic.claude-opus-4-5-20251101-v1:0", + "name": "Claude Opus 4.5", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition", "web-search", "computer-use"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 25 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.5 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 64000 + } + }, + "family": "claude-opus", + "ownedBy": "amazon-bedrock", + "openWeights": false + }, + { + "id": "cohere.command-light-text-v14", + "name": "Command Light", + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 4096, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.6 + } + }, + "family": "command-light", + "ownedBy": "amazon-bedrock", + "openWeights": true + }, + { + "id": "mistral.mistral-large-2402-v1:0", + "name": "Mistral Large (24.02)", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.5 + } + }, + "family": "mistral-large", + "ownedBy": "mistral", + "openWeights": false + }, + { + "id": "anthropic.claude-sonnet-4-6", + "name": "Claude Sonnet 4.6", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition", "web-search", "computer-use"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 15 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.3 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 64000 + } + }, + "family": "claude-sonnet", + "ownedBy": "amazon-bedrock", + "openWeights": false + }, + { + "id": "nvidia.nemotron-nano-v2", + "name": "NVIDIA Nemotron Nano 12B v2 VL BF16", + "capabilities": ["function-call", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.6 + } + }, + "family": "nemotron", + "ownedBy": "nvidia", + "openWeights": false + }, + { + "id": "ai21.jamba-1-5-large-v1:0", + "name": "Jamba 1.5 Large", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 256000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 8 + } + }, + "family": "jamba", + "ownedBy": "amazon-bedrock", + "openWeights": true + }, + { + "id": "eu.anthropic.claude-haiku-4-5-20251001-v1:0", + "name": "Claude Haiku 4.5 (EU)", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition", "web-search", "computer-use"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1 + }, + "output": { + "currency": "USD", + "perMillionTokens": 5 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.1 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 64000 + } + }, + "family": "claude-haiku", + "ownedBy": "amazon-bedrock", + "openWeights": false + }, + { + "id": "us.anthropic.claude-sonnet-4-20250514-v1:0", + "name": "Claude Sonnet 4 (US)", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition", "web-search", "computer-use"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 15 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.3 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 64000 + } + }, + "family": "claude-sonnet", + "ownedBy": "amazon-bedrock", + "openWeights": false + }, + { + "id": "global.anthropic.claude-sonnet-4-20250514-v1:0", + "name": "Claude Sonnet 4 (Global)", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition", "web-search", "computer-use"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 15 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.3 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 64000 + } + }, + "family": "claude-sonnet", + "ownedBy": "amazon-bedrock", + "openWeights": false + }, + { + "id": "global.anthropic.claude-haiku-4-5-20251001-v1:0", + "name": "Claude Haiku 4.5 (Global)", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition", "web-search", "computer-use"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1 + }, + "output": { + "currency": "USD", + "perMillionTokens": 5 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.1 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 64000 + } + }, + "family": "claude-haiku", + "ownedBy": "amazon-bedrock", + "openWeights": false + }, + { + "id": "meta.llama3-3-instruct-v1:0", + "name": "Llama 3.3 70B Instruct", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.72 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.72 + } + }, + "family": "llama", + "ownedBy": "amazon-bedrock", + "openWeights": true + }, + { + "id": "us.anthropic.claude-opus-4-5-20251101-v1:0", + "name": "Claude Opus 4.5 (US)", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition", "web-search", "computer-use"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 25 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.5 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 64000 + } + }, + "family": "claude-opus", + "ownedBy": "amazon-bedrock", + "openWeights": false + }, + { + "id": "anthropic.claude-opus-4-6-v1", + "name": "Claude Opus 4.6", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition", "web-search", "computer-use"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 1000000, + "maxOutputTokens": 128000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 25 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.5 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 32000 + } + }, + "family": "claude-opus", + "ownedBy": "amazon-bedrock", + "openWeights": false + }, + { + "id": "anthropic.claude-3-opus-20240229-v1:0", + "name": "Claude Opus 3", + "capabilities": ["function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 15 + }, + "output": { + "currency": "USD", + "perMillionTokens": 75 + } + }, + "family": "claude-opus", + "ownedBy": "amazon-bedrock", + "openWeights": false + }, + { + "id": "writer.palmyra-x4-v1:0", + "name": "Palmyra X4", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 122880, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2.5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 10 + } + }, + "family": "palmyra", + "ownedBy": "amazon-bedrock", + "openWeights": false + }, + { + "id": "moonshotai.kimi-k2-5", + "name": "Kimi K2.5", + "capabilities": ["reasoning", "function-call", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 256000, + "maxOutputTokens": 256000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.6 + }, + "output": { + "currency": "USD", + "perMillionTokens": 3 + } + }, + "reasoning": { + "supportedEfforts": ["none", "auto"] + }, + "ownedBy": "moonshot", + "openWeights": true + }, + { + "id": "amazon.nova-pro-v1:0", + "name": "Nova Pro", + "capabilities": ["function-call", "file-input", "image-recognition", "video-recognition"], + "inputModalities": ["text", "image", "video"], + "outputModalities": ["text"], + "contextWindow": 300000, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.8 + }, + "output": { + "currency": "USD", + "perMillionTokens": 3.2 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.2 + } + }, + "family": "nova-pro", + "ownedBy": "amazon-bedrock", + "openWeights": false + }, + { + "id": "eu.anthropic.claude-sonnet-4-6", + "name": "Claude Sonnet 4.6 (EU)", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition", "web-search", "computer-use"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 15 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.3 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 64000 + } + }, + "family": "claude-sonnet", + "ownedBy": "amazon-bedrock", + "openWeights": false + }, + { + "id": "global.anthropic.claude-sonnet-4-6", + "name": "Claude Sonnet 4.6 (Global)", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition", "web-search", "computer-use"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 15 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.3 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 64000 + } + }, + "family": "claude-sonnet", + "ownedBy": "amazon-bedrock", + "openWeights": false + }, + { + "id": "meta.llama3-1-instruct-v1:0", + "name": "Llama 3.1 8B Instruct", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.22 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.22 + } + }, + "family": "llama", + "ownedBy": "amazon-bedrock", + "openWeights": true + }, + { + "id": "us.anthropic.claude-opus-4-1-20250805-v1:0", + "name": "Claude Opus 4.1 (US)", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition", "web-search", "computer-use"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 32000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 15 + }, + "output": { + "currency": "USD", + "perMillionTokens": 75 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 1.5 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 32000 + } + }, + "family": "claude-opus", + "ownedBy": "amazon-bedrock", + "openWeights": false + }, + { + "id": "openai.gpt-oss-1:0", + "name": "gpt-oss-120b", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.15 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.6 + } + }, + "family": "gpt-oss", + "ownedBy": "amazon-bedrock", + "openWeights": false + }, + { + "id": "qwen.qwen3-v1:0", + "name": "Qwen3 32B (dense)", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 16384, + "maxOutputTokens": 16384, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.15 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.6 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": true + }, + { + "id": "anthropic.claude-3-5-sonnet-20240620-v1:0", + "name": "Claude Sonnet 3.5", + "capabilities": ["function-call", "file-input", "image-recognition", "web-search", "computer-use"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 15 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.3 + } + }, + "family": "claude-sonnet", + "ownedBy": "amazon-bedrock", + "openWeights": false + }, + { + "id": "anthropic.claude-haiku-4-5-20251001-v1:0", + "name": "Claude Haiku 4.5", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition", "web-search", "computer-use"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1 + }, + "output": { + "currency": "USD", + "perMillionTokens": 5 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.1 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 64000 + } + }, + "family": "claude-haiku", + "ownedBy": "amazon-bedrock", + "openWeights": false + }, + { + "id": "cohere.command-r-v1:0", + "name": "Command R", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.5 + } + }, + "family": "command-r", + "ownedBy": "amazon-bedrock", + "openWeights": true + }, + { + "id": "mistral.voxtral-small-2507", + "name": "Voxtral Small 24B 2507", + "capabilities": ["function-call", "file-input", "audio-recognition"], + "inputModalities": ["text", "audio"], + "outputModalities": ["text"], + "contextWindow": 32000, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.15 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.35 + } + }, + "family": "mistral", + "ownedBy": "mistral", + "openWeights": true + }, + { + "id": "amazon.nova-micro-v1:0", + "name": "Nova Micro", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.035 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.14 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.00875 + } + }, + "family": "nova-micro", + "ownedBy": "amazon-bedrock", + "openWeights": false + }, + { + "id": "meta.llama3-instruct-v1:0", + "name": "Llama 3 70B Instruct", + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 8192, + "maxOutputTokens": 2048, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2.65 + }, + "output": { + "currency": "USD", + "perMillionTokens": 3.5 + } + }, + "family": "llama", + "ownedBy": "amazon-bedrock", + "openWeights": true + }, + { + "id": "global.anthropic.claude-opus-4-6-v1", + "name": "Claude Opus 4.6 (Global)", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition", "web-search", "computer-use"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 1000000, + "maxOutputTokens": 128000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 25 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.5 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 32000 + } + }, + "family": "claude-opus", + "ownedBy": "amazon-bedrock", + "openWeights": false + }, + { + "id": "deepseek.r1-v1:0", + "name": "DeepSeek-R1", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 32768, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.35 + }, + "output": { + "currency": "USD", + "perMillionTokens": 5.4 + } + }, + "family": "deepseek-thinking", + "ownedBy": "deepseek", + "openWeights": false + }, + { + "id": "anthropic.claude-3-5-sonnet-20241022-v2:0", + "name": "Claude Sonnet 3.5 v2", + "capabilities": ["function-call", "file-input", "image-recognition", "web-search", "computer-use"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 15 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.3 + } + }, + "family": "claude-sonnet", + "ownedBy": "amazon-bedrock", + "openWeights": false + }, + { + "id": "minimax.minimax-m2-1", + "name": "MiniMax M2.1", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 204800, + "maxOutputTokens": 131072, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.2 + } + }, + "family": "minimax", + "ownedBy": "minimax", + "openWeights": true + }, + { + "id": "mistral.ministral-3-instruct", + "name": "Ministral 3 8B", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.15 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.15 + } + }, + "family": "ministral", + "ownedBy": "mistral", + "openWeights": false + }, + { + "id": "writer.palmyra-x5-v1:0", + "name": "Palmyra X5", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 1040000, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.6 + }, + "output": { + "currency": "USD", + "perMillionTokens": 6 + } + }, + "family": "palmyra", + "ownedBy": "amazon-bedrock", + "openWeights": false + }, + { + "id": "cohere.command-text-v14", + "name": "Command", + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 4096, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2 + } + }, + "family": "command", + "ownedBy": "amazon-bedrock", + "openWeights": true + }, + { + "id": "us.anthropic.claude-haiku-4-5-20251001-v1:0", + "name": "Claude Haiku 4.5 (US)", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition", "web-search", "computer-use"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1 + }, + "output": { + "currency": "USD", + "perMillionTokens": 5 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.1 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 64000 + } + }, + "family": "claude-haiku", + "ownedBy": "amazon-bedrock", + "openWeights": false + }, + { + "id": "anthropic.claude-opus-4-20250514-v1:0", + "name": "Claude Opus 4", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition", "web-search", "computer-use"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 32000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 15 + }, + "output": { + "currency": "USD", + "perMillionTokens": 75 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 1.5 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 32000 + } + }, + "family": "claude-opus", + "ownedBy": "amazon-bedrock", + "openWeights": false + }, + { + "id": "mistral.voxtral-mini-2507", + "name": "Voxtral Mini 3B 2507", + "capabilities": ["function-call", "audio-recognition"], + "inputModalities": ["audio", "text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.04 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.04 + } + }, + "family": "mistral", + "ownedBy": "mistral", + "openWeights": false + }, + { + "id": "global.anthropic.claude-sonnet-4-5-20250929-v1:0", + "name": "Claude Sonnet 4.5 (Global)", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition", "web-search", "computer-use"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 15 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.3 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 64000 + } + }, + "family": "claude-sonnet", + "ownedBy": "amazon-bedrock", + "openWeights": false + }, + { + "id": "global.anthropic.claude-opus-4-5-20251101-v1:0", + "name": "Claude Opus 4.5 (Global)", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition", "web-search", "computer-use"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 25 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.5 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 64000 + } + }, + "family": "claude-opus", + "ownedBy": "amazon-bedrock", + "openWeights": false + }, + { + "id": "amazon.nova-2-lite-v1:0", + "name": "Nova 2 Lite", + "capabilities": ["function-call", "image-recognition", "video-recognition"], + "inputModalities": ["text", "image", "video"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.33 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2.75 + } + }, + "family": "nova", + "ownedBy": "amazon-bedrock", + "openWeights": false + }, + { + "id": "qwen.qwen3-coder-a35b-v1:0", + "name": "Qwen3 Coder 480B A35B Instruct", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 65536, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.22 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.8 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": true + }, + { + "id": "anthropic.claude-sonnet-4-5-20250929-v1:0", + "name": "Claude Sonnet 4.5", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition", "web-search", "computer-use"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 15 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.3 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 64000 + } + }, + "family": "claude-sonnet", + "ownedBy": "amazon-bedrock", + "openWeights": false + }, + { + "id": "openai.gpt-oss-safeguard", + "name": "GPT OSS Safeguard 20B", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.07 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.2 + } + }, + "family": "gpt-oss", + "ownedBy": "amazon-bedrock", + "openWeights": false + }, + { + "id": "anthropic.claude-instant-v1", + "name": "Claude Instant", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 100000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.8 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2.4 + } + }, + "family": "claude", + "ownedBy": "amazon-bedrock", + "openWeights": false + }, + { + "id": "amazon.nova-premier-v1:0", + "name": "Nova Premier", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition", "video-recognition"], + "inputModalities": ["text", "image", "video"], + "outputModalities": ["text"], + "contextWindow": 1000000, + "maxOutputTokens": 16384, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2.5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 12.5 + } + }, + "family": "nova", + "ownedBy": "amazon-bedrock", + "openWeights": false + }, + { + "id": "mistral.mistral-instruct-v0:2", + "name": "Mistral-7B-Instruct-v0.3", + "capabilities": ["function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 127000, + "maxOutputTokens": 127000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.11 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.11 + } + }, + "family": "mistral", + "ownedBy": "mistral", + "openWeights": true + }, + { + "id": "eu.anthropic.claude-sonnet-4-20250514-v1:0", + "name": "Claude Sonnet 4 (EU)", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition", "web-search", "computer-use"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 15 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.3 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 64000 + } + }, + "family": "claude-sonnet", + "ownedBy": "amazon-bedrock", + "openWeights": false + }, + { + "id": "mistral.mixtral-8x7b-instruct-v0:1", + "name": "Mixtral-8x7B-Instruct-v0.1", + "capabilities": ["structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 32000, + "maxOutputTokens": 32000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.7 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.7 + } + }, + "family": "mixtral", + "ownedBy": "mistral", + "openWeights": true + }, + { + "id": "anthropic.claude-opus-4-1-20250805-v1:0", + "name": "Claude Opus 4.1", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition", "web-search", "computer-use"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 32000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 15 + }, + "output": { + "currency": "USD", + "perMillionTokens": 75 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 1.5 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 32000 + } + }, + "family": "claude-opus", + "ownedBy": "amazon-bedrock", + "openWeights": false + }, + { + "id": "meta.llama4-scout-instruct-v1:0", + "name": "Llama 4 Scout 17B Instruct", + "capabilities": ["function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 3500000, + "maxOutputTokens": 16384, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.17 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.66 + } + }, + "family": "llama", + "ownedBy": "amazon-bedrock", + "openWeights": true + }, + { + "id": "ai21.jamba-1-5-mini-v1:0", + "name": "Jamba 1.5 Mini", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 256000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.4 + } + }, + "family": "jamba", + "ownedBy": "amazon-bedrock", + "openWeights": true + }, + { + "id": "amazon.titan-text-express-v1:0:8k", + "name": "Titan Text G1 - Express", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.6 + } + }, + "family": "titan", + "ownedBy": "amazon-bedrock", + "openWeights": false + }, + { + "id": "anthropic.claude-3-sonnet-20240229-v1:0", + "name": "Claude Sonnet 3", + "capabilities": ["function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 15 + } + }, + "family": "claude-sonnet", + "ownedBy": "amazon-bedrock", + "openWeights": false + }, + { + "id": "eu.anthropic.claude-sonnet-4-5-20250929-v1:0", + "name": "Claude Sonnet 4.5 (EU)", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition", "web-search", "computer-use"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 15 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.3 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 64000 + } + }, + "family": "claude-sonnet", + "ownedBy": "amazon-bedrock", + "openWeights": false + }, + { + "id": "amazon.titan-text-express-v1", + "name": "Titan Text G1 - Express", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.6 + } + }, + "family": "titan", + "ownedBy": "amazon-bedrock", + "openWeights": false + }, + { + "id": "meta.llama4-maverick-instruct-v1:0", + "name": "Llama 4 Maverick 17B Instruct", + "capabilities": ["function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 1000000, + "maxOutputTokens": 16384, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.24 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.97 + } + }, + "family": "llama", + "ownedBy": "amazon-bedrock", + "openWeights": true + }, + { + "id": "us.anthropic.claude-sonnet-4-6", + "name": "Claude Sonnet 4.6 (US)", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition", "web-search", "computer-use"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 15 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.3 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 64000 + } + }, + "family": "claude-sonnet", + "ownedBy": "amazon-bedrock", + "openWeights": false + }, + { + "id": "qwen.qwen3-a22b-2507-v1:0", + "name": "Qwen3 235B A22B 2507", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 262144, + "maxOutputTokens": 131072, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.22 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.88 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": true + }, + { + "id": "us.anthropic.claude-opus-4-20250514-v1:0", + "name": "Claude Opus 4 (US)", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition", "web-search", "computer-use"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 32000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 15 + }, + "output": { + "currency": "USD", + "perMillionTokens": 75 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 1.5 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 32000 + } + }, + "family": "claude-opus", + "ownedBy": "amazon-bedrock", + "openWeights": false + }, + { + "id": "amazon.nova-lite-v1:0", + "name": "Nova Lite", + "capabilities": ["function-call", "file-input", "image-recognition", "video-recognition"], + "inputModalities": ["text", "image", "video"], + "outputModalities": ["text"], + "contextWindow": 300000, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.06 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.24 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.015 + } + }, + "family": "nova-lite", + "ownedBy": "amazon-bedrock", + "openWeights": false + }, + { + "id": "anthropic.claude-3-5-haiku-20241022-v1:0", + "name": "Claude Haiku 3.5", + "capabilities": ["function-call", "file-input", "image-recognition", "web-search"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.8 + }, + "output": { + "currency": "USD", + "perMillionTokens": 4 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.08 + } + }, + "family": "claude-haiku", + "ownedBy": "amazon-bedrock", + "openWeights": false + }, + { + "id": "moonshot.kimi-k2", + "name": "Kimi K2 Thinking", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 256000, + "maxOutputTokens": 256000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.6 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2.5 + } + }, + "ownedBy": "moonshot", + "openWeights": true + }, + { + "id": "zai.glm-4-7-flash", + "name": "GLM-4.7-Flash", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 131072, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.07 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.4 + } + }, + "family": "glm-flash", + "ownedBy": "amazon-bedrock", + "openWeights": true + }, + { + "id": "eu.anthropic.claude-opus-4-5-20251101-v1:0", + "name": "Claude Opus 4.5 (EU)", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition", "web-search", "computer-use"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 25 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.5 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 64000 + } + }, + "family": "claude-opus", + "ownedBy": "amazon-bedrock", + "openWeights": false + }, + { + "id": "deepseek.v3-2-v1:0", + "name": "DeepSeek-V3.2", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 163840, + "maxOutputTokens": 81920, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.62 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.85 + } + }, + "family": "deepseek", + "ownedBy": "deepseek", + "openWeights": true + }, + { + "id": "eu.anthropic.claude-opus-4-6-v1", + "name": "Claude Opus 4.6 (EU)", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition", "web-search", "computer-use"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 1000000, + "maxOutputTokens": 128000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 25 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.5 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 32000 + } + }, + "family": "claude-opus", + "ownedBy": "amazon-bedrock", + "openWeights": false + }, + { + "id": "ideogram", + "name": "Ideogram", + "capabilities": ["function-call", "file-input", "image-recognition", "image-generation"], + "inputModalities": ["text", "image"], + "outputModalities": ["image"], + "contextWindow": 150, + "family": "ideogram", + "ownedBy": "ideogram", + "openWeights": false + }, + { + "id": "ideogram-v2a", + "name": "Ideogram-v2a", + "capabilities": ["function-call", "file-input", "image-generation"], + "inputModalities": ["text"], + "outputModalities": ["image"], + "contextWindow": 150, + "family": "ideogram", + "ownedBy": "ideogram", + "openWeights": false + }, + { + "id": "ideogram-v2a-turbo", + "name": "Ideogram-v2a-Turbo", + "capabilities": ["function-call", "file-input", "image-generation"], + "inputModalities": ["text"], + "outputModalities": ["image"], + "contextWindow": 150, + "family": "ideogram", + "ownedBy": "ideogram", + "openWeights": false + }, + { + "id": "ideogram-v2", + "name": "Ideogram-v2", + "capabilities": ["function-call", "file-input", "image-recognition", "image-generation"], + "inputModalities": ["text", "image"], + "outputModalities": ["image"], + "contextWindow": 150, + "family": "ideogram", + "ownedBy": "ideogram", + "openWeights": false + }, + { + "id": "runway", + "name": "Runway", + "capabilities": ["function-call", "file-input", "image-recognition", "video-generation"], + "inputModalities": ["text", "image"], + "outputModalities": ["video"], + "contextWindow": 256, + "family": "runway", + "ownedBy": "runway", + "openWeights": false + }, + { + "id": "runway-gen-4-turbo", + "name": "Runway-Gen-4-Turbo", + "capabilities": ["function-call", "file-input", "image-recognition", "video-generation"], + "inputModalities": ["text", "image"], + "outputModalities": ["video"], + "contextWindow": 256, + "family": "runway", + "ownedBy": "runway", + "openWeights": false + }, + { + "id": "claude-code", + "name": "claude-code", + "capabilities": ["reasoning", "function-call", "file-input"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "ownedBy": "anthropic", + "openWeights": false + }, + { + "id": "elevenlabs-v3", + "name": "ElevenLabs-v3", + "capabilities": ["function-call", "file-input", "audio-generation"], + "inputModalities": ["text"], + "outputModalities": ["audio"], + "contextWindow": 128000, + "family": "elevenlabs", + "ownedBy": "elevenlabs", + "openWeights": false + }, + { + "id": "elevenlabs-music", + "name": "ElevenLabs-Music", + "capabilities": ["function-call", "file-input", "audio-generation"], + "inputModalities": ["text"], + "outputModalities": ["audio"], + "contextWindow": 2000, + "family": "elevenlabs", + "ownedBy": "elevenlabs", + "openWeights": false + }, + { + "id": "elevenlabs-v2-5-turbo", + "name": "ElevenLabs-v2.5-Turbo", + "capabilities": ["function-call", "file-input", "audio-generation"], + "inputModalities": ["text"], + "outputModalities": ["audio"], + "contextWindow": 128000, + "family": "elevenlabs", + "ownedBy": "elevenlabs", + "openWeights": false + }, + { + "id": "gemini-deep-research", + "name": "gemini-deep-research", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition", "video-recognition"], + "inputModalities": ["text", "image", "video"], + "outputModalities": ["text"], + "contextWindow": 1048576, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.6 + }, + "output": { + "currency": "USD", + "perMillionTokens": 9.6 + } + }, + "ownedBy": "google", + "openWeights": false + }, + { + "id": "nano-banana", + "name": "Nano-Banana", + "capabilities": ["function-call", "file-input", "image-recognition", "image-generation"], + "inputModalities": ["text", "image"], + "outputModalities": ["text", "image"], + "contextWindow": 65536, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.21 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.8 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.021 + } + }, + "family": "nano-banana", + "ownedBy": "google", + "openWeights": false + }, + { + "id": "imagen-4", + "name": "Imagen-4", + "capabilities": ["function-call", "file-input", "image-generation"], + "inputModalities": ["text"], + "outputModalities": ["image"], + "contextWindow": 480, + "family": "imagen", + "ownedBy": "google", + "openWeights": false + }, + { + "id": "imagen-3", + "name": "Imagen-3", + "capabilities": ["function-call", "file-input", "image-generation", "video-generation"], + "inputModalities": ["text"], + "outputModalities": ["image"], + "contextWindow": 480, + "family": "imagen", + "ownedBy": "google", + "openWeights": false + }, + { + "id": "imagen-4-ultra", + "name": "Imagen-4-Ultra", + "capabilities": ["function-call", "file-input", "image-generation"], + "inputModalities": ["text"], + "outputModalities": ["image"], + "contextWindow": 480, + "family": "imagen", + "ownedBy": "google", + "openWeights": false + }, + { + "id": "veo-3-1", + "name": "Veo-3.1", + "capabilities": ["function-call", "file-input", "video-generation"], + "inputModalities": ["text"], + "outputModalities": ["video"], + "contextWindow": 480, + "family": "veo", + "ownedBy": "google", + "openWeights": false + }, + { + "id": "imagen-3-fast", + "name": "Imagen-3-Fast", + "capabilities": ["function-call", "file-input", "image-generation", "video-generation"], + "inputModalities": ["text"], + "outputModalities": ["image"], + "contextWindow": 480, + "family": "imagen", + "ownedBy": "google", + "openWeights": false + }, + { + "id": "lyria", + "name": "Lyria", + "capabilities": ["function-call", "file-input", "audio-generation"], + "inputModalities": ["text"], + "outputModalities": ["audio"], + "family": "lyria", + "ownedBy": "poe", + "openWeights": false + }, + { + "id": "veo-3", + "name": "Veo-3", + "description": "veo3 reverse access with a total cost of just $0.41 per video generation., OpenAI chat port compatible format.", + "capabilities": ["function-call", "file-input", "video-generation"], + "inputModalities": ["text"], + "outputModalities": ["video"], + "contextWindow": 480, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2 + } + }, + "family": "veo", + "ownedBy": "google", + "openWeights": false + }, + { + "id": "veo-3-fast", + "name": "Veo-3-Fast", + "capabilities": ["function-call", "file-input", "video-generation"], + "inputModalities": ["text"], + "outputModalities": ["video"], + "contextWindow": 480, + "family": "veo", + "ownedBy": "google", + "openWeights": false + }, + { + "id": "imagen-4-fast", + "name": "Imagen-4-Fast", + "capabilities": ["function-call", "file-input", "image-generation"], + "inputModalities": ["text"], + "outputModalities": ["image"], + "contextWindow": 480, + "family": "imagen", + "ownedBy": "google", + "openWeights": false + }, + { + "id": "veo-2", + "name": "Veo-2", + "capabilities": ["function-call", "file-input", "video-generation"], + "inputModalities": ["text"], + "outputModalities": ["video"], + "contextWindow": 480, + "family": "veo", + "ownedBy": "google", + "openWeights": false + }, + { + "id": "nano-banana-pro", + "name": "Nano-Banana-Pro", + "capabilities": ["function-call", "file-input", "image-recognition", "image-generation"], + "inputModalities": ["text", "image"], + "outputModalities": ["image"], + "contextWindow": 65536, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 12 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.2 + } + }, + "family": "nano-banana", + "ownedBy": "google", + "openWeights": false + }, + { + "id": "veo-3-1-fast", + "name": "Veo-3.1-Fast", + "capabilities": ["function-call", "file-input", "image-recognition", "video-generation"], + "inputModalities": ["text", "image"], + "outputModalities": ["video"], + "contextWindow": 480, + "family": "veo", + "ownedBy": "google", + "openWeights": false + }, + { + "id": "gpt-5-2-instant", + "name": "GPT-5.2-Instant", + "capabilities": ["function-call", "file-input", "image-recognition", "web-search", "reasoning"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 16384, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.6 + }, + "output": { + "currency": "USD", + "perMillionTokens": 13 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.16 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high", "max"] + }, + "ownedBy": "openai", + "openWeights": false + }, + { + "id": "sora-2", + "name": "Sora-2", + "description": "Sora-2 is the next-generation text-to-video model evolved from Sora, optimized for higher visual realism, stronger physical consistency, and longer temporal coherence. It delivers more stable character consistency, complex motion rendering, camera control, and narrative continuity, while supporting higher resolutions and minute-level video generation for film production, advertising, virtual content creation, and creative multimedia workflows.", + "capabilities": ["function-call", "file-input", "image-recognition", "video-generation"], + "inputModalities": ["text", "image"], + "outputModalities": ["video"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2 + } + }, + "family": "sora", + "ownedBy": "openai", + "openWeights": false + }, + { + "id": "gpt-3-5-turbo-raw", + "name": "GPT-3.5-Turbo-Raw", + "capabilities": ["function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 4524, + "maxOutputTokens": 2048, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.45 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.4 + } + }, + "family": "gpt", + "ownedBy": "openai", + "openWeights": false + }, + { + "id": "gpt-4o-search", + "name": "GPT-4o-Search", + "capabilities": ["function-call", "file-input", "image-recognition", "web-search"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2.2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 9 + } + }, + "family": "gpt", + "ownedBy": "openai", + "openWeights": false + }, + { + "id": "gpt-image-1-5", + "name": "gpt-image-1.5", + "description": "GPT Image 1.5 is a new image generation model powered by OpenAI’s flagship visual capabilities, comprehensively upgraded for high-quality creative and production workflows. It delivers significant improvements in instruction understanding, fine-grained image editing, and detail preservation, while achieving up to 4× faster generation compared to previous versions — reducing latency without compromising quality.\n\nGPT Image 1.5 is well suited for image generation, precise visual editing, and professional content creation, balancing performance with efficiency.", + "capabilities": ["function-call", "file-input", "image-recognition", "image-generation"], + "inputModalities": ["text", "image"], + "outputModalities": ["image"], + "contextWindow": 128000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 10 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 5 + } + }, + "ownedBy": "openai", + "openWeights": false + }, + { + "id": "gpt-image-1-mini", + "name": "GPT-Image-1-Mini", + "description": "OpenAI image generation model gpt-image-1-mini\nBefore use, please run pip install -U openai to upgrade to the latest openai package.", + "capabilities": ["function-call", "file-input", "image-recognition", "image-generation"], + "inputModalities": ["text", "image"], + "outputModalities": ["image"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 40 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 5 + } + }, + "family": "gpt", + "ownedBy": "openai", + "openWeights": false + }, + { + "id": "gpt-4o-aug", + "name": "GPT-4o-Aug", + "capabilities": ["function-call", "file-input", "image-recognition", "web-search"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2.2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 9 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 1.1 + } + }, + "family": "gpt", + "ownedBy": "openai", + "openWeights": false + }, + { + "id": "gpt-image-1", + "name": "GPT-Image-1", + "description": "Azure OpenAI’s gpt-image-1 image generation API offers both text-to-image generation and image-to-image editing with text guidance capabilities.\nBefore using this API, please ensure you have the latest OpenAI package installed by running pip install -U openai.", + "capabilities": ["function-call", "file-input", "image-recognition", "image-generation"], + "inputModalities": ["text", "image"], + "outputModalities": ["image"], + "contextWindow": 128000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 40 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 5 + } + }, + "family": "gpt", + "ownedBy": "openai", + "openWeights": false + }, + { + "id": "gpt-4-classic-0314", + "name": "GPT-4-Classic-0314", + "capabilities": ["function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 8192, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 27 + }, + "output": { + "currency": "USD", + "perMillionTokens": 54 + } + }, + "family": "gpt", + "ownedBy": "openai", + "openWeights": false + }, + { + "id": "dall-e-3", + "name": "DALL-E-3", + "description": "dall-e-3 is an AI image generation model that converts natural language prompts into realistic visuals and artistic content. It delivers accurate semantic understanding, supports customizable output resolutions, and produces high-quality images across a wide range of styles, making it well-suited for concept design, creative prototyping, and professional content workflows.", + "capabilities": ["function-call", "file-input", "image-generation"], + "inputModalities": ["text"], + "outputModalities": ["image"], + "contextWindow": 800, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 40 + }, + "output": { + "currency": "USD", + "perMillionTokens": 40 + } + }, + "family": "dall-e", + "ownedBy": "openai", + "openWeights": false + }, + { + "id": "sora-2-pro", + "name": "Sora-2-Pro", + "description": "OpenAI video model Sora2-pro official API.", + "capabilities": ["function-call", "file-input", "image-recognition", "video-generation"], + "inputModalities": ["text", "image"], + "outputModalities": ["video"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2 + } + }, + "family": "sora", + "ownedBy": "openai", + "openWeights": false + }, + { + "id": "gpt-4o-mini-search", + "name": "GPT-4o-mini-Search", + "capabilities": ["function-call", "file-input", "image-recognition", "web-search"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.14 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.54 + } + }, + "family": "gpt-mini", + "ownedBy": "openai", + "openWeights": false + }, + { + "id": "stablediffusionxl", + "name": "StableDiffusionXL", + "capabilities": ["function-call", "file-input", "image-recognition", "image-generation"], + "inputModalities": ["text", "image"], + "outputModalities": ["image"], + "contextWindow": 200, + "family": "stable-diffusion", + "ownedBy": "poe", + "openWeights": false + }, + { + "id": "topazlabs", + "name": "TopazLabs", + "capabilities": ["function-call", "file-input", "image-generation"], + "inputModalities": ["text"], + "outputModalities": ["image"], + "contextWindow": 204, + "family": "topazlabs", + "ownedBy": "poe", + "openWeights": false + }, + { + "id": "ray2", + "name": "Ray2", + "capabilities": ["function-call", "file-input", "image-recognition", "video-generation"], + "inputModalities": ["text", "image"], + "outputModalities": ["video"], + "contextWindow": 5000, + "family": "ray", + "ownedBy": "poe", + "openWeights": false + }, + { + "id": "claude-sonnet-3-7", + "name": "Claude-Sonnet-3.7", + "capabilities": ["reasoning", "function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 196608, + "maxOutputTokens": 128000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2.6 + }, + "output": { + "currency": "USD", + "perMillionTokens": 13 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.26 + } + }, + "family": "claude-sonnet", + "ownedBy": "anthropic", + "openWeights": false + }, + { + "id": "claude-haiku-3", + "name": "Claude-Haiku-3", + "capabilities": ["function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 189096, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.21 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.1 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.021 + } + }, + "family": "claude-haiku", + "ownedBy": "anthropic", + "openWeights": false + }, + { + "id": "claude-sonnet-3-5", + "name": "Claude-Sonnet-3.5", + "capabilities": ["function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 189096, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2.6 + }, + "output": { + "currency": "USD", + "perMillionTokens": 13 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.26 + } + }, + "family": "claude-sonnet", + "ownedBy": "anthropic", + "openWeights": false + }, + { + "id": "claude-haiku-3-5", + "name": "Claude-Haiku-3.5", + "capabilities": ["function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 189096, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.68 + }, + "output": { + "currency": "USD", + "perMillionTokens": 3.4 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.068 + } + }, + "family": "claude-haiku", + "ownedBy": "anthropic", + "openWeights": false + }, + { + "id": "claude-sonnet-3-5-june", + "name": "Claude-Sonnet-3.5-June", + "capabilities": ["function-call", "file-input", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 189096, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2.6 + }, + "output": { + "currency": "USD", + "perMillionTokens": 13 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.26 + } + }, + "family": "claude-sonnet", + "ownedBy": "anthropic", + "openWeights": false + }, + { + "id": "tako", + "name": "Tako", + "capabilities": ["function-call", "file-input"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 2048, + "family": "tako", + "ownedBy": "poe", + "openWeights": false + }, + { + "id": "glm-4-7-n", + "name": "glm-4.7-n", + "capabilities": ["reasoning", "function-call", "file-input"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 205000, + "maxOutputTokens": 131072, + "reasoning": { + "supportedEfforts": ["none"], + "interleaved": true + }, + "ownedBy": "zhipu", + "openWeights": false + }, + { + "id": "llama-3-1-cs", + "name": "llama-3.1-8b-cs", + "capabilities": ["function-call", "file-input"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "ownedBy": "meta", + "openWeights": false + }, + { + "id": "gpt-oss-cs", + "name": "gpt-oss-120b-cs", + "capabilities": ["reasoning", "function-call", "file-input"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "reasoning": { + "supportedEfforts": ["low", "medium", "high"] + }, + "ownedBy": "openai", + "openWeights": false + }, + { + "id": "qwen3-2507-cs", + "name": "qwen3-235b-2507-cs", + "capabilities": ["reasoning", "function-call", "file-input"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 38912 + } + }, + "ownedBy": "alibaba", + "openWeights": false + }, + { + "id": "qwen3-cs", + "name": "qwen3-32b-cs", + "capabilities": ["reasoning", "function-call", "file-input"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 38912 + } + }, + "ownedBy": "alibaba", + "openWeights": false + }, + { + "id": "llama-3-3-cs", + "name": "llama-3.3-70b-cs", + "capabilities": ["file-input"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "ownedBy": "meta", + "openWeights": false + }, + { + "id": "qwen-3-a22b-instruct-2507", + "name": "Qwen 3 235B Instruct", + "description": "cerebras", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131000, + "maxOutputTokens": 32000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.6 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.2 + } + }, + "family": "qwen", + "ownedBy": "alibaba", + "openWeights": true + }, + { + "id": "llama3-1", + "name": "Llama 3.1 8B", + "description": "cerebras", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 32000, + "maxOutputTokens": 8000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.1 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.1 + } + }, + "family": "llama", + "ownedBy": "meta", + "openWeights": true + }, + { + "id": "doubao-seed-2-0-code-preview", + "description": "The Doubao 2.0 series is a coding model optimized for real programming environments, capable of reliably invoking tools in common IDEs such as Claude Code. The model is specially optimized for frontend capabilities and performs well with common frontend frameworks. The model supports using Skills and can work with various custom skills.", + "capabilities": ["reasoning", "web-search", "function-call", "structured-output"], + "inputModalities": ["text", "image", "video"], + "outputModalities": ["text"], + "contextWindow": 256000, + "maxOutputTokens": 128000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.4822 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2.411 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.09644 + } + }, + "reasoning": { + "supportedEfforts": ["minimal", "low", "medium", "high"] + }, + "ownedBy": "bytedance" + }, + { + "id": "gpt-4-1-nano-free", + "description": "This free model API comes from the OpenAI model deployed on Azure. To prevent abuse, the external content filter provided by Azure has been enforced, which will result in additional delays. If you want to experience the full version of the model API without filters, please use the paid version and request the model name ID without \"-free\".", + "capabilities": ["function-call", "structured-output", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 1047576, + "maxOutputTokens": 32768, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "ownedBy": "openai" + }, + { + "id": "qwen3-vl-flash", + "description": "The Qwen3 series of compact visual-understanding models achieves an effective fusion of thinking mode and non-thinking mode, outperforming the open-source Qwen3-VL-30B-A3B with faster response speeds. It comprehensively upgrades image and video understanding, supporting ultra-long contexts such as long videos and long documents, spatial awareness, and universal object recognition; it also possesses visual 2D/3D localization capabilities and is capable of handling complex real-world tasks.", + "capabilities": ["function-call", "structured-output", "image-recognition", "reasoning"], + "inputModalities": ["text", "image", "video"], + "outputModalities": ["text"], + "contextWindow": 254000, + "maxOutputTokens": 32000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.0206 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.206 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.00412 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 38912 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "qwen3-vl-flash-2026-01-22", + "description": "The Qwen3 series of compact visual-understanding models achieves an effective fusion of thinking mode and non-thinking mode, outperforming the open-source Qwen3-VL-30B-A3B with faster response speeds. It comprehensively upgrades image and video understanding, supporting ultra-long contexts such as long videos and long documents, spatial awareness, and universal object recognition; it also possesses visual 2D/3D localization capabilities and is capable of handling complex real-world tasks.", + "capabilities": ["function-call", "structured-output", "image-recognition", "reasoning"], + "inputModalities": ["text", "image", "video"], + "outputModalities": ["text"], + "contextWindow": 254000, + "maxOutputTokens": 32000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.0206 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.206 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.0206 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 38912 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "doubao-seedream-4-5", + "description": "Seedream 4.5 is ByteDance's latest multimodal image model, integrating capabilities such as text-to-image, image-to-image, and multi-image output, along with incorporating common sense and reasoning abilities. Compared to the previous 4.0 model, it significantly improves generation quality, offering better editing consistency and multi-image fusion effects, with more precise control over image details. The generation of small text and small faces is more natural.", + "capabilities": ["image-generation", "function-call"], + "inputModalities": ["text", "image"], + "outputModalities": ["image"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "ownedBy": "bytedance" + }, + { + "id": "wan2-6-i2v", + "description": "Wan 2.6 - Text-to-Video generation features intelligent storyboard scheduling supporting multi-shot narration, higher quality sound generation, stable multi-person dialogue, more natural and realistic voice tones, and supports video generation up to 15 seconds in length.", + "capabilities": ["video-generation"], + "inputModalities": ["image", "text"], + "outputModalities": ["video"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "wan2-6-t2v", + "description": "Wan 2.6 - Text-to-Video generation features intelligent storyboard scheduling supporting multi-shot narration, higher quality sound generation, stable multi-person dialogue, more natural and realistic voice tones, and supports video generation up to 15 seconds in length.", + "capabilities": ["video-generation"], + "inputModalities": ["text"], + "outputModalities": ["video"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "gpt-4o-audio-preview", + "name": "OpenAI: GPT-4o Audio", + "description": "The gpt-4o-audio-preview model adds support for audio inputs as prompts. This enhancement allows the model to detect nuances within audio recordings and add depth to generated user experiences. Audio outputs are currently not supported. Audio tokens are priced at $40 per million input and $80 per million output audio tokens.", + "capabilities": [ + "function-call", + "structured-output", + "audio-recognition", + "audio-generation", + "image-recognition", + "web-search" + ], + "inputModalities": ["audio", "text"], + "outputModalities": ["text", "audio"], + "contextWindow": 128000, + "maxOutputTokens": 16384, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2.5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 10 + } + }, + "ownedBy": "openai" + }, + { + "id": "gpt-4o-mini-audio-preview", + "capabilities": ["function-call", "image-recognition", "web-search"], + "inputModalities": ["text", "audio"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.15 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.6 + } + }, + "ownedBy": "openai" + }, + { + "id": "flux-2-flex", + "description": "FLUX.2 is purpose-built for real-world creative production workflows. It delivers high-quality images while maintaining character and style consistency across multiple reference images, shows exceptional understanding and execution of structured prompts, and supports complex text reading and writing. It also adheres to brand guidelines, handles lighting, layout, and logo elements with stability, and enables image editing at resolutions up to 4MP — all while preserving fine details, striking a balance between creativity and professional-grade visual output.", + "capabilities": ["image-generation"], + "inputModalities": ["text", "image"], + "outputModalities": ["image"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "ownedBy": "bfl" + }, + { + "id": "flux-2-pro", + "description": "FLUX.2 is purpose-built for real-world creative production workflows. It delivers high-quality images while maintaining character and style consistency across multiple reference images, shows exceptional understanding and execution of structured prompts, and supports complex text reading and writing. It also adheres to brand guidelines, handles lighting, layout, and logo elements with stability, and enables image editing at resolutions up to 4MP — all while preserving fine details, striking a balance between creativity and professional-grade visual output.", + "capabilities": ["image-generation"], + "inputModalities": ["text", "image"], + "outputModalities": ["image"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "ownedBy": "bfl" + }, + { + "id": "jimeng-3-0-1080p", + "description": "DreamVideo 3.0 Pro is a professional-grade text-to-video and image-to-video model built on the Dream framework, delivering a major breakthrough in video generation quality. This version demonstrates strong performance across multiple dimensions, including narrative coherence, instruction following, dynamic fluidity, and visual detail. It supports multi-shot storytelling and generates 1080P high-definition videos with a professional cinematic texture. The model also enables diverse and expressive stylistic rendering, making it well suited for creative production and visual storytelling.", + "capabilities": ["video-generation"], + "inputModalities": ["text", "image"], + "outputModalities": ["video"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + } + }, + { + "id": "jimeng-3-0-720p", + "description": "DreamVideo 3.0 Pro is a professional-grade text-to-video and image-to-video model built on the Dream framework, delivering a major breakthrough in video generation quality. This version demonstrates strong performance across multiple dimensions, including narrative coherence, instruction following, dynamic fluidity, and visual detail. It supports multi-shot storytelling and generates 1080P high-definition videos with a professional cinematic texture. The model also enables diverse and expressive stylistic rendering, making it well suited for creative production and visual storytelling.", + "capabilities": ["video-generation"], + "inputModalities": ["text", "image"], + "outputModalities": ["video"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + } + }, + { + "id": "jimeng-3-0-pro", + "description": "DreamVideo 3.0 Pro is a professional-grade text-to-video and image-to-video model built on the Dream framework, delivering a major breakthrough in video generation quality. This version demonstrates strong performance across multiple dimensions, including narrative coherence, instruction following, dynamic fluidity, and visual detail. It supports multi-shot storytelling and generates 1080P high-definition videos with a professional cinematic texture. The model also enables diverse and expressive stylistic rendering, making it well suited for creative production and visual storytelling.", + "capabilities": ["video-generation"], + "inputModalities": ["text", "image"], + "outputModalities": ["video"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + } + }, + { + "id": "kimi-for-coding", + "description": "kimi-for-coding-free is a free and open version offered by AIHubMix specifically for Kimi users. To maintain stable service operations, the following usage limits apply: a maximum of 5 requests per minute 500 total requests per day, and a daily quota of 1 million tokens.", + "capabilities": ["reasoning", "function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 256000, + "maxOutputTokens": 256000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.02 + } + }, + "ownedBy": "moonshot" + }, + { + "id": "qianfan-ocr", + "description": "Qianfan-OCR-Fast is a multimodal large model specialized for OCR, trained primarily on OCR-domain data while retaining appropriate general multimodal capabilities, and it outperforms Qianfan-OCR.", + "capabilities": ["file-input"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 32000, + "maxOutputTokens": 28000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.062 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.248 + } + } + }, + { + "id": "qianfan-ocr-fast", + "description": "Qianfan-OCR-Fast is a multimodal large model specialized for OCR, trained primarily on OCR-domain data while retaining appropriate general multimodal capabilities, and it outperforms Qianfan-OCR.", + "capabilities": ["file-input"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 32000, + "maxOutputTokens": 28000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.664 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2.738336 + } + } + }, + { + "id": "wan2-2-i2v-plus", + "description": "The newly upgraded Tongyi Wanxiang 2.2 text-to-video offers higher video quality. It optimizes video generation stability and success rate, features stronger instruction-following capabilities, consistently maintains image text, portrait, and product consistency, and provides precise camera motion control.", + "capabilities": ["video-generation"], + "inputModalities": ["text", "image"], + "outputModalities": ["video"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "wan2-2-t2v-plus", + "description": "The newly upgraded Tongyi Wanxiang 2.2 text-to-video offers higher video quality. It can stably generate large-scale complex motions, supports cinematic-level visual performance and control, and features enhanced instruction-following capabilities to achieve realistic physical world reproduction.", + "capabilities": ["video-generation"], + "inputModalities": ["text"], + "outputModalities": ["video"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "wan2-5-i2v-preview", + "description": "Tongyi Wanxiang 2.5 - Text-to-Video Preview features a newly upgraded technical architecture, supporting sound generation synchronized with visuals, 10-second long video generation, stronger instruction-following capabilities, and further improvements in motion ability and visual quality.", + "capabilities": ["video-generation"], + "inputModalities": ["text", "image"], + "outputModalities": ["video"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "wan2-5-t2v-preview", + "description": "Tongyi Wanxiang 2.5 - Text-to-Video Preview, newly upgraded model architecture, supports sound generation synchronized with visuals, supports 10-second long video generation, enhanced instruction compliance, improved motion capability, and further enhanced visual quality.", + "capabilities": ["video-generation"], + "inputModalities": ["text"], + "outputModalities": ["video"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "deepseek-v3-1-fast", + "description": "The model provider is the Sophon platform. DeepSeek V3.1 Fast is the high-TPS speed version of DeepSeek V3.1.\nHybrid thinking mode: By modifying the chat template, a single model can simultaneously support both thinking and non-thinking modes.\nSmarter tool usage: Through post-training optimization, the model’s performance in tool utilization and agent tasks has improved significantly.", + "capabilities": ["function-call", "structured-output", "reasoning"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 163000, + "maxOutputTokens": 163000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.096 + }, + "output": { + "currency": "USD", + "perMillionTokens": 3.288 + } + }, + "reasoning": { + "supportedEfforts": ["none"] + }, + "ownedBy": "deepseek" + }, + { + "id": "deepseek-v3-fast", + "description": "V3 Ultra-Fast Version,The current price is a limited-time 50% discount and will return to the original price on July 31st. The original price is: input: $0.55/M, output: $2.2/M. The model provider is the Sophnet platform. DeepSeek V3 Fast is a high-TPS, ultra-fast version of DeepSeek V3 0324, featuring full-precision (non-quantized) performance, enhanced code and math capabilities, and faster responses!\n\nDeepSeek V3 0324 is a powerful Mixture-of-Experts (MoE) model with a total parameter count of 671B, activating 37B parameters per token.\nIt adopts Multi-Head Latent Attention (MLA) and the DeepSeekMoE architecture to achieve efficient inference and economical training costs.\nIt innovatively implements a load balancing strategy without auxiliary loss and sets multi-token prediction training targets to enhance performance.\nThe model is pre-trained on 14.8 trillion diverse, high-quality tokens and further optimized through supervised fine-tuning and reinforcement learning stages to fully realize its capabilities.\nComprehensive evaluations show that DeepSeek V3 outperforms other open-source models and rivals leading closed-source models in performance.\nThe entire training process only requires 2.788M H800 GPU hours and remains highly stable, with no irrecoverable loss spikes or rollbacks.", + "capabilities": ["function-call", "structured-output", "reasoning"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 32000, + "maxOutputTokens": 32000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.56 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2.24 + } + }, + "reasoning": { + "supportedEfforts": ["none"] + }, + "ownedBy": "deepseek" + }, + { + "id": "veo-2-0-generate-001", + "description": "Veo 2.0 is an advanced video generation model capable of producing high-quality videos based on text or image prompts. It excels in understanding real-world physics and human motion, resulting in fluid character movements and lifelike scenes. Veo 2.0 supports various visual styles and camera control options, including lens types, angles, and motion effects. Users can generate 8-second video clips at 720p resolution.", + "capabilities": ["video-generation"], + "inputModalities": ["video"], + "outputModalities": ["video"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2 + } + }, + "ownedBy": "google" + }, + { + "id": "veo3-1", + "description": "veo3.1 reverse model, and other available model names that can be requested include: veo3.1-pro and veo3.1-components. The price is currently tentatively set to be calculated per token, approximately $0.05 per request.", + "capabilities": ["video-generation"], + "inputModalities": ["text"], + "outputModalities": ["video"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 200 + }, + "output": { + "currency": "USD", + "perMillionTokens": 200 + } + }, + "ownedBy": "google" + }, + { + "id": "imagen-4-0", + "description": "Imagen 4 is a high-quality text-to-image model developed by Google, designed for strong visual fidelity, diverse artistic styles, and precise controllability. It delivers near photographic realism with sharp details and natural lighting while significantly reducing common artifacts such as distorted hands. The model supports a wide range of styles including photorealistic, illustration, anime, oil painting, and pixel art, and offers flexible aspect ratios for use cases from content covers to mobile wallpapers. It also enables image editing and secondary creation on existing images, provides fast and stable generation, and offers strong commercial usability with high visual quality and reliable content safety.", + "capabilities": ["image-generation"], + "inputModalities": ["text", "image"], + "outputModalities": ["image"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2 + } + }, + "ownedBy": "google" + }, + { + "id": "imagen-4-0-fast-generate-preview-06-06", + "capabilities": ["image-generation"], + "inputModalities": ["text", "image"], + "outputModalities": ["image"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2 + } + }, + "ownedBy": "google" + }, + { + "id": "imagen-4-0-ultra", + "capabilities": ["image-generation"], + "inputModalities": ["text", "image"], + "outputModalities": ["image"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2 + } + }, + "ownedBy": "google" + }, + { + "id": "pp-structurev3", + "description": "PP-StructureV3 is an efficient and comprehensive document parsing solution that can effectively convert document images and PDF files into structured content (such as Markdown format). It features powerful capabilities including layout area detection, table recognition, formula recognition, chart understanding, and multi-column reading order recovery. This tool performs excellently across various document types and can handle complex document data.", + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + } + }, + { + "id": "veo-3-0-generate-preview", + "description": "Veo 3.0 Generate Preview is an advanced AI video generation model that supports text-to-video creation with synchronized audio, featuring excellent physical simulation and lip-sync capabilities. Users can generate vivid video clips from short story prompts. 🎟️ Limited-Time Deal: Save 10% Now.", + "capabilities": ["video-generation"], + "inputModalities": ["text", "image", "video"], + "outputModalities": ["video"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2 + } + }, + "ownedBy": "google" + }, + { + "id": "veo-3-1-fast-generate-preview", + "description": "Veo 3.1 is Google's state-of-the-art model for generating high-fidelity, 8-second 720p , 1080p or 4k videos featuring stunning realism and natively generated audio.", + "capabilities": ["video-generation"], + "inputModalities": ["text", "image", "video"], + "outputModalities": ["video"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "ownedBy": "google" + }, + { + "id": "veo-3-1-generate-preview", + "description": "Veo 3.1 is Google's state-of-the-art model for generating high-fidelity, 8-second 720p , 1080p or 4k videos featuring stunning realism and natively generated audio.", + "capabilities": ["video-generation"], + "inputModalities": ["text", "image", "video"], + "outputModalities": ["video"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2 + } + }, + "ownedBy": "google" + }, + { + "id": "ernie-5-0-thinking-exp", + "description": "ERNIE 5.0 is the next-generation natively multimodal foundation model in the ERNIE family. Built on a unified multimodal architecture, it jointly learns from text, images, audio, and video to deliver broad multimodal capabilities.\n\nERNIE 5.0 features significantly upgraded core capabilities and shows strong performance across benchmarks, with notable gains in multimodal understanding, instruction following, creative writing, factual accuracy, and agent planning with tool use.", + "capabilities": ["reasoning"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 119000, + "maxOutputTokens": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.82192 + }, + "output": { + "currency": "USD", + "perMillionTokens": 3.28768 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.82192 + } + }, + "ownedBy": "baidu" + }, + { + "id": "router", + "name": "Switchpoint Router", + "description": "Switchpoint AI's router instantly analyzes your request and directs it to the optimal AI from an ever-evolving library. \n\nAs the world of LLMs advances, our router gets smarter, ensuring you always benefit from the industry's newest models without changing your workflow.\n\nThis model is configured for a simple, flat rate per response here on OpenRouter. It's powered by the full routing engine from [Switchpoint AI](https://www.switchpoint.dev).", + "capabilities": ["reasoning"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131072, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.85 + }, + "output": { + "currency": "USD", + "perMillionTokens": 3.4 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.1 + } + } + }, + { + "id": "gemini-2-5-pro-preview-03-25", + "description": "Supports high concurrency. \nThe Gemini 2.5 Pro preview version is here, with higher limits for production testing. \nGoogle's latest and most powerful model;", + "capabilities": ["reasoning", "function-call", "structured-output", "image-recognition", "web-search"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.25 + }, + "output": { + "currency": "USD", + "perMillionTokens": 10 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.125 + } + }, + "reasoning": { + "supportedEfforts": ["low", "medium", "high"], + "thinkingTokenLimits": { + "min": 128, + "max": 32768 + } + }, + "ownedBy": "google" + }, + { + "id": "qwen3-coder-plus-2025-07-22", + "description": "The code generation model based on Qwen3 has powerful Coding Agent capabilities, excels in tool invocation and environment interaction, and can achieve autonomous programming with outstanding coding abilities while also possessing general capabilities.The model adopts tiered pricing.", + "capabilities": ["function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 65536, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.54 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2.16 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.54 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "imagen-4-0-ultra-generate-exp-05-20", + "description": "Image 4.0 Beta version, for testing purposes only. For production environment, it is recommended to use imagen-4.0-generate-preview-05-20.", + "capabilities": ["image-generation"], + "inputModalities": ["text", "image"], + "outputModalities": ["image"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2 + } + }, + "ownedBy": "google" + }, + { + "id": "jina-embeddings-v5-text-nano", + "description": "A 3.8-billion-parameter general vector model (embedding model) for state-of-the-art multilingual embeddings for edge deployment.", + "capabilities": ["embedding"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.05 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.05 + } + }, + "ownedBy": "jina" + }, + { + "id": "jina-embeddings-v5-text-small", + "description": "A 3.8-billion-parameter general vector (embedding) model providing state-of-the-art multilingual embeddings with task-specific adapters.", + "capabilities": ["embedding"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.05 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.05 + } + }, + "ownedBy": "jina" + }, + { + "id": "doubao-seedream-4-0", + "description": "Seedream 4.0 is a SOTA-level multimodal image creation model based on leading architecture. It breaks the creative boundaries of traditional text-to-image models by natively supporting text, single image, and multiple image inputs. Users can freely combine text and images to achieve various creative styles within the same model, such as multi-image fusion creation based on subject consistency, image editing, and set image generation, making image creation more flexible and controllable.\nSeedream 4.0 supports composite editing with up to 10 images in a single input. Through deep reasoning of prompt words, it automatically adapts the optimal image aspect ratio and generation quantity, enabling continuous output of up to 15 content-related images at one time. Additionally, the model significantly improves the accuracy and content diversity of Chinese generation, supports 4K ultra-high-definition output, and provides a one-stop solution from generation to editing for professional image creation.", + "capabilities": ["image-generation", "function-call"], + "inputModalities": ["text", "image"], + "outputModalities": ["image"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "ownedBy": "bytedance" + }, + { + "id": "embedding-v1", + "description": "Embedding-V1 is a text representation model based on Baidu's Wenxin large model technology, capable of converting text into numerical vector forms for applications such as text retrieval, information recommendation, and knowledge mining. Embedding-V1 provides an Embeddings interface that generates corresponding vector representations based on the input content. By calling this interface, you can input text into the model and obtain the corresponding vector representations for subsequent text processing and analysis.", + "capabilities": ["embedding"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.068 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.068 + } + } + }, + { + "id": "ernie-4-5-turbo-latest", + "description": "Wenxin 4.5 Turbo also has significant improvements in hallucination reduction, logical reasoning, and coding capabilities. Compared to Wenxin 4.5, it is faster and more affordable.", + "capabilities": ["function-call", "structured-output"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 135000, + "maxOutputTokens": 12000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.11 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.44 + } + }, + "ownedBy": "baidu" + }, + { + "id": "ernie-irag-edit", + "description": "Baidu's self-developed ERNIE iRAG Edit image editing model supports operations based on images such as erase (object removal), repaint (object redrawing), and variation (variant generation).", + "capabilities": ["function-call", "structured-output", "image-generation"], + "inputModalities": ["text", "image"], + "outputModalities": ["image"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "ownedBy": "baidu" + }, + { + "id": "glm-4-5-x", + "description": "GLM-4.5-X is the high-speed version of GLM-4.5, offering powerful performance with a generation speed of up to 100 tokens per second.", + "capabilities": ["function-call", "reasoning"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2.2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 8.91 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.44 + } + }, + "reasoning": { + "supportedEfforts": ["none"], + "interleaved": true + }, + "ownedBy": "zhipu" + }, + { + "id": "gme-qwen2-vl-instruct", + "description": "The GME-Qwen2VL series is a unified multimodal Embedding model trained based on the Qwen2-VL multimodal large language model (MLLMs). The GME model supports three types of inputs: text, images, and image-text pairs. All these input types can generate universal vector representations and exhibit excellent retrieval performance.", + "capabilities": ["function-call", "image-recognition"], + "inputModalities": ["text", "image", "video"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.138 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.138 + } + } + }, + { + "id": "gte-rerank-v2", + "description": "gte-rerank-v2 is a multilingual unified text ranking model developed by Tongyi Lab, covering multiple major languages worldwide and providing high-quality text ranking services. It is typically used in scenarios such as semantic retrieval and RAG, and can simply and effectively improve text retrieval performance. Given a query and a set of candidate texts (documents), the model ranks the candidates from highest to lowest based on their semantic relevance to the query.", + "capabilities": ["embedding", "rerank"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.11 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.11 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "irag-1-0", + "description": "Baidu's self-developed ERNIE iRAG (ERNIE image-based RAG), a retrieval-augmented text-to-image technology, combines Baidu Search's hundreds of millions of image resources with powerful foundational model capabilities to generate various ultra-realistic images. The overall effect far surpasses native text-to-image systems, eliminating the typical AI feel while maintaining low costs. ERNIE iRAG features no hallucinations, ultra-realism, and instant usability.", + "capabilities": ["image-generation"], + "inputModalities": ["text"], + "outputModalities": ["image"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + } + }, + { + "id": "jina-deepsearch-v1", + "description": "DeepSearch combines search, reading, and reasoning capabilities to pursue the best possible answer. It's fully compatible with OpenAI's Chat API format—just replace api.openai.com with aihubmix.com to get started. \nThe stream will return the thinking process.", + "capabilities": ["reasoning", "web-search"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 1000000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.05 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.05 + } + }, + "ownedBy": "jina" + }, + { + "id": "jina-embeddings-v4", + "description": "A general-purpose vector model with 3.8 billion parameters, used for multimodal and multilingual retrieval, supporting both unidirectional and multi-vector embedding outputs.", + "capabilities": ["embedding"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.05 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.05 + } + }, + "ownedBy": "jina" + }, + { + "id": "jina-reranker-v3", + "description": "Multimodal multilingual document reranker, 131K context, 0.6B parameters, for visual document sorting.", + "capabilities": ["rerank"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 131000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.05 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.05 + } + }, + "ownedBy": "jina" + }, + { + "id": "qwen-image", + "description": "Qwen-Image is a foundational image generation model in the Qwen series, achieving significant progress in complex text rendering and precise image editing. Experiments show that the model has strong general capabilities in image generation and editing, especially excelling in Chinese text rendering.", + "capabilities": ["image-generation", "function-call"], + "inputModalities": ["text", "image"], + "outputModalities": ["image"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "qwen-image-edit", + "description": "Qwen-Image-Edit is the image editing version of Qwen-Image. Based on the 20B Qwen-Image model, Qwen-Image-Edit successfully extends Qwen-Image's unique text rendering capabilities to image editing tasks, achieving precise text editing. Additionally, Qwen-Image-Edit can input the same image into Qwen2.5-VL (for visual semantic control) and the VAE encoder (for visual appearance control), enabling both semantic and appearance editing functionalities.", + "capabilities": ["image-generation", "function-call"], + "inputModalities": ["text", "image"], + "outputModalities": ["image"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "qwen-image-plus", + "description": "Qwen-Image is a foundational image generation model in the Qwen series, achieving significant progress in complex text rendering and precise image editing. Experiments show that the model has strong general capabilities in image generation and editing, especially excelling in Chinese text rendering.", + "capabilities": ["image-generation", "function-call"], + "inputModalities": ["text", "image"], + "outputModalities": ["image"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "qwen3-reranker", + "description": "Based on the dense foundational model of the Qwen3 series, it is specifically designed for ranking tasks. It inherits the base model’s outstanding multilingual capabilities, long-text understanding, and reasoning skills, achieving significant advancements in ranking tasks.", + "capabilities": ["function-call", "rerank", "reasoning"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 16000, + "maxOutputTokens": 8000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.11 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.11 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 38912 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "tao-8k", + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.068 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.068 + } + } + }, + { + "id": "bce-reranker-base", + "description": "Based on the dense foundational model of the Qwen3 series, it is specifically designed for ranking tasks. It inherits the base model’s outstanding multilingual capabilities, long-text understanding, and reasoning skills, achieving significant advancements in ranking tasks.", + "capabilities": ["rerank"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.068 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.068 + } + } + }, + { + "id": "jina-clip-v2", + "description": "Multi-modal Embeddings Model, multilingual, 1024-dimensional, 865M parameters.", + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.05 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.05 + } + }, + "ownedBy": "jina" + }, + { + "id": "jina-reranker-m0", + "description": "Multimodal multilingual document reranker, 10K context, 2.4B parameters, for visual document sorting.", + "capabilities": ["rerank"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.05 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.05 + } + }, + "ownedBy": "jina" + }, + { + "id": "jina-colbert-v2", + "description": "Multi-language ColBERT embeddings model, 560M parameters, used for embedding and reranking.", + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.05 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.05 + } + }, + "ownedBy": "jina" + }, + { + "id": "gpt-4o-search-preview", + "name": "OpenAI: GPT-4o Search Preview", + "description": "GPT-4o Search Previewis a specialized model for web search in Chat Completions. It is trained to understand and execute web search queries.", + "capabilities": ["structured-output", "web-search", "function-call", "image-recognition"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 16384, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2.5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 10 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 1.25 + } + }, + "ownedBy": "openai" + }, + { + "id": "jina-embeddings-v3", + "description": "Text Embeddings Model, multilingual, 1024-dimensional, 570M parameters.", + "capabilities": ["embedding"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.05 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.05 + } + }, + "ownedBy": "jina" + }, + { + "id": "ernie-4-5", + "description": "Wenxin Large Model 4.5 is a next-generation native multimodal foundational model independently developed by Baidu. It achieves collaborative optimization through joint modeling of multiple modalities, demonstrating excellent multimodal understanding capabilities; it possesses more advanced language abilities, with comprehensive improvements in comprehension, generation, logic, and memory, as well as significant enhancements in hallucination reduction, logical reasoning, and coding capabilities.ERNIE-4.5-21B-A3B is an aligned open-source model with a MoE structure, having a total of 21 billion parameters and 3 billion activated parameters.", + "capabilities": ["function-call", "structured-output"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 160000, + "maxOutputTokens": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.068 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.272 + } + }, + "ownedBy": "baidu" + }, + { + "id": "ernie-4-5-turbo-vl", + "description": "The new version of the Wenxin Yiyan large model significantly improves capabilities in image understanding, creation, translation, and coding. It supports a context length of up to 32K tokens for the first time, with a notable reduction in the latency of the first token.", + "capabilities": ["function-call", "structured-output", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 139000, + "maxOutputTokens": 16000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.4 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.2 + } + }, + "ownedBy": "baidu" + }, + { + "id": "gemini-2-0-flash-preview-image-generation", + "description": "Gemini 2.0 Flash EXP is the official preview version of the drawing model. Compared to Imagen 3.0, Gemini’s image generation is better suited for scenarios that require contextual understanding and reasoning, rather than the pursuit of ultimate artistic performance and visual quality.", + "capabilities": ["image-generation", "function-call", "image-recognition", "web-search"], + "inputModalities": ["text", "image"], + "outputModalities": ["image"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.1 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.4 + } + }, + "ownedBy": "google" + }, + { + "id": "flux.1-kontext-pro", + "description": "Generate and edit images through both text and image prompts. Flux.1 Kontext is a multimodal flow matching model that enables both text-to-image generation and in-context image editing. Modify images while maintaining character consistency and performing local edits up to 8x faster than other leading models.", + "capabilities": ["image-generation"], + "inputModalities": ["text", "image"], + "outputModalities": ["image"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 40 + }, + "output": { + "currency": "USD", + "perMillionTokens": 40 + } + }, + "ownedBy": "bfl" + }, + { + "id": "flux-1-1-pro", + "description": "FLUX-1.1-pro is an AI image generation tool for professional creators and content workflows. It understands complex semantic and structural instructions to deliver high consistency, multi-image coherence, and style customization from text prompts.", + "capabilities": ["image-generation"], + "inputModalities": ["text", "image"], + "outputModalities": ["image"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 40 + }, + "output": { + "currency": "USD", + "perMillionTokens": 40 + } + }, + "ownedBy": "bfl" + }, + { + "id": "doubao-seed-1-6-lite", + "description": "Doubao-Seed-1.6-lite is a brand new multimodal deep reasoning model that supports adjustable reasoning effort, with four modes: Minimal, Low, Medium, and High. It offers better cost performance, making it the best choice for common tasks, with a context window of up to 256k.", + "capabilities": ["function-call", "structured-output", "reasoning", "image-recognition"], + "inputModalities": ["text", "image", "video"], + "outputModalities": ["text"], + "contextWindow": 256000, + "maxOutputTokens": 32000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.082 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.656 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.0164 + } + }, + "ownedBy": "bytedance" + }, + { + "id": "qwen-3-a22b-thinking-2507", + "description": "cerebras", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.28 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2.8 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "qwen2-vl-instruct", + "description": "The model provider is the Sophnet platform. Qwen2-VL-72B-Instruct is the latest iteration in the Qwen2-VL series launched by Alibaba Cloud, representing nearly a year of innovative achievements. This model has 72 billion parameters and can understand images of various resolutions and aspect ratios. Additionally, it supports video understanding of over 20 minutes, enabling high-quality video question answering, dialogue, and content creation, along with complex reasoning and decision-making capabilities.\n\n- State-of-the-art image understanding: capable of processing images of various resolutions and aspect ratios, performing excellently across multiple visual understanding benchmarks.\n- Long video understanding: supports video comprehension exceeding 20 minutes, enabling high-quality video Q&A, dialogues, and content creation.\n- Agent operation capability: equipped with complex reasoning and decision-making abilities, it can integrate with devices such as phones and robots to perform automated operations based on visual environments and textual instructions.\n- Multilingual support: in addition to English and Chinese, it supports understanding text in images in multiple languages, including most European languages, Japanese, Korean, Arabic, Vietnamese, and more.\n- Supports a maximum context length of 128K tokens, offering powerful processing capabilities.", + "capabilities": ["function-call", "image-recognition"], + "inputModalities": ["text", "image", "video"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2.18 + }, + "output": { + "currency": "USD", + "perMillionTokens": 6.54 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "embedding-2", + "description": "A text vector model that converts input text information into vector representations so that, in conjunction with a vector database, it provides an external knowledge base for the large model, thereby improving the accuracy of the model’s reasoning.", + "capabilities": ["embedding"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 8000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.0686 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.0686 + } + } + }, + { + "id": "embedding-3", + "description": "A text vector model that converts input text into vector representations to work with a vector database and provide an external knowledge base for a large model. The model supports custom vector dimensions; it is recommended to choose 256, 512, 1024, or 2048 dimensions.", + "capabilities": ["embedding"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 8000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.0686 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.0686 + } + } + }, + { + "id": "doubao-seed-1-6-250615", + "description": "Doubao-Seed-1.6 is a brand new multimodal deep reasoning model that supports four types of reasoning effort: minimal, low, medium, and high. It offers stronger model performance, serving complex tasks and challenging scenarios. It supports a 256k context window, with output length up to a maximum of 32k tokens.", + "capabilities": ["reasoning", "function-call", "image-recognition"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.18 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2.52 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.036 + } + }, + "ownedBy": "bytedance" + }, + { + "id": "doubao-seed-1-6-flash-250615", + "description": "Doubao-Seed-1.6-flash is an extremely fast multimodal deep thinking model, with TPOT requiring only 10ms. It supports both text and visual understanding, with its text comprehension skills surpassing the previous generation lite model and its visual understanding on par with competitor's pro series models. It supports a 256k context window and an output length of up to 16k tokens.", + "capabilities": ["reasoning", "function-call", "image-recognition"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.044 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.44 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.0088 + } + }, + "ownedBy": "bytedance" + }, + { + "id": "doubao-seed-1-6-thinking-250615", + "description": "The Doubao-Seed-1.6-thinking model has significantly enhanced reasoning capabilities. Compared with Doubao-1.5-thinking-pro, it has further improvements in fundamental abilities such as coding, mathematics, and logical reasoning, and now also supports visual understanding. It supports a 256k context window, with output length supporting up to 16k tokens.", + "capabilities": ["reasoning", "function-call", "image-recognition"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.18 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2.52 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.036 + } + }, + "ownedBy": "bytedance" + }, + { + "id": "gpt-4o-image-vip", + "description": "First Taste of GPT-4o's Image Generation API: Perfectly mirrors the web version's raw image creation capabilities, supporting both text-to-image and image+text-to-image generation. Each creation costs as little as $0.009.", + "capabilities": ["image-generation", "function-call"], + "inputModalities": ["text", "image"], + "outputModalities": ["image"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 7 + }, + "output": { + "currency": "USD", + "perMillionTokens": 7 + } + }, + "ownedBy": "openai" + }, + { + "id": "gpt-4o-image", + "description": "First Taste of GPT-4o's Image Generation API: Perfectly mirrors the web version's raw image creation capabilities, supporting both text-to-image and image+text-to-image generation. Each creation costs as little as $0.005.", + "capabilities": ["image-generation", "function-call"], + "inputModalities": ["text", "image"], + "outputModalities": ["image"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 3 + } + }, + "ownedBy": "openai" + }, + { + "id": "gpt-4o-mini-tts", + "description": "OpenAI’s latest TTS model, gpt-4o-mini-tts, uses the same API endpoint (/v1/audio/speech) as tts-1. However, OpenAI introduced a new pricing method without providing billing details via API, causing discrepancies between official pricing and aihubmix’s charges—some requests may cost more, others less. Avoid using this model if precise billing accuracy is essential.", + "capabilities": ["function-call", "image-recognition", "web-search"], + "inputModalities": ["audio"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.6 + }, + "output": { + "currency": "USD", + "perMillionTokens": 12 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.6 + } + }, + "ownedBy": "openai" + }, + { + "id": "gemini-2-0-flash-thinking-exp-01-21", + "description": "The latest version, Gemini 2.0 Flash Thinking mode, is an experimental model designed to generate the \"thought process\" that the model goes through during its responses. Therefore, Gemini 2.0 Flash Thinking mode has stronger reasoning capabilities in its responses compared to the base Gemini 2.0 Flash model.", + "capabilities": ["reasoning", "function-call", "image-recognition", "web-search"], + "inputModalities": ["text", "image", "audio", "video"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.076 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.304 + } + }, + "ownedBy": "google" + }, + { + "id": "ernie-x1-1-preview", + "description": "The Wenxin large model X1.1 has made significant improvements in question answering, tool invocation, intelligent agents, instruction following, logical reasoning, mathematics, and coding tasks, with notable enhancements in factual accuracy. The context length has been extended to 64K tokens, supporting longer inputs and dialogue history, which improves the coherence of long-chain reasoning while maintaining response speed.", + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 119000, + "maxOutputTokens": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.136 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.544 + } + }, + "ownedBy": "baidu" + }, + { + "id": "bge-large-en", + "description": "bge-large-en, open-sourced by the Beijing Academy of Artificial Intelligence (BAAI), is currently the most powerful vector representation model for Chinese tasks, with its semantic representation capabilities comprehensively surpassing those of similar open-source models.", + "capabilities": ["function-call", "structured-output", "embedding"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.068 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.068 + } + }, + "ownedBy": "baai" + }, + { + "id": "bge-large-zh", + "description": "bge-large-zh, open-sourced by the Beijing Academy of Artificial Intelligence (BAAI), is currently the most powerful vector representation model for Chinese tasks, with its semantic representation capabilities comprehensively surpassing those of similar open-source models.", + "capabilities": ["function-call", "structured-output", "embedding"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.068 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.068 + } + }, + "ownedBy": "baai" + }, + { + "id": "ernie-4-5-turbo-128k-preview", + "description": "Wenxin 4.5 Turbo also shows significant enhancements in reducing hallucinations, logical reasoning, and coding capabilities. Compared to Wenxin 4.5, it is faster and more cost-effective.", + "capabilities": ["function-call", "structured-output"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.108 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.432 + } + }, + "ownedBy": "baidu" + }, + { + "id": "ernie-x1-turbo", + "description": "Wenxin Large Model X1 possesses enhanced abilities in understanding, planning, reflection, and evolution. As a more comprehensive deep-thinking model, Wenxin X1 combines accuracy, creativity, and literary elegance, excelling particularly in Chinese knowledge Q&A, literary creation, document writing, daily conversations, logical reasoning, complex calculations, and tool invocation.", + "capabilities": ["reasoning", "function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 50500, + "maxOutputTokens": 28000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.136 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.544 + } + }, + "ownedBy": "baidu" + }, + { + "id": "moonlight-a3b-instruct", + "description": "Provided by chutes.ai.", + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.2 + } + } + }, + { + "id": "o1-global", + "description": "OpenAI new model", + "capabilities": ["reasoning", "function-call", "image-recognition"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 15 + }, + "output": { + "currency": "USD", + "perMillionTokens": 60 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 7.5 + } + }, + "reasoning": { + "supportedEfforts": ["low", "medium", "high"] + }, + "ownedBy": "openai" + }, + { + "id": "qianfan-qi-vl", + "description": "The Qianfan-QI-VL model is a proprietary image quality inspection and visual understanding large model (Quality Inspection Large Vision Language Model, Qianfan-QI-VL) developed by Baidu Cloud’s Qianfan platform. It is designed for quality inspection of product images uploaded in e-commerce scenarios, with detection capabilities including AIGC human defect detection, mosaic recognition, watermark recognition, and trademark detection.", + "capabilities": ["image-recognition"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.6 + } + } + }, + { + "id": "gemini-exp-1206", + "description": "Google's latest experimental model, currently Google's most powerful model.", + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.25 + }, + "output": { + "currency": "USD", + "perMillionTokens": 5 + } + }, + "ownedBy": "google" + }, + { + "id": "gpt-4o-zh", + "capabilities": ["function-call", "image-recognition", "web-search"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2.5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 10 + } + }, + "ownedBy": "openai" + }, + { + "id": "qwen-max-0125", + "description": "Qwen 2.5-Max latest model", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.38 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.52 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "bge-large-zh-v1-5", + "description": "BAAI/bge-large-zh-v1.5 is a large Chinese text embedding model and part of the BGE (BAAI General Embedding) series. It performs excellently on the C-MTEB benchmark, achieving an average score of 64.53 across 31 datasets, with outstanding results in tasks such as retrieval, semantic similarity, and text pair classification. The model supports a maximum input length of 512 tokens and is suitable for various Chinese natural language processing tasks, such as text retrieval and semantic similarity computation.", + "capabilities": ["function-call", "structured-output", "embedding"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.034 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.034 + } + }, + "ownedBy": "baai" + }, + { + "id": "gemini-2-0-flash-lite-preview-02-05", + "description": "Gemini 2.0 Flash lightweight version", + "capabilities": ["function-call", "image-recognition", "web-search"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.075 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.3 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.075 + } + }, + "ownedBy": "google" + }, + { + "id": "v3", + "description": "Fast and high-quality — top image quality in just 11 seconds per piece, with almost no extra time for batch generation.\nFlexible ratios — supports ultra-wide and tall formats like 3:1, 2:1, offering diverse perspectives.\nUnique strengths — outstanding design capabilities in the V3 and V2 series, with powerful text rendering (Chinese support coming soon).\nPrecise local editing — fine-tuned mask control for area redrawing (edit) and easy background replacement (replace-background).", + "capabilities": ["image-generation"], + "inputModalities": ["text", "image"], + "outputModalities": ["image"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2 + } + } + }, + { + "id": "v_2", + "description": "The Ideogram AI drawing interface is now live. This model boasts powerful text-to-image capabilities, supporting endpoints are: /generate, /remix, /edit.\nThis model is the stable V_2 version, highly recommended for editing.\nUS $0.08/ 1 IMG.\nFor usage examples and pricing details, refer to the documentation at https://docs.aihubmix.com/cn/api/IdeogramAI.", + "capabilities": ["image-generation"], + "inputModalities": ["text", "image"], + "outputModalities": ["image"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2 + } + } + }, + { + "id": "v_2_turbo", + "description": "The Ideogram AI drawing interface is now live. This model boasts powerful text-to-image capabilities, supporting endpoints are: /generate, /remix, /edit.\nThis model is the fast version of V_2, offering increased speed at the slight expense of quality.\nUS $0.05/ IMG.\nFor usage examples and pricing details, refer to the documentation at https://docs.aihubmix.com/cn/api/IdeogramAI.", + "capabilities": ["image-generation"], + "inputModalities": ["text", "image"], + "outputModalities": ["image"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2 + } + } + }, + { + "id": "v_2a", + "description": "The Ideogram AI drawing interface is now live. This model boasts powerful text-to-image capabilities, supporting endpoints are: /generate, /remix.\nThis model is the fast version of V_2, faster and cheaper.\nUS $0.04/ IMG.\nFor usage examples and pricing details, refer to the documentation at https://docs.aihubmix.com/cn/api/IdeogramAI.", + "capabilities": ["image-generation"], + "inputModalities": ["text", "image"], + "outputModalities": ["image"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2 + } + } + }, + { + "id": "v_2a_turbo", + "description": "The Ideogram AI drawing interface is now live. This model boasts powerful text-to-image capabilities, supporting endpoints are: /generate, /remix.\nThis model is the ultra-fast version of V_2, delivering the highest speed while slightly reducing quality.\nUS $0.025/ IMG.\nFor usage examples and pricing details, refer to the documentation at https://docs.aihubmix.com/cn/api/IdeogramAI.", + "capabilities": ["image-generation"], + "inputModalities": ["text", "image"], + "outputModalities": ["image"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2 + } + } + }, + { + "id": "v_1", + "description": "V_1 is a text-to-image model in the Ideogram series. It delivers strong text rendering capabilities, high photorealistic image quality, and precise prompt adherence. The model also introduces Magic Prompt, a new feature that automatically refines input prompts to generate more detailed and creative visuals.", + "capabilities": ["image-generation"], + "inputModalities": ["text", "image"], + "outputModalities": ["image"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2 + } + } + }, + { + "id": "v_1_turbo", + "description": "The Ideogram AI drawing interface is now live. This model boasts powerful text-to-image capabilities, supporting endpoints are: /generate, /remix.\nThis model is the ultra-fast version of the original V_1, offering increased speed at the slight expense of quality.\nUS $0.02/ IMG.\nFor usage examples and pricing details, refer to the documentation at https://docs.aihubmix.com/cn/api/IdeogramAI.", + "capabilities": ["image-generation"], + "inputModalities": ["text", "image"], + "outputModalities": ["image"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2 + } + } + }, + { + "id": "doubao-embedding-large-text-240915", + "description": "doubao-embedding-large-text-240915\nDoubao Embedding is a semantic vectorization model developed by ByteDance, primarily designed for vector search scenarios. It supports both Chinese and English languages and has a maximum context length of approximately 4K tokens.", + "capabilities": ["embedding"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.1 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.1 + } + }, + "ownedBy": "bytedance" + }, + { + "id": "kimi-thinking-preview", + "description": "The latest kimi model.", + "capabilities": ["reasoning", "image-recognition"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 30 + }, + "output": { + "currency": "USD", + "perMillionTokens": 30 + } + }, + "ownedBy": "moonshot" + }, + { + "id": "qwen-plus-2025-07-28", + "name": "Qwen: Qwen Plus 0728", + "description": "Qwen Plus 0728, based on the Qwen3 foundation model, is a 1 million context hybrid reasoning model with a balanced performance, speed, and cost combination.", + "capabilities": ["function-call", "structured-output", "reasoning"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 1000000, + "maxOutputTokens": 32768, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.39999999999999997 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.2 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.11 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 0, + "max": 81920 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "qwen-plus-latest", + "description": "The Qwen series models with balanced capabilities have inference performance and speed between Qwen-Max and Qwen-Turbo, making them suitable for moderately complex tasks. This model is a dynamically updated version, and updates will not be announced in advance. The current version is qwen-plus-2025-04-28.The model adopts tiered pricing.", + "capabilities": ["function-call", "reasoning"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.11 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.275 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.11 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 0, + "max": 81920 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "step3", + "description": "Step3 is a multimodal reasoning model released by StepFun. It uses a Mixture‑of‑Experts (MoE) architecture with 321 billion total parameters and 38 billion activation parameters. The model follows an end‑to‑end design that reduces decoding cost while delivering top‑tier performance on vision‑language reasoning tasks. Thanks to the combined use of Multi‑Head Factorized Attention (MFA) and Attention‑FFN Decoupling (AFD), Step3 remains highly efficient on both flagship and low‑end accelerators. During pre‑training, it processed over 20 trillion text tokens and 4 trillion image‑text mixed tokens, covering more than ten languages. On benchmarks for mathematics, code, and multimodal tasks, Step3 consistently outperforms other open‑source models.", + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.1 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2.75 + } + }, + "ownedBy": "stepfun" + }, + { + "id": "text-embedding-v4", + "description": "This is the Tongyi Laboratory's multilingual unified text vector model trained based on Qwen3, which significantly improves performance in text retrieval, clustering, and classification compared to version V3; it achieves a 15% to 40% improvement on evaluation tasks such as MTEB multilingual, Chinese-English, and code retrieval; supports user-defined vector dimensions ranging from 64 to 2048.", + "capabilities": ["embedding"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.08 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.08 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "qwen-turbo-latest", + "description": "The Qwen series model with the fastest speed and lowest cost, suitable for simple tasks. This model is a dynamically updated version, and updates will not be announced in advance. The model's overall Chinese and English abilities have been significantly improved, human preference alignment has been greatly enhanced, inference capability and complex instruction understanding have been substantially strengthened, performance on difficult tasks is better, and mathematics and coding skills have been significantly improved. The current version is qwen-turbo-2025-04-28.", + "capabilities": ["function-call", "reasoning"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.046 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.92 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 0, + "max": 38912 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "doubao-embedding-text-240715", + "description": "doubao-embedding-text-240715\nDoubao Embedding is a semantic vectorization model developed by ByteDance, primarily designed for vector search scenarios. It supports both Chinese and English languages and has a maximum context length of approximately 4K tokens.", + "capabilities": ["embedding"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.7 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.7 + } + }, + "ownedBy": "bytedance" + }, + { + "id": "deepseek-r1-zero", + "description": "Openly deployed by chutes.ai; inference with FP8; zero is the initial preliminary version of R1 without optimizations and is not recommended for use unless for research purposes.", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2.2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2.2 + } + }, + "ownedBy": "deepseek" + }, + { + "id": "grok-3-fast-beta", + "capabilities": ["function-call", "web-search"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 5.5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 27.5 + } + }, + "ownedBy": "xai" + }, + { + "id": "qwen-turbo-2025-04-28", + "description": "The Qwen3 series Turbo model effectively integrates thinking and non-thinking modes, allowing seamless switching between modes during conversations. With a smaller parameter size, its reasoning ability rivals that of QwQ-32B, and its general capabilities significantly surpass those of Qwen2.5-Turbo, reaching state-of-the-art (SOTA) levels among models of the same scale. This version is a snapshot model as of April 28, 2025.", + "capabilities": ["function-call", "reasoning"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.046 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.92 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 0, + "max": 38912 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "grok-3-mini-fast-beta", + "capabilities": ["reasoning", "function-call", "web-search"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.33 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2.20011 + } + }, + "reasoning": { + "supportedEfforts": ["low", "high"] + }, + "ownedBy": "xai" + }, + { + "id": "qwen-plus-2025-04-28", + "description": "The Qwen3 series Plus model effectively integrates thinking and non-thinking modes, allowing for mode switching during conversations. Its reasoning abilities significantly surpass those of QwQ, and its general capabilities are markedly superior to Qwen2.5-Plus, reaching state-of-the-art (SOTA) levels among models of the same scale. This version is a snapshot model as of April 28, 2025.", + "capabilities": ["function-call", "reasoning"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.13 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2.6 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 0, + "max": 38912 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "glm-4-1v", + "description": "GLM-4.1V-9B-Thinking is an open-source Vision Language Model (VLM) jointly released by Zhipu AI and the KEG Laboratory at Tsinghua University, designed specifically for handling complex multimodal cognitive tasks. Based on the GLM-4-9B-0414 foundation model, it significantly enhances cross-modal reasoning ability and stability by introducing the “Chain-of-Thought” reasoning mechanism and using reinforcement learning strategies. As a lightweight model with 9 billion parameters, it strikes a balance between deployment efficiency and performance. In 28 authoritative benchmark evaluations, it matched or even outperformed the 72-billion-parameter Qwen-2.5-VL-72B model in 18 tasks. The model excels not only in image-text understanding, mathematical and scientific reasoning, and video understanding, but also supports images up to 4K resolution and inputs of arbitrary aspect ratios.", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.04 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.16 + } + }, + "ownedBy": "zhipu" + }, + { + "id": "text-embedding-004", + "capabilities": ["embedding"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.02 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.02 + } + }, + "ownedBy": "google" + }, + { + "id": "doubao-seed-code-preview-latest", + "description": "claude code ", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.2 + } + }, + "reasoning": { + "supportedEfforts": ["none", "high"] + }, + "ownedBy": "bytedance" + }, + { + "id": "glm-zero-preview", + "description": "Simply put, it is the intelligent enhanced version of O1.", + "capabilities": ["reasoning"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2 + } + }, + "ownedBy": "zhipu" + }, + { + "id": "janus-pro", + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2 + } + } + }, + { + "id": "gemini-2-0-flash-thinking-exp-1219", + "description": "The Gemini 2.0 Flash Thinking mode is an experimental model designed to generate the \"thinking process\" that the model undergoes during its response. Therefore, the Gemini 2.0 Flash Thinking mode possesses stronger reasoning capabilities in its responses compared to the base Gemini 2.0 Flash model.", + "capabilities": ["reasoning", "function-call", "image-recognition", "web-search"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.076 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.304 + } + }, + "ownedBy": "google" + }, + { + "id": "o1-preview-2024-09-12", + "capabilities": ["reasoning"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 15 + }, + "output": { + "currency": "USD", + "perMillionTokens": 60 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 7.5 + } + }, + "reasoning": { + "supportedEfforts": ["low", "medium", "high"] + }, + "ownedBy": "openai" + }, + { + "id": "qvq-preview", + "capabilities": ["reasoning"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.2 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "qwq-preview", + "capabilities": ["reasoning"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.16 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.16 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "llama-3-1-sonar-huge-128k", + "description": "On February 22, 2025, this model will be officially discontinued. The Perplexity AI official fine-tuned LLMA internet-connected interface is currently only supported at the api.aihubmix.com address.", + "capabilities": ["web-search"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 5.6 + }, + "output": { + "currency": "USD", + "perMillionTokens": 5.6 + } + }, + "ownedBy": "meta" + }, + { + "id": "llama-3-1-sonar-large-128k", + "description": "On February 22, 2025, this model will be officially discontinued; Perplexity AI's official fine-tuned LLMA internet-connected interface is currently only supported at the api.aihubmix.com address.", + "capabilities": ["web-search"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.2 + } + }, + "ownedBy": "meta" + }, + { + "id": "mistral-large-2407", + "name": "Mistral Large 2407", + "description": "This is Mistral AI's flagship model, Mistral Large 2 (version mistral-large-2407). It's a proprietary weights-available model and excels at reasoning, code, JSON, chat, and more. Read the launch announcement [here](https://mistral.ai/news/mistral-large-2407/).\n\nIt supports dozens of languages including French, German, Spanish, Italian, Portuguese, Arabic, Hindi, Russian, Chinese, Japanese, and Korean, along with 80+ coding languages including Python, Java, C, C++, JavaScript, and Bash. Its long context window allows precise information recall from large documents.\n", + "capabilities": ["function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131072, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 6 + } + }, + "ownedBy": "mistral" + }, + { + "id": "gemini-2-0-flash-thinking-exp", + "capabilities": ["reasoning", "function-call", "image-recognition", "web-search"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.076 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.304 + } + }, + "ownedBy": "google" + }, + { + "id": "gpt-image-test", + "capabilities": ["image-generation"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 40 + } + }, + "ownedBy": "openai" + }, + { + "id": "imagen-3-0-generate-002", + "description": "Imagen 3.0 is Google's latest text-to-image generation model, capable of creating high-quality images from natural language prompts. Compared to its predecessors, Imagen 3.0 offers significant improvements in detail, lighting, and reduced visual artifacts. It supports rendering in various artistic styles, from photorealism to impressionism, as well as abstract and anime styles.", + "capabilities": ["image-generation", "video-generation"], + "inputModalities": ["text", "image"], + "outputModalities": ["image"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2 + } + }, + "ownedBy": "google" + }, + { + "id": "describe", + "description": "This endpoint is used to describe an image.\nSupported image formats include JPEG, PNG, and WebP.\nUS $0.01/ IMG.\nFor usage examples and pricing details, refer to the documentation at https://docs.aihubmix.com/cn/api/IdeogramAI.", + "capabilities": ["image-generation"], + "inputModalities": ["text", "image"], + "outputModalities": ["image"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2 + } + } + }, + { + "id": "upscale", + "description": "The super-resolution upscale interface of the Ideogram AI drawing model is designed to enlarge low-resolution images into high-resolution ones, redrawing details (with controllable similarity and detail proportions).\nUS $0.06/ IMG.\nFor usage examples and pricing details, refer to the documentation at https://docs.aihubmix.com/cn/api/IdeogramAI.", + "capabilities": ["image-generation"], + "inputModalities": ["text", "image"], + "outputModalities": ["image"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2 + } + } + }, + { + "id": "computer-use-preview", + "capabilities": ["computer-use"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 12 + } + } + }, + { + "id": "crush-glm-4-6", + "description": "just for crush", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + } + }, + { + "id": "o1-2024-12-17", + "capabilities": ["reasoning", "function-call", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 15 + }, + "output": { + "currency": "USD", + "perMillionTokens": 60 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 7.5 + } + }, + "reasoning": { + "supportedEfforts": ["low", "medium", "high"] + }, + "ownedBy": "openai" + }, + { + "id": "llama2-4096", + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.5 + } + }, + "ownedBy": "meta" + }, + { + "id": "llama2-40960", + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.5 + } + }, + "ownedBy": "meta" + }, + { + "id": "llama2-2048", + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.1 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.1 + } + }, + "ownedBy": "meta" + }, + { + "id": "llama3-8192(33)", + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2.65 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2.65 + } + }, + "ownedBy": "meta" + }, + { + "id": "llama3-groq-8192-tool-use-preview", + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.00089 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.00089 + } + }, + "ownedBy": "meta" + }, + { + "id": "llama3-chat", + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.3 + } + }, + "ownedBy": "meta" + }, + { + "id": "moonshot-kimi-k2-5", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.6 + }, + "output": { + "currency": "USD", + "perMillionTokens": 3 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.105 + } + }, + "ownedBy": "moonshot" + }, + { + "id": "moonshot-v1-128k", + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 10 + }, + "output": { + "currency": "USD", + "perMillionTokens": 10 + } + }, + "ownedBy": "moonshot" + }, + { + "id": "moonshot-v1-128k-vision-preview", + "capabilities": ["image-recognition"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 10 + }, + "output": { + "currency": "USD", + "perMillionTokens": 10 + } + }, + "ownedBy": "moonshot" + }, + { + "id": "moonshot-v1-32k", + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 4 + }, + "output": { + "currency": "USD", + "perMillionTokens": 4 + } + }, + "ownedBy": "moonshot" + }, + { + "id": "moonshot-v1-32k-vision-preview", + "capabilities": ["image-recognition"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 4 + }, + "output": { + "currency": "USD", + "perMillionTokens": 4 + } + }, + "ownedBy": "moonshot" + }, + { + "id": "moonshot-v1-8k", + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2 + } + }, + "ownedBy": "moonshot" + }, + { + "id": "moonshot-v1-8k-vision-preview", + "capabilities": ["image-recognition"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2 + } + }, + "ownedBy": "moonshot" + }, + { + "id": "o1-mini-2024-09-12", + "capabilities": ["reasoning"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 12 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 1.5 + } + }, + "reasoning": { + "supportedEfforts": ["low", "medium", "high"] + }, + "ownedBy": "openai" + }, + { + "id": "omni-moderation-latest", + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.02 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.02 + } + } + }, + { + "id": "qwen-flash-2025-07-28", + "description": "The model adopts tiered pricing.", + "capabilities": ["function-call", "reasoning"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.02 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.2 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.02 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 0, + "max": 81920 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "qwen-max-longcontext", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 7 + }, + "output": { + "currency": "USD", + "perMillionTokens": 21 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "qwen-turbo-2024-11-01", + "capabilities": ["function-call", "reasoning"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.36 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.08 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 0, + "max": 38912 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "text-ada-001", + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.4 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.4 + } + } + }, + { + "id": "text-babbage-001", + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.5 + } + } + }, + { + "id": "text-curie-001", + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2 + } + } + }, + { + "id": "text-davinci-002", + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 20 + }, + "output": { + "currency": "USD", + "perMillionTokens": 20 + } + } + }, + { + "id": "text-davinci-003", + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 20 + }, + "output": { + "currency": "USD", + "perMillionTokens": 20 + } + } + }, + { + "id": "text-davinci-edit-001", + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 20 + }, + "output": { + "currency": "USD", + "perMillionTokens": 20 + } + } + }, + { + "id": "text-embedding-v1", + "capabilities": ["embedding"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.1 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.1 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "text-moderation-007", + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.2 + } + } + }, + { + "id": "text-moderation-latest", + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.2 + } + } + }, + { + "id": "text-moderation-stable", + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.2 + } + } + }, + { + "id": "text-search-ada-doc-001", + "capabilities": ["web-search"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 20 + }, + "output": { + "currency": "USD", + "perMillionTokens": 20 + } + } + }, + { + "id": "tts-1", + "capabilities": ["audio-generation"], + "inputModalities": ["audio"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 15 + }, + "output": { + "currency": "USD", + "perMillionTokens": 15 + } + }, + "ownedBy": "openai" + }, + { + "id": "tts-1-1106", + "capabilities": ["audio-generation"], + "inputModalities": ["audio"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 15 + }, + "output": { + "currency": "USD", + "perMillionTokens": 15 + } + }, + "ownedBy": "openai" + }, + { + "id": "tts-1-hd", + "capabilities": ["audio-generation"], + "inputModalities": ["audio"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 30 + }, + "output": { + "currency": "USD", + "perMillionTokens": 30 + } + }, + "ownedBy": "openai" + }, + { + "id": "tts-1-hd-1106", + "capabilities": ["audio-generation"], + "inputModalities": ["audio"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 30 + }, + "output": { + "currency": "USD", + "perMillionTokens": 30 + } + }, + "ownedBy": "openai" + }, + { + "id": "veo3", + "description": "veo3 reverse access with a total cost of just $0.41 per video generation., OpenAI chat port compatible format.\nNote that this is a reverse interface, and charges are based on the number of requests. As long as a request is initiated, even if it returns a failure, you will be charged. If you cannot accept this, please do not use it.", + "capabilities": ["video-generation"], + "inputModalities": ["text", "image", "audio", "video"], + "outputModalities": ["video"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2 + } + }, + "ownedBy": "google" + }, + { + "id": "whisper-1", + "description": "Ignore the displayed price on the page; the actual charge for this model request is consistent with the official, so you can use it with confidence.", + "capabilities": ["audio-transcript"], + "inputModalities": ["audio"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 100 + }, + "output": { + "currency": "USD", + "perMillionTokens": 100 + } + }, + "ownedBy": "openai" + }, + { + "id": "whisper-large-v3-turbo", + "capabilities": ["audio-transcript"], + "inputModalities": ["audio"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 5.556 + }, + "output": { + "currency": "USD", + "perMillionTokens": 5.556 + } + }, + "ownedBy": "openai" + }, + { + "id": "yi-large", + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 3 + } + }, + "ownedBy": "01ai" + }, + { + "id": "yi-large-rag", + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 4 + }, + "output": { + "currency": "USD", + "perMillionTokens": 4 + } + }, + "ownedBy": "01ai" + }, + { + "id": "yi-large-turbo", + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.8 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.8 + } + }, + "ownedBy": "01ai" + }, + { + "id": "yi-lightning", + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.2 + } + }, + "ownedBy": "01ai" + }, + { + "id": "yi", + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.4 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.4 + } + } + }, + { + "id": "yi-vl-plus", + "capabilities": ["image-recognition"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.000852 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.000852 + } + }, + "ownedBy": "01ai" + }, + { + "id": "fx-flux-2-pro", + "capabilities": ["image-generation"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + } + }, + { + "id": "gemini-2-0-flash-exp-image-generation", + "capabilities": ["function-call", "image-recognition", "image-generation", "web-search"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.1 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.4 + } + }, + "ownedBy": "google" + }, + { + "id": "gemini-2-5-pro-exp-03-25", + "description": "Google’s latest experimental model, highly unstable, for experience only.\nIt boasts strong reasoning and coding capabilities, able to \"think\" before responding, enhancing performance and accuracy in complex tasks. It supports multimodal inputs (text, audio, images, video) and a 1 million token context window, suitable for advanced programming, math, and science tasks.\n\nThis means Gemini 2.5 can handle more complex problems in coding, science and math, and support more context-aware agents.", + "capabilities": ["structured-output", "function-call", "reasoning", "image-recognition", "web-search"], + "inputModalities": ["text", "image", "audio", "video"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.25 + }, + "output": { + "currency": "USD", + "perMillionTokens": 5 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.125 + } + }, + "reasoning": { + "supportedEfforts": ["low", "medium", "high"], + "thinkingTokenLimits": { + "min": 128, + "max": 32768 + } + }, + "ownedBy": "google" + }, + { + "id": "gemini-embedding-exp-03-07", + "capabilities": ["embedding"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.02 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.02 + } + }, + "ownedBy": "google" + }, + { + "id": "gemini-exp-1114", + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.25 + }, + "output": { + "currency": "USD", + "perMillionTokens": 5 + } + }, + "ownedBy": "google" + }, + { + "id": "gemini-exp-1121", + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.25 + }, + "output": { + "currency": "USD", + "perMillionTokens": 5 + } + }, + "ownedBy": "google" + }, + { + "id": "gemini-pro", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.6 + } + }, + "ownedBy": "google" + }, + { + "id": "gemini-pro-vision", + "capabilities": ["function-call", "image-recognition"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1 + } + }, + "ownedBy": "google" + }, + { + "id": "gemma-it", + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.1 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.1 + } + }, + "ownedBy": "google" + }, + { + "id": "glm-3-turbo", + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.71 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.71 + } + }, + "ownedBy": "zhipu" + }, + { + "id": "glm-4", + "name": "Z.ai: GLM 4 32B ", + "description": "GLM 4 32B is a cost-effective foundation language model.\n\nIt can efficiently perform complex tasks and has significantly enhanced capabilities in tool use, online search, and code-related intelligent tasks.\n\nIt is made by the same lab behind the thudm models.", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.09999999999999999 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.09999999999999999 + } + }, + "ownedBy": "zhipu" + }, + { + "id": "glm-4-flash", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.1 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.1 + } + }, + "ownedBy": "zhipu" + }, + { + "id": "glm-4-plus", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 8 + }, + "output": { + "currency": "USD", + "perMillionTokens": 8 + } + }, + "ownedBy": "zhipu" + }, + { + "id": "glm-4-5-airx", + "description": "GLM-4.5-AirX is the high-speed version of GLM-4.5-Air, with faster response times, specifically designed for large-scale high-speed demands.", + "capabilities": ["function-call", "reasoning"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.1 + }, + "output": { + "currency": "USD", + "perMillionTokens": 4.51 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.22 + } + }, + "reasoning": { + "supportedEfforts": ["none"], + "interleaved": true + }, + "ownedBy": "zhipu" + }, + { + "id": "glm-4v", + "capabilities": ["function-call", "image-recognition"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 14.2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 14.2 + } + }, + "ownedBy": "zhipu" + }, + { + "id": "glm-4v-plus", + "capabilities": ["function-call", "image-recognition"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2 + } + }, + "ownedBy": "zhipu" + }, + { + "id": "gpt-3-5-turbo-16k", + "name": "OpenAI: GPT-3.5 Turbo 16k", + "description": "This model offers four times the context length of gpt-3.5-turbo, allowing it to support approximately 20 pages of text in a single request at a higher cost. Training data: up to Sep 2021.", + "capabilities": ["function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 16385, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 4 + } + }, + "ownedBy": "openai" + }, + { + "id": "gpt-3-5-turbo-16k-0613", + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 4 + } + }, + "ownedBy": "openai" + }, + { + "id": "gpt-4-0125-preview", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 10 + }, + "output": { + "currency": "USD", + "perMillionTokens": 30 + } + }, + "ownedBy": "openai" + }, + { + "id": "gpt-4-0314", + "name": "OpenAI: GPT-4 (older v0314)", + "description": "GPT-4-0314 is the first version of GPT-4 released, with a context length of 8,192 tokens, and was supported until June 14. Training data: up to Sep 2021.", + "capabilities": ["function-call", "structured-output", "image-recognition"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 8191, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 30 + }, + "output": { + "currency": "USD", + "perMillionTokens": 60 + } + }, + "ownedBy": "openai" + }, + { + "id": "gpt-4-0613", + "capabilities": ["function-call", "image-recognition"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 30 + }, + "output": { + "currency": "USD", + "perMillionTokens": 60 + } + }, + "ownedBy": "openai" + }, + { + "id": "gpt-4-1106-preview", + "name": "OpenAI: GPT-4 Turbo (older v1106)", + "description": "The latest GPT-4 Turbo model with vision capabilities. Vision requests can now use JSON mode and function calling.\n\nTraining data: up to April 2023.", + "capabilities": ["function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 10 + }, + "output": { + "currency": "USD", + "perMillionTokens": 30 + } + }, + "ownedBy": "openai" + }, + { + "id": "gpt-4-32k-0314", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 60 + }, + "output": { + "currency": "USD", + "perMillionTokens": 120 + } + }, + "ownedBy": "openai" + }, + { + "id": "gpt-4-32k-0613", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 60 + }, + "output": { + "currency": "USD", + "perMillionTokens": 120 + } + }, + "ownedBy": "openai" + }, + { + "id": "gpt-4-turbo-2024-04-09", + "capabilities": ["function-call", "image-recognition"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 10 + }, + "output": { + "currency": "USD", + "perMillionTokens": 30 + } + }, + "ownedBy": "openai" + }, + { + "id": "gpt-4-turbo-preview", + "name": "OpenAI: GPT-4 Turbo Preview", + "description": "The preview GPT-4 model with improved instruction following, JSON mode, reproducible outputs, parallel function calling, and more. Training data: up to Dec 2023.\n\n**Note:** heavily rate limited by OpenAI while in preview.", + "capabilities": ["function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 10 + }, + "output": { + "currency": "USD", + "perMillionTokens": 30 + } + }, + "ownedBy": "openai" + }, + { + "id": "gpt-4-vision-preview", + "capabilities": ["function-call", "image-recognition"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 10 + }, + "output": { + "currency": "USD", + "perMillionTokens": 30 + } + }, + "ownedBy": "openai" + }, + { + "id": "gpt-4o-mini-2024-07-18", + "name": "OpenAI: GPT-4o-mini (2024-07-18)", + "description": "GPT-4o mini is OpenAI's newest model after [GPT-4 Omni](/models/openai/gpt-4o), supporting both text and image inputs with text outputs.\n\nAs their most advanced small model, it is many multiples more affordable than other recent frontier models, and more than 60% cheaper than [GPT-3.5 Turbo](/models/openai/gpt-3.5-turbo). It maintains SOTA intelligence, while being significantly more cost-effective.\n\nGPT-4o mini achieves an 82% score on MMLU and presently ranks higher than GPT-4 on chat preferences [common leaderboards](https://arena.lmsys.org/).\n\nCheck out the [launch announcement](https://openai.com/index/gpt-4o-mini-advancing-cost-efficient-intelligence/) to learn more.\n\n#multimodal", + "capabilities": ["function-call", "structured-output", "image-recognition", "web-search"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 16384, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.15 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.6 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.075 + } + }, + "ownedBy": "openai" + }, + { + "id": "imagen-4-0-generate-preview-05-20", + "description": "Google's latest raw image model", + "capabilities": ["image-generation"], + "inputModalities": ["text", "image"], + "outputModalities": ["image"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2 + } + }, + "ownedBy": "google" + }, + { + "id": "jina-embeddings-v2-base-code", + "description": "Model optimized for code and document search, 768-dimensional, 137M parameters.", + "capabilities": ["embedding"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.05 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.05 + } + }, + "ownedBy": "jina" + }, + { + "id": "learnlm-1-5-pro-experimental", + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.25 + }, + "output": { + "currency": "USD", + "perMillionTokens": 5 + } + }, + "ownedBy": "google" + }, + { + "id": "llama-3-1-versatile", + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.6 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.6 + } + }, + "ownedBy": "meta" + }, + { + "id": "llama-3-1-sonar-small-128k", + "description": "On February 22, 2025, this model will be officially discontinued. The Perplexity AI official fine-tuned LLMA online interface is currently supported only at the api.aihubmix.com address.", + "capabilities": ["web-search"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.3 + } + }, + "ownedBy": "meta" + }, + { + "id": "llama-3-2-vision-preview", + "capabilities": ["image-recognition"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.2 + } + }, + "ownedBy": "meta" + }, + { + "id": "llama-3-2-preview", + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.2 + } + }, + "ownedBy": "meta" + }, + { + "id": "baichuan3-turbo", + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.9 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.9 + } + }, + "ownedBy": "baichuan" + }, + { + "id": "baichuan3-turbo-128k", + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 3.8 + }, + "output": { + "currency": "USD", + "perMillionTokens": 3.8 + } + }, + "ownedBy": "baichuan" + }, + { + "id": "baichuan4", + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 16 + }, + "output": { + "currency": "USD", + "perMillionTokens": 16 + } + }, + "ownedBy": "baichuan" + }, + { + "id": "baichuan4-air", + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.16 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.16 + } + }, + "ownedBy": "baichuan" + }, + { + "id": "baichuan4-turbo", + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2.4 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2.4 + } + }, + "ownedBy": "baichuan" + }, + { + "id": "doubao-1-5-lite-32k", + "description": "Doubao-1.5-lite, a brand-new generation of lightweight model, offers exceptional response speed with both performance and latency reaching world-class levels. It supports a 32k context window and an output length of up to 12k tokens.", + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.05 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.1 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.01 + } + }, + "ownedBy": "bytedance" + }, + { + "id": "doubao-1-5-pro-256k", + "description": "Doubao-1.5-pro-256k, a fully upgraded version based on Doubao-1.5-Pro, delivers an overall performance improvement of 10%. It supports inference with a 256k context window and an output length of up to 12k tokens. With higher performance, larger window size, and exceptional cost-effectiveness, it is suitable for a wider range of application scenarios.", + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.8 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.44 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.8 + } + }, + "ownedBy": "bytedance" + }, + { + "id": "doubao-1-5-vision-pro-32k", + "description": "Doubao-1.5-vision-pro is a newly upgraded multimodal large model that supports image recognition at any resolution and extreme aspect ratios. It enhances visual reasoning, document recognition, detailed information understanding, and instruction-following capabilities. It supports a 32k context window and an output length of up to 12k tokens.", + "capabilities": ["image-recognition"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.46 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.38 + } + }, + "ownedBy": "bytedance" + }, + { + "id": "doubao-lite-128k", + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.14 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.28 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.14 + } + }, + "ownedBy": "bytedance" + }, + { + "id": "doubao-lite-32k", + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.06 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.12 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.012 + } + }, + "ownedBy": "bytedance" + }, + { + "id": "doubao-lite-4k", + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.06 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.12 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.06 + } + }, + "ownedBy": "bytedance" + }, + { + "id": "doubao-pro-128k", + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.8 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.44 + } + }, + "ownedBy": "bytedance" + }, + { + "id": "doubao-pro-256k", + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.8 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.44 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.8 + } + }, + "ownedBy": "bytedance" + }, + { + "id": "doubao-pro-32k", + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.14 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.35 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.028 + } + }, + "ownedBy": "bytedance" + }, + { + "id": "doubao-pro-4k", + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.14 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.35 + } + }, + "ownedBy": "bytedance" + }, + { + "id": "minimax-text-01", + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.14 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.12 + } + }, + "ownedBy": "minimax" + }, + { + "id": "qwen2-instruct", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.2 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "qwen2-a14b-instruct", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.24 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.24 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "stable-diffusion-3-5-large", + "description": "Stable Diffusion 3.5 Large, developed by Stability AI, is a text-to-image generation model that supports high-quality image creation with excellent prompt responsiveness and customization, suitable for professional applications.", + "capabilities": ["image-generation"], + "inputModalities": ["text", "image"], + "outputModalities": ["image"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 4 + }, + "output": { + "currency": "USD", + "perMillionTokens": 4 + } + }, + "ownedBy": "stability" + }, + { + "id": "wizardcoder-python-v1-0", + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.9 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.9 + } + } + }, + { + "id": "phi-3-medium-128k", + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 6 + }, + "output": { + "currency": "USD", + "perMillionTokens": 18 + } + }, + "ownedBy": "microsoft" + }, + { + "id": "phi-3-medium-4k", + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1 + }, + "output": { + "currency": "USD", + "perMillionTokens": 3 + } + }, + "ownedBy": "microsoft" + }, + { + "id": "phi-3-small-128k", + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1 + }, + "output": { + "currency": "USD", + "perMillionTokens": 3 + } + }, + "ownedBy": "microsoft" + }, + { + "id": "cohere-command-r", + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.64 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.92 + } + } + }, + { + "id": "llama-3-2-vision", + "capabilities": ["image-recognition"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.4 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.4 + } + }, + "ownedBy": "meta" + }, + { + "id": "cerebras-llama-3-3", + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.6 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.6 + } + } + }, + { + "id": "chatglm_lite", + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.2858 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.2858 + } + } + }, + { + "id": "chatglm_pro", + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.4286 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.4286 + } + } + }, + { + "id": "chatglm_std", + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.7144 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.7144 + } + } + }, + { + "id": "chatglm_turbo", + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.7144 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.7144 + } + } + }, + { + "id": "claude-2", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 8.8 + }, + "output": { + "currency": "USD", + "perMillionTokens": 8.8 + } + }, + "ownedBy": "anthropic" + }, + { + "id": "claude-2-0", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 8.8 + }, + "output": { + "currency": "USD", + "perMillionTokens": 39.6 + } + }, + "ownedBy": "anthropic" + }, + { + "id": "claude-2-1", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 8.8 + }, + "output": { + "currency": "USD", + "perMillionTokens": 39.6 + } + }, + "ownedBy": "anthropic" + }, + { + "id": "claude-3-5-sonnet@20240620", + "capabilities": ["function-call", "image-recognition", "web-search", "computer-use"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 3.3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 16.5 + } + }, + "ownedBy": "anthropic" + }, + { + "id": "claude-3-haiku-20240229", + "capabilities": ["function-call", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.275 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.275 + } + }, + "ownedBy": "anthropic" + }, + { + "id": "claude-3-haiku@20240307", + "capabilities": ["function-call", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.275 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.375 + } + }, + "ownedBy": "anthropic" + }, + { + "id": "claude-3-opus@20240229", + "capabilities": ["function-call", "image-recognition"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 16.5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 82.5 + } + }, + "ownedBy": "anthropic" + }, + { + "id": "claude-instant-1", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.793 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.793 + } + }, + "ownedBy": "anthropic" + }, + { + "id": "claude-instant-1-2", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.88 + }, + "output": { + "currency": "USD", + "perMillionTokens": 3.96 + } + }, + "ownedBy": "anthropic" + }, + { + "id": "code-davinci-edit-001", + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 20 + }, + "output": { + "currency": "USD", + "perMillionTokens": 20 + } + } + }, + { + "id": "cogview-3", + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 35.5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 35.5 + } + }, + "ownedBy": "zhipu" + }, + { + "id": "cogview-3-plus", + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 10 + }, + "output": { + "currency": "USD", + "perMillionTokens": 10 + } + }, + "ownedBy": "zhipu" + }, + { + "id": "command", + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2 + } + }, + "ownedBy": "cohere" + }, + { + "id": "command-light", + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2 + } + }, + "ownedBy": "cohere" + }, + { + "id": "command-light-nightly", + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2 + } + }, + "ownedBy": "cohere" + }, + { + "id": "command-nightly", + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2 + } + }, + "ownedBy": "cohere" + }, + { + "id": "dall-e-2", + "capabilities": ["image-generation"], + "inputModalities": ["text", "image"], + "outputModalities": ["image"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 16 + }, + "output": { + "currency": "USD", + "perMillionTokens": 16 + } + }, + "ownedBy": "openai" + }, + { + "id": "daocloud-deepseek-v3-2", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.22 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.33 + } + } + }, + { + "id": "davinci", + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 20 + }, + "output": { + "currency": "USD", + "perMillionTokens": 20 + } + }, + "ownedBy": "openai" + }, + { + "id": "davinci-002", + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2 + } + }, + "ownedBy": "openai" + }, + { + "id": "llama-3-3-instant-turbo", + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.11 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.352 + } + }, + "ownedBy": "meta" + }, + { + "id": "deepseek-coder-v2-instruct", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.16 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.32 + } + }, + "ownedBy": "deepseek" + }, + { + "id": "deepseek-v2-chat", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.16 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.32 + } + }, + "ownedBy": "deepseek" + }, + { + "id": "deepseek-v2-5", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.16 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.32 + } + }, + "ownedBy": "deepseek" + }, + { + "id": "deepseek-llm-chat", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.16 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.16 + } + }, + "ownedBy": "deepseek" + }, + { + "id": "distil-whisper-large-v3-en", + "capabilities": ["audio-transcript"], + "inputModalities": ["audio"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 5.556 + }, + "output": { + "currency": "USD", + "perMillionTokens": 5.556 + } + } + }, + { + "id": "doubao-1-5-thinking-vision-pro-250428", + "description": "Deep Thinking \nImage Understanding \nVisual Localization \nVideo Understanding \nTool Invocation \nStructured Output", + "capabilities": ["reasoning", "image-recognition"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 2 + } + }, + "reasoning": { + "supportedEfforts": ["none", "high"] + }, + "ownedBy": "bytedance" + }, + { + "id": "deepseek-r1-distill-qianfan-llama", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.137 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.548 + } + }, + "ownedBy": "deepseek" + }, + { + "id": "doubao-1-5-pro-256k-250115", + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.684 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.2312 + } + }, + "ownedBy": "bytedance" + }, + { + "id": "doubao-1-5-pro-32k-250115", + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.108 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.27 + } + }, + "ownedBy": "bytedance" + }, + { + "id": "gpt-4o-2024-08-06-global", + "capabilities": ["function-call", "image-recognition", "web-search"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2.5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 10 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 1.25 + } + }, + "ownedBy": "openai" + }, + { + "id": "gpt-4o-mini-global", + "capabilities": ["function-call", "image-recognition", "web-search"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.15 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.6 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.075 + } + }, + "ownedBy": "openai" + }, + { + "id": "llama-3", + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 4.795 + }, + "output": { + "currency": "USD", + "perMillionTokens": 4.795 + } + }, + "ownedBy": "meta" + }, + { + "id": "o3-global", + "capabilities": ["reasoning", "function-call", "image-recognition", "web-search"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 8 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.5 + } + }, + "ownedBy": "openai" + }, + { + "id": "o3-mini-global", + "capabilities": ["reasoning", "function-call", "web-search"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.1 + }, + "output": { + "currency": "USD", + "perMillionTokens": 4.4 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.55 + } + }, + "reasoning": { + "supportedEfforts": ["low", "medium", "high"] + }, + "ownedBy": "openai" + }, + { + "id": "o3-pro-global", + "capabilities": ["reasoning", "function-call", "image-recognition", "web-search"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 20 + }, + "output": { + "currency": "USD", + "perMillionTokens": 80 + } + }, + "reasoning": { + "supportedEfforts": ["low", "medium", "high"] + }, + "ownedBy": "openai" + }, + { + "id": "qianfan-chinese-llama-2", + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.822 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.822 + } + } + }, + { + "id": "qianfan-llama-vl", + "capabilities": ["image-recognition"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.274 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.685 + } + } + }, + { + "id": "qwen3-max-thinking", + "name": "Qwen: Qwen3 Max Thinking", + "description": "Qwen3-Max-Thinking is the flagship reasoning model in the Qwen3 series, designed for high-stakes cognitive tasks that require deep, multi-step reasoning. By significantly scaling model capacity and reinforcement learning compute, it delivers major gains in factual accuracy, complex reasoning, instruction following, alignment with human preferences, and agentic behavior.", + "capabilities": ["function-call", "structured-output", "reasoning"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 262144, + "maxOutputTokens": 65536, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 6 + } + }, + "reasoning": { + "supportedEfforts": ["low", "medium", "high"] + }, + "ownedBy": "alibaba" + }, + { + "id": "free", + "name": "Free Models Router", + "description": "The simplest way to get free inference. openrouter/free is a router that selects free models at random from the models available on OpenRouter. The router smartly filters for models that support features needed for your request such as image understanding, tool calling, structured outputs and more. ", + "capabilities": ["function-call", "structured-output", "reasoning", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000 + }, + { + "id": "solar-pro-3", + "name": "Upstage: Solar Pro 3 (free)", + "description": "Solar Pro 3 is Upstage's powerful Mixture-of-Experts (MoE) language model. With 102B total parameters and 12B active parameters per forward pass, it delivers exceptional performance while maintaining computational efficiency. Optimized for Korean with English and Japanese support.", + "capabilities": ["function-call", "structured-output", "reasoning"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "ownedBy": "upstageai" + }, + { + "id": "minimax-m2-her", + "name": "MiniMax: MiniMax M2-her", + "description": "MiniMax M2-her is a dialogue-first large language model built for immersive roleplay, character-driven chat, and expressive multi-turn conversations. Designed to stay consistent in tone and personality, it supports rich message roles (user_system, group, sample_message_user, sample_message_ai) and can learn from example dialogue to better match the style and pacing of your scenario, making it a strong choice for storytelling, companions, and conversational experiences where natural flow and vivid interaction matter most.", + "capabilities": ["reasoning", "function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 65536, + "maxOutputTokens": 2048, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.2 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.03 + } + }, + "reasoning": { + "supportedEfforts": [], + "interleaved": true + }, + "ownedBy": "minimax" + }, + { + "id": "gpt-audio", + "name": "OpenAI: GPT Audio", + "description": "The gpt-audio model is OpenAI's first generally available audio model. The new snapshot features an upgraded decoder for more natural sounding voices and maintains better voice consistency. Audio is priced at $32 per million input tokens and $64 per million output tokens.", + "capabilities": ["structured-output", "audio-recognition", "audio-generation"], + "inputModalities": ["text", "audio"], + "outputModalities": ["text", "audio"], + "contextWindow": 128000, + "maxOutputTokens": 16384, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2.5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 10 + } + }, + "ownedBy": "openai" + }, + { + "id": "gpt-audio-mini", + "name": "OpenAI: GPT Audio Mini", + "description": "A cost-efficient version of GPT Audio. The new snapshot features an upgraded decoder for more natural sounding voices and maintains better voice consistency. Input is priced at $0.60 per million tokens and output is priced at $2.40 per million tokens.", + "capabilities": ["structured-output", "audio-recognition", "audio-generation"], + "inputModalities": ["text", "audio"], + "outputModalities": ["text", "audio"], + "contextWindow": 128000, + "maxOutputTokens": 16384, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.6 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2.4 + } + }, + "ownedBy": "openai" + }, + { + "id": "olmo-3-1-instruct", + "name": "AllenAI: Olmo 3.1 32B Instruct", + "description": "Olmo 3.1 32B Instruct is a large-scale, 32-billion-parameter instruction-tuned language model engineered for high-performance conversational AI, multi-turn dialogue, and practical instruction following. As part of the Olmo 3.1 family, this variant emphasizes responsiveness to complex user directions and robust chat interactions while retaining strong capabilities on reasoning and coding benchmarks. Developed by Ai2 under the Apache 2.0 license, Olmo 3.1 32B Instruct reflects the Olmo initiative’s commitment to openness and transparency.", + "capabilities": ["function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 65536, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.19999999999999998 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.6 + } + }, + "ownedBy": "ai2" + }, + { + "id": "seed-1-6-flash", + "name": "ByteDance Seed: Seed 1.6 Flash", + "description": "Seed 1.6 Flash is an ultra-fast multimodal deep thinking model by ByteDance Seed, supporting both text and visual understanding. It features a 256k context window and can generate outputs of up to 16k tokens.", + "capabilities": ["function-call", "structured-output", "reasoning", "image-recognition", "video-recognition"], + "inputModalities": ["image", "text", "video"], + "outputModalities": ["text"], + "contextWindow": 262144, + "maxOutputTokens": 32768, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.075 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.3 + } + }, + "ownedBy": "bytedance" + }, + { + "id": "mistral-small-creative", + "name": "Mistral: Mistral Small Creative", + "description": "Mistral Small Creative is an experimental small model designed for creative writing, narrative generation, roleplay and character-driven dialogue, general-purpose instruction following, and conversational agents.", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 32768, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.09999999999999999 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.3 + } + }, + "ownedBy": "mistral" + }, + { + "id": "olmo-3-1-think", + "name": "AllenAI: Olmo 3.1 32B Think", + "description": "Olmo 3.1 32B Think is a large-scale, 32-billion-parameter model designed for deep reasoning, complex multi-step logic, and advanced instruction following. Building on the Olmo 3 series, version 3.1 delivers refined reasoning behavior and stronger performance across demanding evaluations and nuanced conversational tasks. Developed by Ai2 under the Apache 2.0 license, Olmo 3.1 32B Think continues the Olmo initiative’s commitment to openness, providing full transparency across model weights, code, and training methodology.", + "capabilities": ["structured-output", "reasoning"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 65536, + "maxOutputTokens": 65536, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.15 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.5 + } + }, + "ownedBy": "ai2" + }, + { + "id": "relace-search", + "name": "Relace: Relace Search", + "description": "The relace-search model uses 4-12 `view_file` and `grep` tools in parallel to explore a codebase and return relevant files to the user request. \n\nIn contrast to RAG, relace-search performs agentic multi-step reasoning to produce highly precise results 4x faster than any frontier model. It's designed to serve as a subagent that passes its findings to an \"oracle\" coding agent, who orchestrates/performs the rest of the coding task.\n\nTo use relace-search you need to build an appropriate agent harness, and parse the response for relevant information to hand off to the oracle. Read more about it in the [Relace documentation](https://docs.relace.ai/docs/fast-agentic-search/agent).", + "capabilities": ["function-call", "web-search"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 256000, + "maxOutputTokens": 128000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1 + }, + "output": { + "currency": "USD", + "perMillionTokens": 3 + } + }, + "ownedBy": "relace" + }, + { + "id": "bodybuilder", + "name": "Body Builder (beta)", + "description": "Transform your natural language requests into structured OpenRouter API request objects. Describe what you want to accomplish with AI models, and Body Builder will construct the appropriate API calls. Example: \"count to 10 using gemini and opus.\"\n\nThis is useful for creating multi-model requests, custom model routers, or programmatic generation of API calls from human descriptions.\n\n**BETA NOTICE**: Body Builder is in beta, and currently free. Pricing and functionality may change in the future.", + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000 + }, + { + "id": "olmo-3-think", + "name": "AllenAI: Olmo 3 32B Think", + "description": "Olmo 3 32B Think is a large-scale, 32-billion-parameter model purpose-built for deep reasoning, complex logic chains and advanced instruction-following scenarios. Its capacity enables strong performance on demanding evaluation tasks and highly nuanced conversational reasoning. Developed by Ai2 under the Apache 2.0 license, Olmo 3 32B Think embodies the Olmo initiative’s commitment to openness, offering full transparency across weights, code and training methodology.", + "capabilities": ["structured-output", "reasoning"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 65536, + "maxOutputTokens": 65536, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.15 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.5 + } + }, + "ownedBy": "ai2" + }, + { + "id": "olmo-3-instruct", + "name": "AllenAI: Olmo 3 7B Instruct", + "description": "Olmo 3 7B Instruct is a supervised instruction-fine-tuned variant of the Olmo 3 7B base model, optimized for instruction-following, question-answering, and natural conversational dialogue. By leveraging high-quality instruction data and an open training pipeline, it delivers strong performance across everyday NLP tasks while remaining accessible and easy to integrate. Developed by Ai2 under the Apache 2.0 license, the model offers a transparent, community-friendly option for instruction-driven applications.", + "capabilities": ["structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 65536, + "maxOutputTokens": 65536, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.09999999999999999 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.19999999999999998 + } + }, + "ownedBy": "ai2" + }, + { + "id": "cogito-v2-1", + "name": "Deep Cogito: Cogito v2.1 671B", + "description": "Cogito v2.1 671B MoE represents one of the strongest open models globally, matching performance of frontier closed and open models. This model is trained using self play with reinforcement learning to reach state-of-the-art performance on multiple categories (instruction following, coding, longer queries and creative writing). This advanced system demonstrates significant progress toward scalable superintelligence through policy improvement.", + "capabilities": ["structured-output", "reasoning"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.25 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.25 + } + }, + "ownedBy": "cogito" + }, + { + "id": "nova-premier-v1", + "name": "Amazon: Nova Premier 1.0", + "description": "Amazon Nova Premier is the most capable of Amazon’s multimodal models for complex reasoning tasks and for use as the best teacher for distilling custom models.", + "capabilities": ["function-call", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 1000000, + "maxOutputTokens": 32000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2.5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 12.5 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.625 + } + }, + "ownedBy": "amazon" + }, + { + "id": "sonar-pro-search", + "name": "Perplexity: Sonar Pro Search", + "description": "Exclusively available on the OpenRouter API, Sonar Pro's new Pro Search mode is Perplexity's most advanced agentic search system. It is designed for deeper reasoning and analysis. Pricing is based on tokens plus $18 per thousand requests. This model powers the Pro Search mode on the Perplexity platform.\n\nSonar Pro Search adds autonomous, multi-step reasoning to Sonar Pro. So, instead of just one query + synthesis, it plans and executes entire research workflows using tools.", + "capabilities": ["structured-output", "reasoning", "web-search", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 8000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 15 + } + }, + "ownedBy": "perplexity" + }, + { + "id": "lfm2-a1b", + "name": "LiquidAI: LFM2-8B-A1B", + "description": "LFM2-8B-A1B is an efficient on-device Mixture-of-Experts (MoE) model from Liquid AI’s LFM2 family, built for fast, high-quality inference on edge hardware. It uses 8.3B total parameters with only ~1.5B active per token, delivering strong performance while keeping compute and memory usage low—making it ideal for phones, tablets, and laptops.", + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 32768, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.01 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.02 + } + }, + "ownedBy": "liquidai" + }, + { + "id": "lfm-2-2", + "name": "LiquidAI: LFM2-2.6B", + "description": "LFM2 is a new generation of hybrid models developed by Liquid AI, specifically designed for edge AI and on-device deployment. It sets a new standard in terms of quality, speed, and memory efficiency.", + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 32768, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.01 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.02 + } + }, + "ownedBy": "liquidai" + }, + { + "id": "gpt-5-image-mini", + "name": "OpenAI: GPT-5 Image Mini", + "description": "GPT-5 Image Mini combines OpenAI's advanced language capabilities, powered by [GPT-5 Mini](https://openrouter.ai/openai/gpt-5-mini), with GPT Image 1 Mini for efficient image generation. This natively multimodal model features superior instruction following, text rendering, and detailed image editing with reduced latency and cost. It excels at high-quality visual creation while maintaining strong text understanding, making it ideal for applications that require both efficient image generation and text processing at scale.", + "capabilities": [ + "function-call", + "structured-output", + "reasoning", + "web-search", + "image-recognition", + "image-generation" + ], + "inputModalities": ["image", "text"], + "outputModalities": ["image", "text"], + "contextWindow": 400000, + "maxOutputTokens": 128000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2.5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.25 + } + }, + "reasoning": { + "supportedEfforts": ["minimal", "low", "medium", "high"] + }, + "ownedBy": "openai" + }, + { + "id": "qwen3-vl-thinking", + "name": "Qwen: Qwen3 VL 8B Thinking", + "description": "Qwen3-VL-8B-Thinking is the reasoning-optimized variant of the Qwen3-VL-8B multimodal model, designed for advanced visual and textual reasoning across complex scenes, documents, and temporal sequences. It integrates enhanced multimodal alignment and long-context processing (native 256K, expandable to 1M tokens) for tasks such as scientific visual analysis, causal inference, and mathematical reasoning over image or video inputs.\n\nCompared to the Instruct edition, the Thinking version introduces deeper visual-language fusion and deliberate reasoning pathways that improve performance on long-chain logic tasks, STEM problem-solving, and multi-step video understanding. It achieves stronger temporal grounding via Interleaved-MRoPE and timestamp-aware embeddings, while maintaining robust OCR, multilingual comprehension, and text generation on par with large text-only LLMs.", + "capabilities": ["function-call", "structured-output", "reasoning", "image-recognition"], + "inputModalities": ["image", "text"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 32768, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.117 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.365 + } + }, + "reasoning": { + "supportedEfforts": ["low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 38912 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "ernie-4-5-a3b-thinking", + "name": "Baidu: ERNIE 4.5 21B A3B Thinking", + "description": "ERNIE-4.5-21B-A3B-Thinking is Baidu's upgraded lightweight MoE model, refined to boost reasoning depth and quality for top-tier performance in logical puzzles, math, science, coding, text generation, and expert-level academic benchmarks.", + "capabilities": ["reasoning"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 65536, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.07 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.28 + } + }, + "ownedBy": "baidu" + }, + { + "id": "qwen3-vl-a3b-thinking", + "name": "Qwen: Qwen3 VL 30B A3B Thinking", + "description": "Qwen3-VL-30B-A3B-Thinking is a multimodal model that unifies strong text generation with visual understanding for images and videos. Its Thinking variant enhances reasoning in STEM, math, and complex tasks. It excels in perception of real-world/synthetic categories, 2D/3D spatial grounding, and long-form visual comprehension, achieving competitive multimodal benchmark results. For agentic use, it handles multi-image multi-turn instructions, video timeline alignments, GUI automation, and visual coding from sketches to debugged UI. Text performance matches flagship Qwen3 models, suiting document AI, OCR, UI assistance, spatial tasks, and agent research.", + "capabilities": ["function-call", "structured-output", "reasoning", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 32768, + "reasoning": { + "supportedEfforts": ["low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 38912 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "cydonia-v4-1", + "name": "TheDrummer: Cydonia 24B V4.1", + "description": "Uncensored and creative writing model based on Mistral Small 3.2 24B with good recall, prompt adherence, and intelligence.", + "capabilities": ["structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 131072, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.5 + } + } + }, + { + "id": "relace-apply-3", + "name": "Relace: Relace Apply 3", + "description": "Relace Apply 3 is a specialized code-patching LLM that merges AI-suggested edits straight into your source files. It can apply updates from GPT-4o, Claude, and others into your files at 10,000 tokens/sec on average.\n\nThe model requires the prompt to be in the following format: \n{instruction}\n{initial_code}\n{edit_snippet}\n\nZero Data Retention is enabled for Relace. Learn more about this model in their [documentation](https://docs.relace.ai/api-reference/instant-apply/apply)", + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 256000, + "maxOutputTokens": 128000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.85 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.25 + } + }, + "ownedBy": "relace" + }, + { + "id": "qwen3-vl-a22b-thinking", + "name": "Qwen: Qwen3 VL 235B A22B Thinking", + "description": "Qwen3-VL-235B-A22B Thinking is a multimodal model that unifies strong text generation with visual understanding across images and video. The Thinking model is optimized for multimodal reasoning in STEM and math. The series emphasizes robust perception (recognition of diverse real-world and synthetic categories), spatial understanding (2D/3D grounding), and long-form visual comprehension, with competitive results on public multimodal benchmarks for both perception and reasoning.\n\nBeyond analysis, Qwen3-VL supports agentic interaction and tool use: it can follow complex instructions over multi-image, multi-turn dialogues; align text to video timelines for precise temporal queries; and operate GUI elements for automation tasks. The models also enable visual coding workflows, turning sketches or mockups into code and assisting with UI debugging, while maintaining strong text-only performance comparable to the flagship Qwen3 language models. This makes Qwen3-VL suitable for production scenarios spanning document AI, multilingual OCR, software/UI assistance, spatial/embodied tasks, and research on vision-language agents.", + "capabilities": ["function-call", "structured-output", "reasoning", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 32768, + "reasoning": { + "supportedEfforts": ["low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 38912 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "tongyi-deepresearch-a3b", + "name": "Tongyi DeepResearch 30B A3B", + "description": "Tongyi DeepResearch is an agentic large language model developed by Tongyi Lab, with 30 billion total parameters activating only 3 billion per token. It's optimized for long-horizon, deep information-seeking tasks and delivers state-of-the-art performance on benchmarks like Humanity's Last Exam, BrowserComp, BrowserComp-ZH, WebWalkerQA, GAIA, xbench-DeepSearch, and FRAMES. This makes it superior for complex agentic search, reasoning, and multi-step problem-solving compared to prior models.\n\nThe model includes a fully automated synthetic data pipeline for scalable pre-training, fine-tuning, and reinforcement learning. It uses large-scale continual pre-training on diverse agentic data to boost reasoning and stay fresh. It also features end-to-end on-policy RL with a customized Group Relative Policy Optimization, including token-level gradients and negative sample filtering for stable training. The model supports ReAct for core ability checks and an IterResearch-based 'Heavy' mode for max performance through test-time scaling. It's ideal for advanced research agents, tool use, and heavy inference workflows.", + "capabilities": ["function-call", "structured-output", "reasoning"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 131072, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.09 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.44999999999999996 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.09 + } + } + }, + { + "id": "qwen3-next-a3b-thinking", + "name": "Qwen: Qwen3 Next 80B A3B Thinking", + "description": "Qwen3-Next-80B-A3B-Thinking is a reasoning-first chat model in the Qwen3-Next line that outputs structured “thinking” traces by default. It’s designed for hard multi-step problems; math proofs, code synthesis/debugging, logic, and agentic planning, and reports strong results across knowledge, reasoning, coding, alignment, and multilingual evaluations. Compared with prior Qwen3 variants, it emphasizes stability under long chains of thought and efficient scaling during inference, and it is tuned to follow complex instructions while reducing repetitive or off-task behavior.\n\nThe model is suitable for agent frameworks and tool use (function calling), retrieval-heavy workflows, and standardized benchmarking where step-by-step solutions are required. It supports long, detailed completions and leverages throughput-oriented techniques (e.g., multi-token prediction) for faster generation. Note that it operates in thinking-only mode.", + "capabilities": ["function-call", "structured-output", "reasoning"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.15 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.2 + } + }, + "reasoning": { + "supportedEfforts": ["low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 38912 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "jamba-large-1-7", + "name": "AI21: Jamba Large 1.7", + "description": "Jamba Large 1.7 is the latest model in the Jamba open family, offering improvements in grounding, instruction-following, and overall efficiency. Built on a hybrid SSM-Transformer architecture with a 256K context window, it delivers more accurate, contextually grounded responses and better steerability than previous versions.", + "capabilities": ["function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 256000, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 8 + } + }, + "ownedBy": "ai21" + }, + { + "id": "ui-tars-1-5", + "name": "ByteDance: UI-TARS 7B ", + "description": "UI-TARS-1.5 is a multimodal vision-language agent optimized for GUI-based environments, including desktop interfaces, web browsers, mobile systems, and games. Built by ByteDance, it builds upon the UI-TARS framework with reinforcement learning-based reasoning, enabling robust action planning and execution across virtual interfaces.\n\nThis model achieves state-of-the-art results on a range of interactive and grounding benchmarks, including OSworld, WebVoyager, AndroidWorld, and ScreenSpot. It also demonstrates perfect task completion across diverse Poki games and outperforms prior models in Minecraft agent tasks. UI-TARS-1.5 supports thought decomposition during inference and shows strong scaling across variants, with the 1.5 version notably exceeding the performance of earlier 72B and 7B checkpoints.", + "capabilities": ["image-recognition"], + "inputModalities": ["image", "text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 2048, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.09999999999999999 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.19999999999999998 + } + }, + "ownedBy": "bytedance" + }, + { + "id": "devstral-medium", + "name": "Mistral: Devstral Medium", + "description": "Devstral Medium is a high-performance code generation and agentic reasoning model developed jointly by Mistral AI and All Hands AI. Positioned as a step up from Devstral Small, it achieves 61.6% on SWE-Bench Verified, placing it ahead of Gemini 2.5 Pro and GPT-4.1 in code-related tasks, at a fraction of the cost. It is designed for generalization across prompt styles and tool use in code agents and frameworks.\n\nDevstral Medium is available via API only (not open-weight), and supports enterprise deployment on private infrastructure, with optional fine-tuning capabilities.", + "capabilities": ["function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131072, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.39999999999999997 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2 + } + }, + "ownedBy": "mistral" + }, + { + "id": "spotlight", + "name": "Arcee AI: Spotlight", + "description": "Spotlight is a 7‑billion‑parameter vision‑language model derived from Qwen 2.5‑VL and fine‑tuned by Arcee AI for tight image‑text grounding tasks. It offers a 32 k‑token context window, enabling rich multimodal conversations that combine lengthy documents with one or more images. Training emphasized fast inference on consumer GPUs while retaining strong captioning, visual‐question‑answering, and diagram‑analysis accuracy. As a result, Spotlight slots neatly into agent workflows where screenshots, charts or UI mock‑ups need to be interpreted on the fly. Early benchmarks show it matching or out‑scoring larger VLMs such as LLaVA‑1.6 13 B on popular VQA and POPE alignment tests. ", + "capabilities": ["image-recognition"], + "inputModalities": ["image", "text"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 65537, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.18 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.18 + } + }, + "ownedBy": "arceeai" + }, + { + "id": "maestro-reasoning", + "name": "Arcee AI: Maestro Reasoning", + "description": "Maestro Reasoning is Arcee's flagship analysis model: a 32 B‑parameter derivative of Qwen 2.5‑32 B tuned with DPO and chain‑of‑thought RL for step‑by‑step logic. Compared to the earlier 7 B preview, the production 32 B release widens the context window to 128 k tokens and doubles pass‑rate on MATH and GSM‑8K, while also lifting code completion accuracy. Its instruction style encourages structured \"thought → answer\" traces that can be parsed or hidden according to user preference. That transparency pairs well with audit‑focused industries like finance or healthcare where seeing the reasoning path matters. In Arcee Conductor, Maestro is automatically selected for complex, multi‑constraint queries that smaller SLMs bounce. ", + "capabilities": ["reasoning"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 32000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.8999999999999999 + }, + "output": { + "currency": "USD", + "perMillionTokens": 3.3000000000000003 + } + } + }, + { + "id": "virtuoso-large", + "name": "Arcee AI: Virtuoso Large", + "description": "Virtuoso‑Large is Arcee's top‑tier general‑purpose LLM at 72 B parameters, tuned to tackle cross‑domain reasoning, creative writing and enterprise QA. Unlike many 70 B peers, it retains the 128 k context inherited from Qwen 2.5, letting it ingest books, codebases or financial filings wholesale. Training blended DeepSeek R1 distillation, multi‑epoch supervised fine‑tuning and a final DPO/RLHF alignment stage, yielding strong performance on BIG‑Bench‑Hard, GSM‑8K and long‑context Needle‑In‑Haystack tests. Enterprises use Virtuoso‑Large as the \"fallback\" brain in Conductor pipelines when other SLMs flag low confidence. Despite its size, aggressive KV‑cache optimizations keep first‑token latency in the low‑second range on 8× H100 nodes, making it a practical production‑grade powerhouse.", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.75 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.2 + } + }, + "ownedBy": "arceeai" + }, + { + "id": "coder-large", + "name": "Arcee AI: Coder Large", + "description": "Coder‑Large is a 32 B‑parameter offspring of Qwen 2.5‑Instruct that has been further trained on permissively‑licensed GitHub, CodeSearchNet and synthetic bug‑fix corpora. It supports a 32k context window, enabling multi‑file refactoring or long diff review in a single call, and understands 30‑plus programming languages with special attention to TypeScript, Go and Terraform. Internal benchmarks show 5–8 pt gains over CodeLlama‑34 B‑Python on HumanEval and competitive BugFix scores thanks to a reinforcement pass that rewards compilable output. The model emits structured explanations alongside code blocks by default, making it suitable for educational tooling as well as production copilot scenarios. Cost‑wise, Together AI prices it well below proprietary incumbents, so teams can scale interactive coding without runaway spend. ", + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 32768, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.7999999999999999 + } + }, + "ownedBy": "arceeai" + }, + { + "id": "o4-mini-high", + "name": "OpenAI: o4 Mini High", + "description": "OpenAI o4-mini-high is the same model as [o4-mini](/openai/o4-mini) with reasoning_effort set to high. \n\nOpenAI o4-mini is a compact reasoning model in the o-series, optimized for fast, cost-efficient performance while retaining strong multimodal and agentic capabilities. It supports tool use and demonstrates competitive reasoning and coding performance across benchmarks like AIME (99.5% with Python) and SWE-bench, outperforming its predecessor o3-mini and even approaching o3 in some domains.\n\nDespite its smaller size, o4-mini exhibits high accuracy in STEM tasks, visual problem solving (e.g., MathVista, MMMU), and code editing. It is especially well-suited for high-throughput scenarios where latency or cost is critical. Thanks to its efficient architecture and refined reinforcement learning training, o4-mini can chain tools, generate structured outputs, and solve multi-step tasks with minimal delay—often in under a minute.", + "capabilities": ["function-call", "structured-output", "reasoning", "web-search", "image-recognition"], + "inputModalities": ["image", "text"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 100000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.1 + }, + "output": { + "currency": "USD", + "perMillionTokens": 4.4 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.275 + } + }, + "reasoning": { + "supportedEfforts": ["low", "medium", "high"] + }, + "ownedBy": "openai" + }, + { + "id": "llemma_7b", + "name": "EleutherAI: Llemma 7b", + "description": "Llemma 7B is a language model for mathematics. It was initialized with Code Llama 7B weights, and trained on the Proof-Pile-2 for 200B tokens. Llemma models are particularly strong at chain-of-thought mathematical reasoning and using computational tools for mathematics, such as Python and formal theorem provers.", + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 4096, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.7999999999999999 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.2 + } + } + }, + { + "id": "codellama-instruct-solidity", + "name": "AlfredPros: CodeLLaMa 7B Instruct Solidity", + "description": "A finetuned 7 billion parameters Code LLaMA - Instruct model to generate Solidity smart contract using 4-bit QLoRA finetuning provided by PEFT library.", + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 4096, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.7999999999999999 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.2 + } + } + }, + { + "id": "olmo-2-0325-instruct", + "name": "AllenAI: Olmo 2 32B Instruct", + "description": "OLMo-2 32B Instruct is a supervised instruction-finetuned variant of the OLMo-2 32B March 2025 base model. It excels in complex reasoning and instruction-following tasks across diverse benchmarks such as GSM8K, MATH, IFEval, and general NLP evaluation. Developed by AI2, OLMo-2 32B is part of an open, research-oriented initiative, trained primarily on English-language datasets to advance the understanding and development of open-source language models.", + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.049999999999999996 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.19999999999999998 + } + }, + "ownedBy": "ai2" + }, + { + "id": "skyfall-v2", + "name": "TheDrummer: Skyfall 36B V2", + "description": "Skyfall 36B v2 is an enhanced iteration of Mistral Small 2501, specifically fine-tuned for improved creativity, nuanced writing, role-playing, and coherent storytelling.", + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 32768, + "maxOutputTokens": 32768, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.55 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.7999999999999999 + } + } + }, + { + "id": "o3-mini-high", + "name": "OpenAI: o3 Mini High", + "description": "OpenAI o3-mini-high is the same model as [o3-mini](/openai/o3-mini) with reasoning_effort set to high. \n\no3-mini is a cost-efficient language model optimized for STEM reasoning tasks, particularly excelling in science, mathematics, and coding. The model features three adjustable reasoning effort levels and supports key developer capabilities including function calling, structured outputs, and streaming, though it does not include vision processing capabilities.\n\nThe model demonstrates significant improvements over its predecessor, with expert testers preferring its responses 56% of the time and noting a 39% reduction in major errors on complex questions. With medium reasoning effort settings, o3-mini matches the performance of the larger o1 model on challenging reasoning evaluations like AIME and GPQA, while maintaining lower latency and cost.", + "capabilities": ["function-call", "structured-output", "reasoning", "web-search"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 200000, + "maxOutputTokens": 100000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.1 + }, + "output": { + "currency": "USD", + "perMillionTokens": 4.4 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.55 + } + }, + "reasoning": { + "supportedEfforts": ["low", "medium", "high"] + }, + "ownedBy": "openai" + }, + { + "id": "aion-1-0", + "name": "AionLabs: Aion-1.0", + "description": "Aion-1.0 is a multi-model system designed for high performance across various tasks, including reasoning and coding. It is built on DeepSeek-R1, augmented with additional models and techniques such as Tree of Thoughts (ToT) and Mixture of Experts (MoE). It is Aion Lab's most powerful reasoning model.", + "capabilities": ["reasoning"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 32768, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 4 + }, + "output": { + "currency": "USD", + "perMillionTokens": 8 + } + }, + "ownedBy": "aion" + }, + { + "id": "aion-1-0-mini", + "name": "AionLabs: Aion-1.0-Mini", + "description": "Aion-1.0-Mini 32B parameter model is a distilled version of the DeepSeek-R1 model, designed for strong performance in reasoning domains such as mathematics, coding, and logic. It is a modified variant of a FuseAI model that outperforms R1-Distill-Qwen-32B and R1-Distill-Llama-70B, with benchmark results available on its [Hugging Face page](https://huggingface.co/FuseAI/FuseO1-DeepSeekR1-QwQ-SkyT1-32B-Preview), independently replicated for verification.", + "capabilities": ["reasoning"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 32768, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.7 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.4 + } + }, + "ownedBy": "aion" + }, + { + "id": "aion-rp-llama-3-1", + "name": "AionLabs: Aion-RP 1.0 (8B)", + "description": "Aion-RP-Llama-3.1-8B ranks the highest in the character evaluation portion of the RPBench-Auto benchmark, a roleplaying-specific variant of Arena-Hard-Auto, where LLMs evaluate each other’s responses. It is a fine-tuned base model rather than an instruct model, designed to produce more natural and varied writing.", + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 32768, + "maxOutputTokens": 32768, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.7999999999999999 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.5999999999999999 + } + }, + "ownedBy": "aion" + }, + { + "id": "l3-1-hanami-x1", + "name": "Sao10K: Llama 3.1 70B Hanami x1", + "description": "This is [Sao10K](/sao10k)'s experiment over [Euryale v2.2](/sao10k/l3.1-euryale-70b).", + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 16000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 3 + } + } + }, + { + "id": "l3-3-euryale", + "name": "Sao10K: Llama 3.3 Euryale 70B", + "description": "Euryale L3.3 70B is a model focused on creative roleplay from [Sao10k](https://ko-fi.com/sao10k). It is the successor of [Euryale L3 70B v2.2](/models/sao10k/l3-euryale-70b).", + "capabilities": ["structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 131072, + "maxOutputTokens": 16384, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.65 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.75 + } + } + }, + { + "id": "nova-lite-v1", + "name": "Amazon: Nova Lite 1.0", + "description": "Amazon Nova Lite 1.0 is a very low-cost multimodal model from Amazon that focused on fast processing of image, video, and text inputs to generate text output. Amazon Nova Lite can handle real-time customer interactions, document analysis, and visual question-answering tasks with high accuracy.\n\nWith an input context of 300K tokens, it can analyze multiple images or up to 30 minutes of video in a single input.", + "capabilities": ["function-call", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 300000, + "maxOutputTokens": 5120, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.06 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.24 + } + }, + "ownedBy": "amazon" + }, + { + "id": "nova-micro-v1", + "name": "Amazon: Nova Micro 1.0", + "description": "Amazon Nova Micro 1.0 is a text-only model that delivers the lowest latency responses in the Amazon Nova family of models at a very low cost. With a context length of 128K tokens and optimized for speed and cost, Amazon Nova Micro excels at tasks such as text summarization, translation, content classification, interactive chat, and brainstorming. It has simple mathematical reasoning and coding abilities.", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 128000, + "maxOutputTokens": 5120, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.035 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.14 + } + }, + "ownedBy": "amazon" + }, + { + "id": "pixtral-large-2411", + "name": "Mistral: Pixtral Large 2411", + "description": "Pixtral Large is a 124B parameter, open-weight, multimodal model built on top of [Mistral Large 2](/mistralai/mistral-large-2411). The model is able to understand documents, charts and natural images.\n\nThe model is available under the Mistral Research License (MRL) for research and educational use, and the Mistral Commercial License for experimentation, testing, and production for commercial purposes.\n\n", + "capabilities": ["function-call", "structured-output", "image-recognition"], + "inputModalities": ["text", "image"], + "outputModalities": ["text"], + "contextWindow": 131072, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 6 + } + }, + "ownedBy": "mistral" + }, + { + "id": "sorcererlm-8x22b", + "name": "SorcererLM 8x22B", + "description": "SorcererLM is an advanced RP and storytelling model, built as a Low-rank 16-bit LoRA fine-tuned on [WizardLM-2 8x22B](/microsoft/wizardlm-2-8x22b).\n\n- Advanced reasoning and emotional intelligence for engaging and immersive interactions\n- Vivid writing capabilities enriched with spatial and contextual awareness\n- Enhanced narrative depth, promoting creative and dynamic storytelling", + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 16000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 4.5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 4.5 + } + } + }, + { + "id": "unslopnemo", + "name": "TheDrummer: UnslopNemo 12B", + "description": "UnslopNemo v4.1 is the latest addition from the creator of Rocinante, designed for adventure writing and role-play scenarios.", + "capabilities": ["function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 32768, + "maxOutputTokens": 32768, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.39999999999999997 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.39999999999999997 + } + } + }, + { + "id": "magnum-v4", + "name": "Magnum v4 72B", + "description": "This is a series of models designed to replicate the prose quality of the Claude 3 models, specifically Sonnet(https://openrouter.ai/anthropic/claude-3.5-sonnet) and Opus(https://openrouter.ai/anthropic/claude-3-opus).\n\nThe model is fine-tuned on top of [Qwen2.5 72B](https://openrouter.ai/qwen/qwen-2.5-72b-instruct).", + "capabilities": ["structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 16384, + "maxOutputTokens": 2048, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 5 + } + } + }, + { + "id": "inflection-3-pi", + "name": "Inflection: Inflection 3 Pi", + "description": "Inflection 3 Pi powers Inflection's [Pi](https://pi.ai) chatbot, including backstory, emotional intelligence, productivity, and safety. It has access to recent news, and excels in scenarios like customer support and roleplay.\n\nPi has been trained to mirror your tone and style, if you use more emojis, so will Pi! Try experimenting with various prompts and conversation styles.", + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 8000, + "maxOutputTokens": 1024, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2.5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 10 + } + }, + "ownedBy": "inflection" + }, + { + "id": "inflection-3-productivity", + "name": "Inflection: Inflection 3 Productivity", + "description": "Inflection 3 Productivity is optimized for following instructions. It is better for tasks requiring JSON output or precise adherence to provided guidelines. It has access to recent news.\n\nFor emotional intelligence similar to Pi, see [Inflect 3 Pi](/inflection/inflection-3-pi)\n\nSee [Inflection's announcement](https://inflection.ai/blog/enterprise) for more details.", + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 8000, + "maxOutputTokens": 1024, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2.5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 10 + } + }, + "ownedBy": "inflection" + }, + { + "id": "rocinante", + "name": "TheDrummer: Rocinante 12B", + "description": "Rocinante 12B is designed for engaging storytelling and rich prose.\n\nEarly testers have reported:\n- Expanded vocabulary with unique and expressive word choices\n- Enhanced creativity for vivid narratives\n- Adventure-filled and captivating stories", + "capabilities": ["function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 32768, + "maxOutputTokens": 32768, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.16999999999999998 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.43 + } + } + }, + { + "id": "llama-3-1-lumimaid", + "name": "NeverSleep: Lumimaid v0.2 8B", + "description": "Lumimaid v0.2 8B is a finetune of [Llama 3.1 8B](/models/meta-llama/llama-3.1-8b-instruct) with a \"HUGE step up dataset wise\" compared to Lumimaid v0.1. Sloppy chats output were purged.\n\nUsage of this model is subject to [Meta's Acceptable Use Policy](https://llama.meta.com/llama3/use-policy/).", + "capabilities": ["structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 32768, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.09 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.6 + } + }, + "ownedBy": "meta" + }, + { + "id": "l3-1-euryale", + "name": "Sao10K: Llama 3.1 Euryale 70B v2.2", + "description": "Euryale L3.1 70B v2.2 is a model focused on creative roleplay from [Sao10k](https://ko-fi.com/sao10k). It is the successor of [Euryale L3 70B v2.1](/models/sao10k/l3-euryale-70b).", + "capabilities": ["function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 32768, + "maxOutputTokens": 32768, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.65 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.75 + } + } + }, + { + "id": "l3-euryale", + "name": "Sao10k: Llama 3 Euryale 70B v2.1", + "description": "Euryale 70B v2.1 is a model focused on creative roleplay from [Sao10k](https://ko-fi.com/sao10k).\n\n- Better prompt adherence.\n- Better anatomy / spatial awareness.\n- Adapts much better to unique and custom formatting / reply formats.\n- Very creative, lots of unique swipes.\n- Is not restrictive during roleplays.", + "capabilities": ["function-call"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 8192, + "maxOutputTokens": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.48 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.48 + } + } + }, + { + "id": "llama-guard-2", + "name": "Meta: LlamaGuard 2 8B", + "description": "This safeguard model has 8B parameters and is based on the Llama 3 family. Just like is predecessor, [LlamaGuard 1](https://huggingface.co/meta-llama/LlamaGuard-7b), it can do both prompt and response classification.\n\nLlamaGuard 2 acts as a normal LLM would, generating text that indicates whether the given input/output is safe/unsafe. If deemed unsafe, it will also share the content categories violated.\n\nFor best results, please use raw prompt input or the `/completions` endpoint, instead of the chat API.\n\nIt has demonstrated strong performance compared to leading closed-source models in human evaluations.\n\nTo read more about the model release, [click here](https://ai.meta.com/blog/meta-llama-3/). Usage of this model is subject to [Meta's Acceptable Use Policy](https://llama.meta.com/llama3/use-policy/).", + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.19999999999999998 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.19999999999999998 + } + }, + "ownedBy": "meta" + }, + { + "id": "mistral-instruct-v0-2", + "name": "Mistral: Mistral 7B Instruct v0.2", + "description": "A high-performing, industry-standard 7.3B parameter model, with optimizations for speed and context length.\n\nAn improved version of [Mistral 7B Instruct](/modelsmistralai/mistral-7b-instruct-v0.1), with the following changes:\n\n- 32k context window (vs 8k context in v0.1)\n- Rope-theta = 1e6\n- No Sliding-Window Attention", + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 32768, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.19999999999999998 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.19999999999999998 + } + }, + "ownedBy": "mistral" + }, + { + "id": "mixtral-8x7b-instruct", + "name": "Mistral: Mixtral 8x7B Instruct", + "description": "Mixtral 8x7B Instruct is a pretrained generative Sparse Mixture of Experts, by Mistral AI, for chat and instruction use. Incorporates 8 experts (feed-forward networks) for a total of 47 billion parameters.\n\nInstruct model fine-tuned by Mistral. #moe", + "capabilities": ["function-call", "structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 32768, + "maxOutputTokens": 16384, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.54 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.54 + } + }, + "ownedBy": "mistral" + }, + { + "id": "noromaid", + "name": "Noromaid 20B", + "description": "A collab between IkariDev and Undi. This merge is suitable for RP, ERP, and general knowledge.\n\n#merge #uncensored", + "capabilities": ["structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 4096, + "maxOutputTokens": 2048, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.75 + } + } + }, + { + "id": "goliath", + "name": "Goliath 120B", + "description": "A large LLM created by combining two fine-tuned Llama 70B models into one 120B model. Combines Xwin and Euryale.\n\nCredits to\n- [@chargoddard](https://huggingface.co/chargoddard) for developing the framework used to merge the model - [mergekit](https://github.com/cg123/mergekit).\n- [@Undi95](https://huggingface.co/Undi95) for helping with the merge ratios.\n\n#merge", + "capabilities": ["structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 6144, + "maxOutputTokens": 1024, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 3.75 + }, + "output": { + "currency": "USD", + "perMillionTokens": 7.5 + } + } + }, + { + "id": "weaver", + "name": "Mancer: Weaver (alpha)", + "description": "An attempt to recreate Claude-style verbosity, but don't expect the same level of coherence or memory. Meant for use in roleplay/narrative situations.", + "capabilities": ["structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 8000, + "maxOutputTokens": 2000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.75 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1 + } + } + }, + { + "id": "remm-slerp-l2", + "name": "ReMM SLERP 13B", + "description": "A recreation trial of the original MythoMax-L2-B13 but with updated models. #merge", + "capabilities": ["structured-output"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 6144, + "maxOutputTokens": 4096, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.44999999999999996 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.65 + } + } + }, + { + "id": "gte-base", + "name": "Thenlper: GTE-Base", + "description": "The gte-base embedding model encodes English sentences and paragraphs into a 768-dimensional dense vector space, delivering efficient and effective semantic embeddings optimized for textual similarity, semantic search, and clustering applications.", + "capabilities": ["embedding"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 512, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.005 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "gte-large", + "name": "Thenlper: GTE-Large", + "description": "The gte-large embedding model converts English sentences, paragraphs and moderate-length documents into a 1024-dimensional dense vector space, delivering high-quality semantic embeddings optimized for information retrieval, semantic textual similarity, reranking and clustering tasks. Trained via multi-stage contrastive learning on a large domain-diverse relevance corpus, it offers excellent performance across general-purpose embedding use-cases.", + "capabilities": ["embedding"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 512, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.01 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "e5-large-v2", + "name": "Intfloat: E5-Large-v2", + "description": "The e5-large-v2 embedding model maps English sentences, paragraphs, and documents into a 1024-dimensional dense vector space, delivering high-accuracy semantic embeddings optimized for retrieval, semantic search, reranking, and similarity-scoring tasks.", + "capabilities": ["embedding"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 512, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.01 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + } + }, + { + "id": "e5-base-v2", + "name": "Intfloat: E5-Base-v2", + "description": "The e5-base-v2 embedding model encodes English sentences and paragraphs into a 768-dimensional dense vector space, producing efficient and high-quality semantic embeddings optimized for tasks such as semantic search, similarity scoring, retrieval and clustering.", + "capabilities": ["embedding"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 512, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.005 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + } + }, + { + "id": "paraphrase-minilm-l6-v2", + "name": "Sentence Transformers: paraphrase-MiniLM-L6-v2", + "description": "The paraphrase-MiniLM-L6-v2 embedding model converts sentences and short paragraphs into a 384-dimensional dense vector space, producing high-quality semantic embeddings optimized for paraphrase detection, semantic similarity scoring, clustering, and lightweight retrieval tasks.", + "capabilities": ["embedding"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 512, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.005 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + } + }, + { + "id": "all-minilm-l12-v2", + "name": "Sentence Transformers: all-MiniLM-L12-v2", + "description": "The all-MiniLM-L12-v2 embedding model maps sentences and short paragraphs into a 384-dimensional dense vector space, producing efficient and high-quality semantic embeddings optimized for tasks such as semantic search, clustering, and similarity-scoring.", + "capabilities": ["embedding"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 512, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.005 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + } + }, + { + "id": "multi-qa-mpnet-base-dot-v1", + "name": "Sentence Transformers: multi-qa-mpnet-base-dot-v1", + "description": "The multi-qa-mpnet-base-dot-v1 embedding model transforms sentences and short paragraphs into a 768-dimensional dense vector space, generating high-quality semantic embeddings optimized for question-and-answer retrieval, semantic search, and similarity-scoring across diverse content.", + "capabilities": ["embedding"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 512, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.005 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + } + }, + { + "id": "all-mpnet-base-v2", + "name": "Sentence Transformers: all-mpnet-base-v2", + "description": "The all-mpnet-base-v2 embedding model encodes sentences and short paragraphs into a 768-dimensional dense vector space, providing high-fidelity semantic embeddings well suited for tasks like information retrieval, clustering, similarity scoring, and text ranking.", + "capabilities": ["embedding"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 512, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.005 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + } + }, + { + "id": "all-minilm-l6-v2", + "name": "Sentence Transformers: all-MiniLM-L6-v2", + "description": "The all-MiniLM-L6-v2 embedding model maps sentences and short paragraphs into a 384-dimensional dense vector space, enabling high-quality semantic representations that are ideal for downstream tasks such as information retrieval, clustering, similarity scoring, and text ranking.", + "capabilities": ["embedding"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 512, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.005 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + } + }, + { + "id": "mistral-embed-2312", + "name": "Mistral: Mistral Embed 2312", + "description": "Mistral Embed is a specialized embedding model for text data, optimized for semantic search and RAG applications. Developed by Mistral AI in late 2023, it produces 1024-dimensional vectors that effectively capture semantic relationships in text.", + "capabilities": ["embedding"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.09999999999999999 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "ownedBy": "mistral" + }, + { + "id": "codestral-embed-2505", + "name": "Mistral: Codestral Embed 2505", + "description": "Mistral Codestral Embed is specially designed for code, perfect for embedding code databases, repositories, and powering coding assistants with state-of-the-art retrieval.", + "capabilities": ["embedding"], + "inputModalities": ["text"], + "outputModalities": ["text"], + "contextWindow": 8192, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.15 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "ownedBy": "mistral" + }, + { + "id": "kolors", + "name": "kolors", + "ownedBy": "kolors" + }, + { + "id": "kat-coder-air-v1", + "name": "kat-coder-air-v1", + "ownedBy": "streamlake" + }, + { + "id": "kat-coder-exp-1010", + "name": "kat-coder-exp-1010", + "ownedBy": "streamlake" + }, + { + "id": "minimax-m2-1--lightning", + "name": "minimax-m2-1--lightning", + "capabilities": ["reasoning", "function-call"], + "reasoning": { + "supportedEfforts": [], + "interleaved": true + }, + "ownedBy": "minimax" + }, + { + "id": "qwen-image-edit-2509", + "name": "qwen-image-edit-2509", + "capabilities": ["function-call", "image-generation"], + "ownedBy": "alibaba" + }, + { + "id": "wan2-2-i2v-a14b", + "name": "wan2-2-i2v-a14b", + "capabilities": ["video-generation"], + "ownedBy": "alibaba" + }, + { + "id": "wan2-2-t2v-a14b", + "name": "wan2-2-t2v-a14b", + "capabilities": ["video-generation"], + "ownedBy": "alibaba" + }, + { + "id": "telespeechasr", + "name": "telespeechasr", + "ownedBy": "silicon" + }, + { + "id": "moss-ttsd-v0-5", + "name": "moss-ttsd-v0-5", + "ownedBy": "silicon" + }, + { + "id": "cosyvoice2", + "name": "cosyvoice2", + "ownedBy": "silicon" + }, + { + "id": "sensevoicesmall", + "name": "sensevoicesmall", + "ownedBy": "silicon" + }, + { + "id": "indextts-2", + "name": "indextts-2", + "capabilities": ["audio-generation"], + "ownedBy": "silicon" + }, + { + "id": "bce-embedding-base_v1", + "name": "bce-embedding-base_v1", + "capabilities": ["embedding"], + "ownedBy": "silicon" + }, + { + "id": "bce-reranker-base_v1", + "name": "bce-reranker-base_v1", + "capabilities": ["rerank"], + "ownedBy": "silicon" + }, + { + "id": "internlm2_5-chat", + "name": "internlm2_5-chat", + "ownedBy": "intern" + }, + { + "id": "glm-4-chat", + "name": "glm-4-chat", + "capabilities": ["function-call"], + "ownedBy": "zhipu" + }, + { + "id": "flux.1-schnell", + "name": "flux.1-schnell", + "capabilities": ["image-generation"], + "ownedBy": "bfl" + }, + { + "id": "fish-speech-1-4", + "name": "fish-speech-1-4", + "ownedBy": "silicon" + }, + { + "id": "gpt-sovits", + "name": "gpt-sovits", + "ownedBy": "openai" + }, + { + "id": "fish-speech-1-5", + "name": "fish-speech-1-5", + "ownedBy": "silicon" + }, + { + "id": "flux.1-pro", + "name": "flux.1-pro", + "capabilities": ["image-generation"], + "ownedBy": "bfl" + }, + { + "id": "seed-rice", + "name": "seed-rice", + "ownedBy": "bytedance" + }, + { + "id": "abab5-5-chat", + "name": "abab5-5-chat", + "ownedBy": "minimax" + }, + { + "id": "abab5-5s-chat", + "name": "abab5-5s-chat", + "ownedBy": "minimax" + }, + { + "id": "abab6-5g-chat", + "name": "abab6-5g-chat", + "ownedBy": "minimax" + }, + { + "id": "abab6-5s-chat", + "name": "abab6-5s-chat", + "ownedBy": "minimax" + }, + { + "id": "abab6-5t-chat", + "name": "abab6-5t-chat", + "ownedBy": "minimax" + }, + { + "id": "charglm-3", + "name": "charglm-3", + "ownedBy": "ocoolai" + }, + { + "id": "charglm-4", + "name": "charglm-4", + "ownedBy": "ocoolai" + }, + { + "id": "chatglm-pro", + "name": "chatglm-pro", + "ownedBy": "ocoolai" + }, + { + "id": "chirp-v3-0", + "name": "chirp-v3-0", + "ownedBy": "ocoolai" + }, + { + "id": "chirp-v3-5", + "name": "chirp-v3-5", + "ownedBy": "ocoolai" + }, + { + "id": "claude-3-5-haiku-20241022-cursor", + "name": "claude-3-5-haiku-20241022-cursor", + "capabilities": ["function-call", "image-recognition", "web-search"], + "ownedBy": "anthropic" + }, + { + "id": "claude-3-5-sonnet-all", + "name": "claude-3-5-sonnet-all", + "capabilities": ["function-call", "image-recognition", "web-search", "computer-use"], + "ownedBy": "anthropic" + }, + { + "id": "claude-3-7-sonnet-20250219-all", + "name": "claude-3-7-sonnet-20250219-all", + "capabilities": ["reasoning", "function-call", "image-recognition", "web-search", "computer-use"], + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 64000 + } + }, + "ownedBy": "anthropic" + }, + { + "id": "claude-3-7-sonnet-thinking-all", + "name": "claude-3-7-sonnet-thinking-all", + "capabilities": ["reasoning", "function-call", "image-recognition", "web-search", "computer-use"], + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 64000 + } + }, + "ownedBy": "anthropic" + }, + { + "id": "claude-3-haiku-20240307-cursor", + "name": "claude-3-haiku-20240307-cursor", + "capabilities": ["function-call", "image-recognition"], + "ownedBy": "anthropic" + }, + { + "id": "claude-opus-4-6-20260205", + "name": "claude-opus-4-6-20260205", + "capabilities": ["reasoning", "function-call", "image-recognition", "web-search", "computer-use"], + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 32000 + } + }, + "ownedBy": "anthropic" + }, + { + "id": "codegeex-4", + "name": "codegeex-4", + "ownedBy": "ocoolai" + }, + { + "id": "concise", + "name": "concise", + "ownedBy": "ocoolai" + }, + { + "id": "concise-scholar", + "name": "concise-scholar", + "ownedBy": "ocoolai" + }, + { + "id": "deepseek-chat-0324", + "name": "deepseek-chat-0324", + "capabilities": ["reasoning", "function-call"], + "reasoning": { + "supportedEfforts": ["none"] + }, + "ownedBy": "deepseek" + }, + { + "id": "deepseek-r1-250528", + "name": "deepseek-r1-250528", + "capabilities": ["reasoning", "function-call"], + "ownedBy": "deepseek" + }, + { + "id": "deepseek-reasoner-0528", + "name": "deepseek-reasoner-0528", + "capabilities": ["reasoning", "function-call"], + "ownedBy": "deepseek" + }, + { + "id": "deepseek-reasoner-v3-2", + "name": "deepseek-reasoner-v3-2", + "capabilities": ["reasoning", "function-call"], + "ownedBy": "deepseek" + }, + { + "id": "deepseek", + "name": "deepseek", + "capabilities": ["function-call"], + "ownedBy": "deepseek" + }, + { + "id": "deepseek-v3-1-250821", + "name": "deepseek-v3-1-250821", + "capabilities": ["reasoning", "function-call"], + "reasoning": { + "supportedEfforts": ["none"] + }, + "ownedBy": "deepseek" + }, + { + "id": "deepseek-v3-250324", + "name": "deepseek-v3-250324", + "capabilities": ["reasoning", "function-call"], + "reasoning": { + "supportedEfforts": ["none"] + }, + "ownedBy": "deepseek" + }, + { + "id": "detail", + "name": "detail", + "ownedBy": "ocoolai" + }, + { + "id": "detail-scholar", + "name": "detail-scholar", + "ownedBy": "ocoolai" + }, + { + "id": "doubao-1-5-lite-32k-250115", + "name": "doubao-1-5-lite-32k-250115", + "ownedBy": "bytedance" + }, + { + "id": "doubao-1-5-pro-32k-character-250715", + "name": "doubao-1-5-pro-32k-character-250715", + "ownedBy": "bytedance" + }, + { + "id": "doubao-1-5-thinking-pro-250415", + "name": "doubao-1-5-thinking-pro-250415", + "capabilities": ["reasoning"], + "reasoning": { + "supportedEfforts": ["none", "high"] + }, + "ownedBy": "bytedance" + }, + { + "id": "doubao-1-5-vision-pro-32k-250115", + "name": "doubao-1-5-vision-pro-32k-250115", + "capabilities": ["image-recognition"], + "ownedBy": "bytedance" + }, + { + "id": "doubao-1-5-vision-lite-250315", + "name": "doubao-1-5-vision-lite-250315", + "capabilities": ["image-recognition"], + "ownedBy": "bytedance" + }, + { + "id": "doubao-1-5-vision-pro-250328", + "name": "doubao-1-5-vision-pro-250328", + "capabilities": ["image-recognition"], + "ownedBy": "bytedance" + }, + { + "id": "doubao-embedding", + "name": "doubao-embedding", + "capabilities": ["embedding"], + "ownedBy": "bytedance" + }, + { + "id": "doubao-embedding-large-text-250515", + "name": "doubao-embedding-large-text-250515", + "capabilities": ["embedding"], + "ownedBy": "bytedance" + }, + { + "id": "doubao-embedding-vision-241215", + "name": "doubao-embedding-vision-241215", + "capabilities": ["embedding", "image-recognition"], + "ownedBy": "bytedance" + }, + { + "id": "doubao-embedding-vision-250328", + "name": "doubao-embedding-vision-250328", + "capabilities": ["embedding", "image-recognition"], + "ownedBy": "bytedance" + }, + { + "id": "doubao-embedding-vision-250615", + "name": "doubao-embedding-vision-250615", + "capabilities": ["embedding", "image-recognition"], + "ownedBy": "bytedance" + }, + { + "id": "doubao-seed-1-6-251015", + "name": "doubao-seed-1-6-251015", + "capabilities": ["reasoning", "function-call", "image-recognition"], + "reasoning": { + "supportedEfforts": ["minimal", "low", "medium", "high"] + }, + "ownedBy": "bytedance" + }, + { + "id": "doubao-seed-1-6-flash-250828", + "name": "doubao-seed-1-6-flash-250828", + "capabilities": ["reasoning", "function-call", "image-recognition"], + "ownedBy": "bytedance" + }, + { + "id": "doubao-seed-1-6-lite-251015", + "name": "doubao-seed-1-6-lite-251015", + "capabilities": ["reasoning", "function-call", "image-recognition"], + "reasoning": { + "supportedEfforts": ["minimal", "low", "medium", "high"] + }, + "ownedBy": "bytedance" + }, + { + "id": "doubao-seed-1-8-251228", + "name": "doubao-seed-1-8-251228", + "capabilities": ["reasoning", "function-call", "image-recognition"], + "reasoning": { + "supportedEfforts": ["minimal", "low", "medium", "high"] + }, + "ownedBy": "bytedance" + }, + { + "id": "doubao-seedream-4-0-250828", + "name": "doubao-seedream-4-0-250828", + "capabilities": ["function-call"], + "ownedBy": "bytedance" + }, + { + "id": "doubao-seedream-4-5-251128", + "name": "doubao-seedream-4-5-251128", + "capabilities": ["function-call"], + "ownedBy": "bytedance" + }, + { + "id": "doubao-tts", + "name": "doubao-tts", + "ownedBy": "bytedance" + }, + { + "id": "doubao-vision-pro-32k", + "name": "doubao-vision-pro-32k", + "capabilities": ["image-recognition"], + "ownedBy": "bytedance" + }, + { + "id": "embedding-001", + "name": "embedding-001", + "capabilities": ["embedding"], + "ownedBy": "ocoolai" + }, + { + "id": "embedding-gecko-001", + "name": "embedding-gecko-001", + "capabilities": ["embedding"], + "ownedBy": "ocoolai" + }, + { + "id": "emohaa", + "name": "emohaa", + "ownedBy": "ocoolai" + }, + { + "id": "ernie-3-5-128k", + "name": "ernie-3-5-128k", + "ownedBy": "baidu" + }, + { + "id": "ernie-3-5-8k", + "name": "ernie-3-5-8k", + "ownedBy": "baidu" + }, + { + "id": "ernie-3-5-8k-preview", + "name": "ernie-3-5-8k-preview", + "ownedBy": "baidu" + }, + { + "id": "ernie-4-0-8k", + "name": "ernie-4-0-8k", + "ownedBy": "baidu" + }, + { + "id": "ernie-4-0-8k-latest", + "name": "ernie-4-0-8k-latest", + "ownedBy": "baidu" + }, + { + "id": "ernie-4-0-8k-preview", + "name": "ernie-4-0-8k-preview", + "ownedBy": "baidu" + }, + { + "id": "ernie-4-0-turbo-128k", + "name": "ernie-4-0-turbo-128k", + "ownedBy": "baidu" + }, + { + "id": "ernie-4-0-turbo-8k", + "name": "ernie-4-0-turbo-8k", + "ownedBy": "baidu" + }, + { + "id": "ernie-4-0-turbo-8k-latest", + "name": "ernie-4-0-turbo-8k-latest", + "ownedBy": "baidu" + }, + { + "id": "ernie-4-0-turbo-8k-preview", + "name": "ernie-4-0-turbo-8k-preview", + "ownedBy": "baidu" + }, + { + "id": "ernie-4-5-turbo-128k", + "name": "ernie-4-5-turbo-128k", + "ownedBy": "baidu" + }, + { + "id": "ernie-4-5-turbo-32k", + "name": "ernie-4-5-turbo-32k", + "ownedBy": "baidu" + }, + { + "id": "ernie-character-8k", + "name": "ernie-character-8k", + "ownedBy": "baidu" + }, + { + "id": "ernie-character-fiction-8k", + "name": "ernie-character-fiction-8k", + "ownedBy": "baidu" + }, + { + "id": "ernie-lite-8k", + "name": "ernie-lite-8k", + "ownedBy": "baidu" + }, + { + "id": "ernie-lite-pro-128k", + "name": "ernie-lite-pro-128k", + "ownedBy": "baidu" + }, + { + "id": "ernie-novel-8k", + "name": "ernie-novel-8k", + "ownedBy": "baidu" + }, + { + "id": "ernie-speed-128k", + "name": "ernie-speed-128k", + "ownedBy": "baidu" + }, + { + "id": "ernie-speed-8k", + "name": "ernie-speed-8k", + "ownedBy": "baidu" + }, + { + "id": "ernie-speed-pro-128k", + "name": "ernie-speed-pro-128k", + "ownedBy": "baidu" + }, + { + "id": "ernie-tiny-8k", + "name": "ernie-tiny-8k", + "ownedBy": "baidu" + }, + { + "id": "flux", + "name": "flux", + "capabilities": ["image-generation"], + "ownedBy": "bfl" + }, + { + "id": "gemini-1-5-flash-exp-0827", + "name": "gemini-1-5-flash-exp-0827", + "capabilities": ["image-recognition"], + "ownedBy": "google" + }, + { + "id": "gemini-1-5-flash-latest", + "name": "gemini-1-5-flash-latest", + "capabilities": ["image-recognition"], + "ownedBy": "google" + }, + { + "id": "gemini-1-5-pro-001", + "name": "gemini-1-5-pro-001", + "capabilities": ["image-recognition"], + "ownedBy": "google" + }, + { + "id": "gemini-1-5-pro-002", + "name": "gemini-1-5-pro-002", + "capabilities": ["image-recognition"], + "ownedBy": "google" + }, + { + "id": "gemini-1-5-pro-exp-0827", + "name": "gemini-1-5-pro-exp-0827", + "capabilities": ["image-recognition"], + "ownedBy": "google" + }, + { + "id": "gemini-1-5-pro-latest", + "name": "gemini-1-5-pro-latest", + "capabilities": ["image-recognition"], + "ownedBy": "google" + }, + { + "id": "gemini-2-0-flash-lite-preview", + "name": "gemini-2-0-flash-lite-preview", + "capabilities": ["function-call", "image-recognition", "web-search"], + "ownedBy": "google" + }, + { + "id": "gemini-2-0-pro-exp", + "name": "gemini-2-0-pro-exp", + "capabilities": ["function-call", "image-recognition", "web-search"], + "ownedBy": "google" + }, + { + "id": "gemini-2-5-computer-use-preview-10-2025", + "name": "gemini-2-5-computer-use-preview-10-2025", + "capabilities": ["reasoning", "function-call", "image-recognition", "web-search", "computer-use"], + "ownedBy": "google" + }, + { + "id": "gemini-2-5-flash-ci", + "name": "gemini-2-5-flash-ci", + "capabilities": ["reasoning", "function-call", "image-recognition", "web-search"], + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 0, + "max": 24576 + } + }, + "ownedBy": "google" + }, + { + "id": "gemini-2-5-flash-preview-native-audio-dialog", + "name": "gemini-2-5-flash-preview-native-audio-dialog", + "capabilities": ["reasoning", "function-call", "image-recognition", "web-search"], + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 0, + "max": 24576 + } + }, + "ownedBy": "google" + }, + { + "id": "gemini-2-5-pro-ci", + "name": "gemini-2-5-pro-ci", + "capabilities": ["reasoning", "function-call", "image-recognition", "web-search"], + "reasoning": { + "supportedEfforts": ["low", "medium", "high"], + "thinkingTokenLimits": { + "min": 128, + "max": 32768 + } + }, + "ownedBy": "google" + }, + { + "id": "gemini-2-5-pro-preview-06-05-thinking-512", + "name": "gemini-2-5-pro-preview-06-05-thinking-512", + "capabilities": ["reasoning", "function-call", "image-recognition", "web-search"], + "reasoning": { + "supportedEfforts": ["low", "medium", "high"], + "thinkingTokenLimits": { + "min": 128, + "max": 32768 + } + }, + "ownedBy": "google" + }, + { + "id": "gemini-2-5-pro-thinking-128", + "name": "gemini-2-5-pro-thinking-128", + "capabilities": ["reasoning", "function-call", "image-recognition", "web-search"], + "reasoning": { + "supportedEfforts": ["low", "medium", "high"], + "thinkingTokenLimits": { + "min": 128, + "max": 32768 + } + }, + "ownedBy": "google" + }, + { + "id": "gemini-2-5-pro-thinking-512", + "name": "gemini-2-5-pro-thinking-512", + "capabilities": ["reasoning", "function-call", "image-recognition", "web-search"], + "reasoning": { + "supportedEfforts": ["low", "medium", "high"], + "thinkingTokenLimits": { + "min": 128, + "max": 32768 + } + }, + "ownedBy": "google" + }, + { + "id": "gemini-3-pro-image-preview-2k", + "name": "gemini-3-pro-image-preview-2k", + "capabilities": ["function-call", "image-recognition", "image-generation", "web-search"], + "ownedBy": "google" + }, + { + "id": "gemini-3-pro-image-preview-4k", + "name": "gemini-3-pro-image-preview-4k", + "capabilities": ["function-call", "image-recognition", "image-generation", "web-search"], + "ownedBy": "google" + }, + { + "id": "gemini-3-1-pro-preview-all", + "name": "gemini-3-1-pro-preview-all", + "capabilities": ["reasoning", "function-call"], + "reasoning": { + "supportedEfforts": ["low", "medium", "high"], + "thinkingTokenLimits": { + "min": 128, + "max": 32768 + } + }, + "ownedBy": "google" + }, + { + "id": "gemini-embedding-exp", + "name": "gemini-embedding-exp", + "capabilities": ["embedding"], + "ownedBy": "google" + }, + { + "id": "glm-4-air", + "name": "glm-4-air", + "capabilities": ["function-call"], + "ownedBy": "zhipu" + }, + { + "id": "glm-4-airx", + "name": "glm-4-airx", + "capabilities": ["function-call"], + "ownedBy": "zhipu" + }, + { + "id": "glm-4-flashx", + "name": "glm-4-flashx", + "capabilities": ["function-call"], + "ownedBy": "zhipu" + }, + { + "id": "glm-4-long", + "name": "glm-4-long", + "capabilities": ["function-call"], + "ownedBy": "zhipu" + }, + { + "id": "glm-4v-flash", + "name": "glm-4v-flash", + "capabilities": ["function-call", "image-recognition"], + "ownedBy": "zhipu" + }, + { + "id": "glm-z1-air", + "name": "glm-z1-air", + "ownedBy": "zhipu" + }, + { + "id": "glm-z1-airx", + "name": "glm-z1-airx", + "ownedBy": "zhipu" + }, + { + "id": "glm-z1-flash", + "name": "glm-z1-flash", + "ownedBy": "zhipu" + }, + { + "id": "gpt-3-5o", + "name": "gpt-3-5o", + "ownedBy": "openai" + }, + { + "id": "gpt-4-all", + "name": "gpt-4-all", + "capabilities": ["function-call", "image-recognition"], + "ownedBy": "openai" + }, + { + "id": "gpt-4-claude3-haiku-20240307", + "name": "gpt-4-claude3-haiku-20240307", + "capabilities": ["function-call", "image-recognition"], + "ownedBy": "openai" + }, + { + "id": "gpt-4-claude3-opus-20240229", + "name": "gpt-4-claude3-opus-20240229", + "capabilities": ["function-call", "image-recognition"], + "ownedBy": "openai" + }, + { + "id": "gpt-4-claude3-sonnet-20240229", + "name": "gpt-4-claude3-sonnet-20240229", + "capabilities": ["function-call", "image-recognition"], + "ownedBy": "openai" + }, + { + "id": "gpt-4-claude3-5-haiku-20241022", + "name": "gpt-4-claude3-5-haiku-20241022", + "capabilities": ["function-call", "image-recognition"], + "ownedBy": "openai" + }, + { + "id": "gpt-4-claude3-5-sonnet-20240620", + "name": "gpt-4-claude3-5-sonnet-20240620", + "capabilities": ["function-call", "image-recognition"], + "ownedBy": "openai" + }, + { + "id": "gpt-4-claude3-5-sonnet-20241022", + "name": "gpt-4-claude3-5-sonnet-20241022", + "capabilities": ["function-call", "image-recognition"], + "ownedBy": "openai" + }, + { + "id": "gpt-4-claude3-5-sonnet-all", + "name": "gpt-4-claude3-5-sonnet-all", + "capabilities": ["function-call", "image-recognition"], + "ownedBy": "openai" + }, + { + "id": "gpt-4-claude3-7-sonnet-20250219", + "name": "gpt-4-claude3-7-sonnet-20250219", + "capabilities": ["function-call", "image-recognition"], + "ownedBy": "openai" + }, + { + "id": "gpt-4-gizmo-*", + "name": "gpt-4-gizmo-*", + "capabilities": ["function-call", "image-recognition"], + "ownedBy": "openai" + }, + { + "id": "gpt-4-grok-3-all", + "name": "gpt-4-grok-3-all", + "capabilities": ["function-call", "image-recognition", "web-search"], + "ownedBy": "openai" + }, + { + "id": "gpt-4-1-2025-04-14", + "name": "gpt-4-1-2025-04-14", + "capabilities": ["function-call", "image-recognition", "web-search"], + "ownedBy": "openai" + }, + { + "id": "gpt-4-1-nano-2025-04-14", + "name": "gpt-4-1-nano-2025-04-14", + "capabilities": ["function-call", "image-recognition"], + "ownedBy": "openai" + }, + { + "id": "gpt-4-5-preview", + "name": "gpt-4-5-preview", + "capabilities": ["function-call", "web-search"], + "ownedBy": "openai" + }, + { + "id": "gpt-4-5-preview-2025-02-27", + "name": "gpt-4-5-preview-2025-02-27", + "capabilities": ["function-call", "web-search"], + "ownedBy": "openai" + }, + { + "id": "gpt-4o-all", + "name": "gpt-4o-all", + "capabilities": ["function-call", "image-recognition", "web-search"], + "ownedBy": "openai" + }, + { + "id": "gpt-4o-audio-preview-2024-10-01", + "name": "gpt-4o-audio-preview-2024-10-01", + "capabilities": ["function-call", "image-recognition", "web-search"], + "ownedBy": "openai" + }, + { + "id": "gpt-4o-audio-preview-2024-12-17", + "name": "gpt-4o-audio-preview-2024-12-17", + "capabilities": ["function-call", "image-recognition", "web-search"], + "ownedBy": "openai" + }, + { + "id": "gpt-4o-lite", + "name": "gpt-4o-lite", + "capabilities": ["function-call", "image-recognition", "web-search"], + "ownedBy": "openai" + }, + { + "id": "gpt-4o-mini-audio-preview-2024-12-17", + "name": "gpt-4o-mini-audio-preview-2024-12-17", + "capabilities": ["function-call", "image-recognition", "web-search"], + "ownedBy": "openai" + }, + { + "id": "gpt-4o-mini-realtime-preview", + "name": "gpt-4o-mini-realtime-preview", + "capabilities": ["function-call", "image-recognition", "web-search"], + "ownedBy": "openai" + }, + { + "id": "gpt-4o-mini-realtime-preview-2024-12-17", + "name": "gpt-4o-mini-realtime-preview-2024-12-17", + "capabilities": ["function-call", "image-recognition", "web-search"], + "ownedBy": "openai" + }, + { + "id": "gpt-4o-mini-transcribe", + "name": "gpt-4o-mini-transcribe", + "capabilities": ["function-call", "image-recognition", "web-search"], + "ownedBy": "openai" + }, + { + "id": "gpt-4o-mini-transcribe-2025-03-20", + "name": "gpt-4o-mini-transcribe-2025-03-20", + "capabilities": ["function-call", "image-recognition", "web-search"], + "ownedBy": "openai" + }, + { + "id": "gpt-4o-mini-tts-1", + "name": "gpt-4o-mini-tts-1", + "capabilities": ["audio-generation"], + "ownedBy": "openai" + }, + { + "id": "gpt-4o-mini-tts-2025-03-20", + "name": "gpt-4o-mini-tts-2025-03-20", + "capabilities": ["audio-generation"], + "ownedBy": "openai" + }, + { + "id": "gpt-4o-realtime-preview", + "name": "gpt-4o-realtime-preview", + "capabilities": ["function-call", "image-recognition", "web-search"], + "ownedBy": "openai" + }, + { + "id": "gpt-4o-realtime-preview-2024-10-01", + "name": "gpt-4o-realtime-preview-2024-10-01", + "capabilities": ["function-call", "image-recognition", "web-search"], + "ownedBy": "openai" + }, + { + "id": "gpt-4o-realtime-preview-2024-12-17", + "name": "gpt-4o-realtime-preview-2024-12-17", + "capabilities": ["function-call", "image-recognition", "web-search"], + "ownedBy": "openai" + }, + { + "id": "gpt-4o-realtime-preview-2025-06-03", + "name": "gpt-4o-realtime-preview-2025-06-03", + "capabilities": ["function-call", "image-recognition", "web-search"], + "ownedBy": "openai" + }, + { + "id": "gpt-4o-transcribe", + "name": "gpt-4o-transcribe", + "capabilities": ["function-call", "image-recognition", "web-search"], + "ownedBy": "openai" + }, + { + "id": "gpt-4o-transcribe-2025-03-20", + "name": "gpt-4o-transcribe-2025-03-20", + "capabilities": ["function-call", "image-recognition", "web-search"], + "ownedBy": "openai" + }, + { + "id": "gpt-5-2025-08-07", + "name": "gpt-5-2025-08-07", + "capabilities": ["function-call", "image-recognition", "web-search", "reasoning"], + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high", "max"] + }, + "ownedBy": "openai" + }, + { + "id": "gpt-5-all", + "name": "gpt-5-all", + "capabilities": ["function-call", "image-recognition", "web-search", "reasoning"], + "reasoning": { + "supportedEfforts": ["minimal", "low", "medium", "high"] + }, + "ownedBy": "openai" + }, + { + "id": "gpt-5-chat-2025-08-07", + "name": "gpt-5-chat-2025-08-07", + "capabilities": ["image-recognition", "web-search", "reasoning"], + "reasoning": { + "supportedEfforts": ["minimal", "low", "medium", "high"] + }, + "ownedBy": "openai" + }, + { + "id": "gpt-5-mini-2025-08-07", + "name": "gpt-5-mini-2025-08-07", + "capabilities": ["function-call", "image-recognition", "web-search", "reasoning"], + "reasoning": { + "supportedEfforts": ["minimal", "low", "medium", "high"] + }, + "ownedBy": "openai" + }, + { + "id": "gpt-5-nano-2025-08-07", + "name": "gpt-5-nano-2025-08-07", + "capabilities": ["function-call", "image-recognition", "web-search", "reasoning"], + "reasoning": { + "supportedEfforts": ["minimal", "low", "medium", "high"] + }, + "ownedBy": "openai" + }, + { + "id": "gpt-5-pro-2025-10-06", + "name": "gpt-5-pro-2025-10-06", + "capabilities": ["function-call", "image-recognition", "web-search", "reasoning"], + "reasoning": { + "supportedEfforts": ["high"] + }, + "ownedBy": "openai" + }, + { + "id": "gpt-5-thinking-all", + "name": "gpt-5-thinking-all", + "capabilities": ["reasoning", "function-call", "image-recognition", "web-search"], + "reasoning": { + "supportedEfforts": ["minimal", "low", "medium", "high"] + }, + "ownedBy": "openai" + }, + { + "id": "gpt-5-1-2025-11-13", + "name": "gpt-5-1-2025-11-13", + "capabilities": ["function-call", "image-recognition", "web-search", "reasoning"], + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"] + }, + "ownedBy": "openai" + }, + { + "id": "gpt-5-2-2025-12-11", + "name": "gpt-5-2-2025-12-11", + "capabilities": ["function-call", "image-recognition", "web-search", "reasoning"], + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high", "max"] + }, + "ownedBy": "openai" + }, + { + "id": "gpt-oss-1", + "name": "gpt-oss-1", + "capabilities": ["reasoning"], + "reasoning": { + "supportedEfforts": ["low", "medium", "high"] + }, + "ownedBy": "openai" + }, + { + "id": "grok-3-all", + "name": "grok-3-all", + "capabilities": ["function-call", "web-search"], + "ownedBy": "xai" + }, + { + "id": "grok-3-deepersearch", + "name": "grok-3-deepersearch", + "capabilities": ["function-call", "web-search"], + "ownedBy": "xai" + }, + { + "id": "grok-3-deepsearch", + "name": "grok-3-deepsearch", + "capabilities": ["function-call", "web-search"], + "ownedBy": "xai" + }, + { + "id": "grok-3-deepsearch-all", + "name": "grok-3-deepsearch-all", + "capabilities": ["function-call", "web-search"], + "ownedBy": "xai" + }, + { + "id": "grok-3-image", + "name": "grok-3-image", + "capabilities": ["function-call", "image-generation", "web-search"], + "ownedBy": "xai" + }, + { + "id": "grok-3-reasoner", + "name": "grok-3-reasoner", + "capabilities": ["reasoning", "function-call", "web-search"], + "ownedBy": "xai" + }, + { + "id": "grok-3-reasoner-all", + "name": "grok-3-reasoner-all", + "capabilities": ["reasoning", "function-call", "web-search"], + "ownedBy": "xai" + }, + { + "id": "gte-rerank", + "name": "gte-rerank", + "capabilities": ["embedding", "rerank"], + "ownedBy": "alibaba" + }, + { + "id": "hailuo", + "name": "hailuo", + "ownedBy": "ocoolai" + }, + { + "id": "hunyuan-code", + "name": "hunyuan-code", + "capabilities": ["function-call"], + "ownedBy": "tencent" + }, + { + "id": "hunyuan-embedding", + "name": "hunyuan-embedding", + "capabilities": ["function-call", "embedding"], + "ownedBy": "tencent" + }, + { + "id": "hunyuan-functioncall", + "name": "hunyuan-functioncall", + "capabilities": ["function-call"], + "ownedBy": "tencent" + }, + { + "id": "hunyuan-large", + "name": "hunyuan-large", + "capabilities": ["function-call"], + "ownedBy": "tencent" + }, + { + "id": "hunyuan-large-longcontext", + "name": "hunyuan-large-longcontext", + "capabilities": ["function-call"], + "ownedBy": "tencent" + }, + { + "id": "hunyuan-lite", + "name": "hunyuan-lite", + "capabilities": ["function-call"], + "ownedBy": "tencent" + }, + { + "id": "hunyuan-pro", + "name": "hunyuan-pro", + "capabilities": ["function-call"], + "ownedBy": "tencent" + }, + { + "id": "hunyuan-role", + "name": "hunyuan-role", + "capabilities": ["function-call"], + "ownedBy": "tencent" + }, + { + "id": "hunyuan-standard", + "name": "hunyuan-standard", + "capabilities": ["function-call"], + "ownedBy": "tencent" + }, + { + "id": "hunyuan-t1-20250321", + "name": "hunyuan-t1-20250321", + "capabilities": ["reasoning", "function-call"], + "ownedBy": "tencent" + }, + { + "id": "hunyuan-t1-latest", + "name": "hunyuan-t1-latest", + "capabilities": ["reasoning", "function-call"], + "ownedBy": "tencent" + }, + { + "id": "hunyuan-turbo", + "name": "hunyuan-turbo", + "capabilities": ["function-call"], + "ownedBy": "tencent" + }, + { + "id": "hunyuan-turbo-vision", + "name": "hunyuan-turbo-vision", + "capabilities": ["function-call", "image-recognition"], + "ownedBy": "tencent" + }, + { + "id": "hunyuan-turbos-20250226", + "name": "hunyuan-turbos-20250226", + "capabilities": ["function-call"], + "ownedBy": "tencent" + }, + { + "id": "hunyuan-turbos-20250313", + "name": "hunyuan-turbos-20250313", + "capabilities": ["function-call"], + "ownedBy": "tencent" + }, + { + "id": "hunyuan-turbos-latest", + "name": "hunyuan-turbos-latest", + "capabilities": ["function-call"], + "ownedBy": "tencent" + }, + { + "id": "hunyuan-vision", + "name": "hunyuan-vision", + "capabilities": ["function-call", "image-recognition"], + "ownedBy": "tencent" + }, + { + "id": "jimeng-3-0", + "name": "jimeng-3-0", + "ownedBy": "ocoolai" + }, + { + "id": "jina-clip-v1", + "name": "jina-clip-v1", + "ownedBy": "jina" + }, + { + "id": "jina-colbert-v1-en", + "name": "jina-colbert-v1-en", + "ownedBy": "jina" + }, + { + "id": "jina-embeddings-v2-base-de", + "name": "jina-embeddings-v2-base-de", + "capabilities": ["embedding"], + "ownedBy": "jina" + }, + { + "id": "jina-embeddings-v2-base-en", + "name": "jina-embeddings-v2-base-en", + "capabilities": ["embedding"], + "ownedBy": "jina" + }, + { + "id": "jina-embeddings-v2-base-es", + "name": "jina-embeddings-v2-base-es", + "capabilities": ["embedding"], + "ownedBy": "jina" + }, + { + "id": "jina-embeddings-v2-base-zh", + "name": "jina-embeddings-v2-base-zh", + "capabilities": ["embedding"], + "ownedBy": "jina" + }, + { + "id": "jina-reranker-v1-base-en", + "name": "jina-reranker-v1-base-en", + "capabilities": ["rerank"], + "ownedBy": "jina" + }, + { + "id": "jina-reranker-v1-tiny-en", + "name": "jina-reranker-v1-tiny-en", + "capabilities": ["rerank"], + "ownedBy": "jina" + }, + { + "id": "jina-reranker-v1-turbo-en", + "name": "jina-reranker-v1-turbo-en", + "capabilities": ["rerank"], + "ownedBy": "jina" + }, + { + "id": "jina-reranker-v2-base-multilingual", + "name": "jina-reranker-v2-base-multilingual", + "capabilities": ["rerank"], + "ownedBy": "jina" + }, + { + "id": "kat-dev-exp", + "name": "kat-dev-exp", + "ownedBy": "streamlake" + }, + { + "id": "kimi-k2-250905", + "name": "kimi-k2-250905", + "capabilities": ["function-call"], + "ownedBy": "moonshot" + }, + { + "id": "kimi-latest", + "name": "kimi-latest", + "capabilities": ["image-recognition"], + "ownedBy": "moonshot" + }, + { + "id": "luma-video", + "name": "luma-video", + "capabilities": ["video-generation"], + "ownedBy": "ocoolai" + }, + { + "id": "llama-4", + "name": "llama-4", + "capabilities": ["function-call", "image-recognition"], + "ownedBy": "meta" + }, + { + "id": "midjourney", + "name": "midjourney", + "capabilities": ["image-generation"], + "ownedBy": "ocoolai" + }, + { + "id": "mj-chat", + "name": "mj-chat", + "ownedBy": "ocoolai" + }, + { + "id": "nano-banana-2", + "name": "nano-banana-2", + "ownedBy": "google" + }, + { + "id": "net-glm-3-turbo", + "name": "net-glm-3-turbo", + "ownedBy": "ocoolai" + }, + { + "id": "net-gpt-3-5-turbo", + "name": "net-gpt-3-5-turbo", + "ownedBy": "ocoolai" + }, + { + "id": "net-gpt-3-5-turbo-16k", + "name": "net-gpt-3-5-turbo-16k", + "ownedBy": "ocoolai" + }, + { + "id": "net-gpt-4", + "name": "net-gpt-4", + "ownedBy": "ocoolai" + }, + { + "id": "net-gpt-4-0125-preview", + "name": "net-gpt-4-0125-preview", + "capabilities": ["function-call"], + "ownedBy": "ocoolai" + }, + { + "id": "net-gpt-4-1106-preview", + "name": "net-gpt-4-1106-preview", + "capabilities": ["function-call"], + "ownedBy": "ocoolai" + }, + { + "id": "net-gpt-4-turbo-preview", + "name": "net-gpt-4-turbo-preview", + "capabilities": ["function-call"], + "ownedBy": "ocoolai" + }, + { + "id": "o1-all", + "name": "o1-all", + "capabilities": ["reasoning", "function-call", "image-recognition"], + "reasoning": { + "supportedEfforts": ["low", "medium", "high"] + }, + "ownedBy": "openai" + }, + { + "id": "o1-pro-all", + "name": "o1-pro-all", + "capabilities": ["reasoning", "function-call", "image-recognition"], + "reasoning": { + "supportedEfforts": ["low", "medium", "high"] + }, + "ownedBy": "openai" + }, + { + "id": "o3-2025-04-16", + "name": "o3-2025-04-16", + "capabilities": ["reasoning", "function-call", "image-recognition", "web-search"], + "ownedBy": "openai" + }, + { + "id": "o3-mini-2025-01-31", + "name": "o3-mini-2025-01-31", + "capabilities": ["reasoning", "function-call", "web-search"], + "reasoning": { + "supportedEfforts": ["low", "medium", "high"] + }, + "ownedBy": "openai" + }, + { + "id": "o3-mini-all", + "name": "o3-mini-all", + "capabilities": ["reasoning", "function-call", "web-search"], + "reasoning": { + "supportedEfforts": ["low", "medium", "high"] + }, + "ownedBy": "openai" + }, + { + "id": "o3-mini-high-all", + "name": "o3-mini-high-all", + "capabilities": ["reasoning", "function-call", "web-search"], + "reasoning": { + "supportedEfforts": ["low", "medium", "high"] + }, + "ownedBy": "openai" + }, + { + "id": "o3-pro-2025-06-10", + "name": "o3-pro-2025-06-10", + "capabilities": ["reasoning", "function-call", "image-recognition", "web-search"], + "reasoning": { + "supportedEfforts": ["low", "medium", "high"] + }, + "ownedBy": "openai" + }, + { + "id": "o4-mini-2025-04-16", + "name": "o4-mini-2025-04-16", + "capabilities": ["reasoning", "function-call", "image-recognition", "web-search"], + "reasoning": { + "supportedEfforts": ["low", "medium", "high"] + }, + "ownedBy": "openai" + }, + { + "id": "ocoolai-helper", + "name": "ocoolai-helper", + "ownedBy": "ocoolai" + }, + { + "id": "omni-moderation-2024-09-26", + "name": "omni-moderation-2024-09-26", + "ownedBy": "ocoolai" + }, + { + "id": "parse-pdf", + "name": "parse-pdf", + "ownedBy": "ocoolai" + }, + { + "id": "pika-text-to-video", + "name": "pika-text-to-video", + "capabilities": ["video-generation"], + "ownedBy": "ocoolai" + }, + { + "id": "qwen-chat", + "name": "qwen-chat", + "capabilities": ["function-call"], + "ownedBy": "alibaba" + }, + { + "id": "qwen-coder-plus", + "name": "qwen-coder-plus", + "capabilities": ["function-call"], + "ownedBy": "alibaba" + }, + { + "id": "qwen-coder-plus-latest", + "name": "qwen-coder-plus-latest", + "capabilities": ["function-call"], + "ownedBy": "alibaba" + }, + { + "id": "qwen-coder-turbo", + "name": "qwen-coder-turbo", + "capabilities": ["function-call"], + "ownedBy": "alibaba" + }, + { + "id": "qwen-coder-turbo-latest", + "name": "qwen-coder-turbo-latest", + "capabilities": ["function-call"], + "ownedBy": "alibaba" + }, + { + "id": "qwen-math-plus-latest", + "name": "qwen-math-plus-latest", + "capabilities": ["function-call"], + "ownedBy": "alibaba" + }, + { + "id": "qwen-math-turbo-latest", + "name": "qwen-math-turbo-latest", + "capabilities": ["function-call"], + "ownedBy": "alibaba" + }, + { + "id": "qwen-max-fast", + "name": "qwen-max-fast", + "capabilities": ["function-call"], + "ownedBy": "alibaba" + }, + { + "id": "qwen-vl-max-latest", + "name": "qwen-vl-max-latest", + "capabilities": ["function-call", "image-recognition"], + "ownedBy": "alibaba" + }, + { + "id": "qwen-vl-ocr-latest", + "name": "qwen-vl-ocr-latest", + "capabilities": ["function-call", "image-recognition", "file-input"], + "ownedBy": "alibaba" + }, + { + "id": "qwen-vl-plus-latest", + "name": "qwen-vl-plus-latest", + "capabilities": ["function-call", "image-recognition"], + "ownedBy": "alibaba" + }, + { + "id": "qwen1-5-chat", + "name": "qwen1-5-chat", + "capabilities": ["function-call"], + "ownedBy": "alibaba" + }, + { + "id": "qwen2-math-instruct", + "name": "qwen2-math-instruct", + "capabilities": ["function-call"], + "ownedBy": "alibaba" + }, + { + "id": "qwen3-coder-flash-2025-07-28", + "name": "qwen3-coder-flash-2025-07-28", + "capabilities": ["function-call"], + "ownedBy": "alibaba" + }, + { + "id": "research", + "name": "research", + "ownedBy": "ocoolai" + }, + { + "id": "research-scholar", + "name": "research-scholar", + "ownedBy": "ocoolai" + }, + { + "id": "sambert-v1", + "name": "sambert-v1", + "ownedBy": "ocoolai" + }, + { + "id": "search-gpts-chat", + "name": "search-gpts-chat", + "ownedBy": "ocoolai" + }, + { + "id": "seedream-4-0-250828", + "name": "seedream-4-0-250828", + "ownedBy": "bytedance" + }, + { + "id": "sora", + "name": "sora", + "capabilities": ["video-generation"], + "ownedBy": "openai" + }, + { + "id": "sparkdesk-4-0-ultra", + "name": "sparkdesk-4-0-ultra", + "ownedBy": "ocoolai" + }, + { + "id": "sparkdesk-lite", + "name": "sparkdesk-lite", + "ownedBy": "ocoolai" + }, + { + "id": "sparkdesk-max", + "name": "sparkdesk-max", + "ownedBy": "ocoolai" + }, + { + "id": "sparkdesk-max-32k", + "name": "sparkdesk-max-32k", + "ownedBy": "ocoolai" + }, + { + "id": "sparkdesk-pro", + "name": "sparkdesk-pro", + "ownedBy": "ocoolai" + }, + { + "id": "sparkdesk-pro-128k", + "name": "sparkdesk-pro-128k", + "ownedBy": "ocoolai" + }, + { + "id": "speech-01-hd", + "name": "speech-01-hd", + "ownedBy": "ocoolai" + }, + { + "id": "speech-01-turbo", + "name": "speech-01-turbo", + "ownedBy": "ocoolai" + }, + { + "id": "speech-02-hd", + "name": "speech-02-hd", + "ownedBy": "ocoolai" + }, + { + "id": "speech-02-turbo", + "name": "speech-02-turbo", + "ownedBy": "ocoolai" + }, + { + "id": "stable-diffusion", + "name": "stable-diffusion", + "capabilities": ["image-generation"], + "ownedBy": "stability" + }, + { + "id": "step-1-128k", + "name": "step-1-128k", + "ownedBy": "stepfun" + }, + { + "id": "step-1-256k", + "name": "step-1-256k", + "ownedBy": "stepfun" + }, + { + "id": "step-1-8k", + "name": "step-1-8k", + "ownedBy": "stepfun" + }, + { + "id": "step-1-flash", + "name": "step-1-flash", + "ownedBy": "stepfun" + }, + { + "id": "step-1-5v-mini", + "name": "step-1-5v-mini", + "ownedBy": "stepfun" + }, + { + "id": "step-1o-turbo-vision", + "name": "step-1o-turbo-vision", + "capabilities": ["image-recognition"], + "ownedBy": "stepfun" + }, + { + "id": "step-1o-vision-32k", + "name": "step-1o-vision-32k", + "capabilities": ["image-recognition"], + "ownedBy": "stepfun" + }, + { + "id": "step-1v-32k", + "name": "step-1v-32k", + "ownedBy": "stepfun" + }, + { + "id": "step-1v-8k", + "name": "step-1v-8k", + "ownedBy": "stepfun" + }, + { + "id": "step-2-16k-exp", + "name": "step-2-16k-exp", + "ownedBy": "stepfun" + }, + { + "id": "step-2-mini", + "name": "step-2-mini", + "ownedBy": "stepfun" + }, + { + "id": "step-r1-v-mini", + "name": "step-r1-v-mini", + "capabilities": ["reasoning"], + "ownedBy": "stepfun" + }, + { + "id": "step-tts-mini", + "name": "step-tts-mini", + "capabilities": ["audio-generation"], + "ownedBy": "stepfun" + }, + { + "id": "suno", + "name": "suno", + "ownedBy": "suno" + }, + { + "id": "suno-v3", + "name": "suno-v3", + "ownedBy": "suno" + }, + { + "id": "text-embedding-v2", + "name": "text-embedding-v2", + "capabilities": ["embedding"], + "ownedBy": "alibaba" + }, + { + "id": "text-embedding-v3", + "name": "text-embedding-v3", + "capabilities": ["embedding"], + "ownedBy": "alibaba" + }, + { + "id": "tts-hd-1", + "name": "tts-hd-1", + "capabilities": ["audio-generation"], + "ownedBy": "openai" + }, + { + "id": "url-analysis", + "name": "url-analysis", + "ownedBy": "ocoolai" + }, + { + "id": "yi-vision-v2", + "name": "yi-vision-v2", + "capabilities": ["image-recognition"], + "ownedBy": "01ai" + }, + { + "id": "seedance-get", + "name": "seedance-get", + "ownedBy": "bytedance" + }, + { + "id": "qwen-turbo-2024-02-06", + "name": "qwen-turbo-2024-02-06", + "capabilities": ["function-call", "reasoning"], + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 0, + "max": 38912 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "deepseek-v3-1-sn", + "name": "deepseek-v3-1-sn", + "capabilities": ["reasoning", "function-call"], + "reasoning": { + "supportedEfforts": ["none"] + }, + "ownedBy": "deepseek" + }, + { + "id": "mj_fast_blend", + "name": "mj_fast_blend", + "ownedBy": "dmxapi" + }, + { + "id": "mj_fast_upscale", + "name": "mj_fast_upscale", + "ownedBy": "dmxapi" + }, + { + "id": "qwen-mt-flash", + "name": "qwen-mt-flash", + "ownedBy": "alibaba" + }, + { + "id": "speech-2-5-turbo-preview", + "name": "speech-2-5-turbo-preview", + "ownedBy": "dmxapi" + }, + { + "id": "wan2-6-image", + "name": "wan2-6-image", + "capabilities": ["image-generation", "video-generation"], + "ownedBy": "alibaba" + }, + { + "id": "clh45think-20251001", + "name": "clh45think-20251001", + "ownedBy": "dmxapi" + }, + { + "id": "mj_turbo_upscale_creative", + "name": "mj_turbo_upscale_creative", + "ownedBy": "dmxapi" + }, + { + "id": "qwen-plus-2024-11-25", + "name": "qwen-plus-2024-11-25", + "capabilities": ["function-call", "reasoning"], + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 0, + "max": 81920 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "qwen-vl-plus-2024-08-09", + "name": "qwen-vl-plus-2024-08-09", + "capabilities": ["function-call", "image-recognition"], + "ownedBy": "alibaba" + }, + { + "id": "yi-spark", + "name": "yi-spark", + "ownedBy": "01ai" + }, + { + "id": "kling_multi_elements_add", + "name": "kling_multi_elements_add", + "capabilities": ["video-generation"], + "ownedBy": "kling" + }, + { + "id": "mj_fast_modal", + "name": "mj_fast_modal", + "ownedBy": "dmxapi" + }, + { + "id": "deepgeminiflash-liu", + "name": "deepgeminiflash-liu", + "ownedBy": "dmxapi" + }, + { + "id": "mj_relax_pic_reader", + "name": "mj_relax_pic_reader", + "ownedBy": "dmxapi" + }, + { + "id": "qwen3-5-plus-2026-02-15", + "name": "qwen3-5-plus-2026-02-15", + "capabilities": ["function-call", "reasoning"], + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 38912 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "mj_fast_upscale_2x", + "name": "mj_fast_upscale_2x", + "ownedBy": "dmxapi" + }, + { + "id": "mj_relax_zoom", + "name": "mj_relax_zoom", + "ownedBy": "dmxapi" + }, + { + "id": "mj_fast_inpaint", + "name": "mj_fast_inpaint", + "ownedBy": "dmxapi" + }, + { + "id": "vidu-get", + "name": "vidu-get", + "capabilities": ["video-generation"], + "ownedBy": "vidu" + }, + { + "id": "wan2-6-get", + "name": "wan2-6-get", + "capabilities": ["video-generation"], + "ownedBy": "alibaba" + }, + { + "id": "g-p-t-5-2", + "name": "g-p-t-5-2", + "ownedBy": "dmxapi" + }, + { + "id": "qwen-turbo-2025-02-11", + "name": "qwen-turbo-2025-02-11", + "capabilities": ["function-call", "reasoning"], + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 0, + "max": 38912 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "gpt-5-web", + "name": "gpt-5-web", + "capabilities": ["function-call", "image-recognition", "web-search", "reasoning"], + "reasoning": { + "supportedEfforts": ["minimal", "low", "medium", "high"] + }, + "ownedBy": "openai" + }, + { + "id": "minimax-hailuo-02", + "name": "minimax-hailuo-02", + "ownedBy": "minimax" + }, + { + "id": "yaya-duck-cute-180", + "name": "yaya-duck-cute-180", + "ownedBy": "dmxapi" + }, + { + "id": "mj_relax_pan", + "name": "mj_relax_pan", + "ownedBy": "dmxapi" + }, + { + "id": "clo20251101", + "name": "clo20251101", + "ownedBy": "dmxapi" + }, + { + "id": "gpt-5-2-pro-responses", + "name": "gpt-5-2-pro-responses", + "capabilities": ["function-call", "image-recognition", "web-search", "reasoning"], + "reasoning": { + "supportedEfforts": ["medium", "high", "max"] + }, + "ownedBy": "openai" + }, + { + "id": "minimax-hailuo-2-3-fast", + "name": "minimax-hailuo-2-3-fast", + "ownedBy": "minimax" + }, + { + "id": "mj_relax_modal", + "name": "mj_relax_modal", + "ownedBy": "dmxapi" + }, + { + "id": "mj_turbo_shorten", + "name": "mj_turbo_shorten", + "ownedBy": "dmxapi" + }, + { + "id": "somark", + "name": "somark", + "ownedBy": "dmxapi" + }, + { + "id": "mj_fast_high_variation", + "name": "mj_fast_high_variation", + "ownedBy": "dmxapi" + }, + { + "id": "deepgeminipro-liu", + "name": "deepgeminipro-liu", + "ownedBy": "dmxapi" + }, + { + "id": "mj_relax_edits", + "name": "mj_relax_edits", + "ownedBy": "dmxapi" + }, + { + "id": "qwen-vl-chat-v1", + "name": "qwen-vl-chat-v1", + "capabilities": ["function-call", "image-recognition"], + "ownedBy": "alibaba" + }, + { + "id": "abab7-chat-preview", + "name": "abab7-chat-preview", + "ownedBy": "minimax" + }, + { + "id": "kling_audio_video_to_audio", + "name": "kling_audio_video_to_audio", + "capabilities": ["video-generation"], + "ownedBy": "kling" + }, + { + "id": "g-p-t-5-1-codex", + "name": "g-p-t-5-1-codex", + "ownedBy": "dmxapi" + }, + { + "id": "kling_multi_elements_submit", + "name": "kling_multi_elements_submit", + "capabilities": ["video-generation"], + "ownedBy": "kling" + }, + { + "id": "m2-her", + "name": "m2-her", + "ownedBy": "dmxapi" + }, + { + "id": "deepseek-r1-plus", + "name": "deepseek-r1-plus", + "capabilities": ["reasoning", "function-call"], + "ownedBy": "deepseek" + }, + { + "id": "doubao-embedding-vision-251215", + "name": "doubao-embedding-vision-251215", + "capabilities": ["embedding", "image-recognition"], + "ownedBy": "bytedance" + }, + { + "id": "qwen-vl-max-2024-11-19", + "name": "qwen-vl-max-2024-11-19", + "capabilities": ["function-call", "image-recognition"], + "ownedBy": "alibaba" + }, + { + "id": "g3-flash-preview", + "name": "g3-flash-preview", + "ownedBy": "dmxapi" + }, + { + "id": "gui-plus", + "name": "gui-plus", + "ownedBy": "dmxapi" + }, + { + "id": "mj_fast_upscale_subtle", + "name": "mj_fast_upscale_subtle", + "ownedBy": "dmxapi" + }, + { + "id": "baichuan-m2-plus", + "name": "baichuan-m2-plus", + "capabilities": ["reasoning"], + "ownedBy": "baichuan" + }, + { + "id": "mj_turbo_prompt_analyzer", + "name": "mj_turbo_prompt_analyzer", + "ownedBy": "dmxapi" + }, + { + "id": "qwen-vl-max-2024-08-09", + "name": "qwen-vl-max-2024-08-09", + "capabilities": ["function-call", "image-recognition"], + "ownedBy": "alibaba" + }, + { + "id": "g-p-t-5-1", + "name": "g-p-t-5-1", + "ownedBy": "dmxapi" + }, + { + "id": "qwen-coder-plus-2024-11-06", + "name": "qwen-coder-plus-2024-11-06", + "capabilities": ["function-call"], + "ownedBy": "alibaba" + }, + { + "id": "qwen-math-plus-2024-09-19", + "name": "qwen-math-plus-2024-09-19", + "capabilities": ["function-call"], + "ownedBy": "alibaba" + }, + { + "id": "speech-2-6-hd", + "name": "speech-2-6-hd", + "ownedBy": "dmxapi" + }, + { + "id": "mj_turbo_upload", + "name": "mj_turbo_upload", + "ownedBy": "dmxapi" + }, + { + "id": "mj_turbo_video", + "name": "mj_turbo_video", + "capabilities": ["video-generation"], + "ownedBy": "dmxapi" + }, + { + "id": "deep-research", + "name": "deep-research", + "ownedBy": "dmxapi" + }, + { + "id": "ernie-lite-8k-0308", + "name": "ernie-lite-8k-0308", + "ownedBy": "baidu" + }, + { + "id": "qwen-audio-turbo-2024-12-04", + "name": "qwen-audio-turbo-2024-12-04", + "capabilities": ["function-call"], + "ownedBy": "alibaba" + }, + { + "id": "mj_turbo_custom_zoom", + "name": "mj_turbo_custom_zoom", + "ownedBy": "dmxapi" + }, + { + "id": "mj_turbo_edits", + "name": "mj_turbo_edits", + "ownedBy": "dmxapi" + }, + { + "id": "mj_turbo_prompt_analyzer_extended", + "name": "mj_turbo_prompt_analyzer_extended", + "ownedBy": "dmxapi" + }, + { + "id": "qwen-max-2024-09-19", + "name": "qwen-max-2024-09-19", + "capabilities": ["function-call"], + "ownedBy": "alibaba" + }, + { + "id": "doubao-seed-2-0-lite-260215", + "name": "doubao-seed-2-0-lite-260215", + "capabilities": ["function-call", "reasoning"], + "reasoning": { + "supportedEfforts": ["minimal", "low", "medium", "high"] + }, + "ownedBy": "bytedance" + }, + { + "id": "kling_audio_text_to_audio", + "name": "kling_audio_text_to_audio", + "capabilities": ["video-generation"], + "ownedBy": "kling" + }, + { + "id": "mj_relax_upscale_subtle", + "name": "mj_relax_upscale_subtle", + "ownedBy": "dmxapi" + }, + { + "id": "hunyuan-a13b", + "name": "hunyuan-a13b", + "capabilities": ["function-call", "reasoning"], + "reasoning": { + "supportedEfforts": ["none"] + }, + "ownedBy": "tencent" + }, + { + "id": "kling-text2video-get", + "name": "kling-text2video-get", + "capabilities": ["video-generation"], + "ownedBy": "kling" + }, + { + "id": "kling-v2-6-image2video", + "name": "kling-v2-6-image2video", + "capabilities": ["image-generation", "video-generation"], + "ownedBy": "kling" + }, + { + "id": "grok-4-1-non-thinking", + "name": "grok-4-1-non-thinking", + "capabilities": ["reasoning", "function-call", "image-recognition", "web-search"], + "ownedBy": "xai" + }, + { + "id": "mj_relax_imagine", + "name": "mj_relax_imagine", + "ownedBy": "dmxapi" + }, + { + "id": "mj_fast_variation", + "name": "mj_fast_variation", + "ownedBy": "dmxapi" + }, + { + "id": "mj_relax_prompt_analyzer", + "name": "mj_relax_prompt_analyzer", + "ownedBy": "dmxapi" + }, + { + "id": "mj_turbo_reroll", + "name": "mj_turbo_reroll", + "ownedBy": "dmxapi" + }, + { + "id": "kling_image_expand", + "name": "kling_image_expand", + "capabilities": ["image-generation", "video-generation"], + "ownedBy": "kling" + }, + { + "id": "qwen-audio-turbo-latest", + "name": "qwen-audio-turbo-latest", + "capabilities": ["function-call"], + "ownedBy": "alibaba" + }, + { + "id": "seedgeminipro-liu", + "name": "seedgeminipro-liu", + "ownedBy": "bytedance" + }, + { + "id": "hehe-tywd", + "name": "hehe-tywd", + "ownedBy": "dmxapi" + }, + { + "id": "multimodal-embedding-v1", + "name": "multimodal-embedding-v1", + "capabilities": ["embedding"], + "ownedBy": "dmxapi" + }, + { + "id": "minimax-clone-lastversion", + "name": "minimax-clone-lastversion", + "ownedBy": "minimax" + }, + { + "id": "qwen-turbo-2024-09-19", + "name": "qwen-turbo-2024-09-19", + "capabilities": ["function-call", "reasoning"], + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 0, + "max": 38912 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "qwen-vl-max-2024-10-30", + "name": "qwen-vl-max-2024-10-30", + "capabilities": ["function-call", "image-recognition"], + "ownedBy": "alibaba" + }, + { + "id": "clo-4-6", + "name": "clo-4-6", + "ownedBy": "dmxapi" + }, + { + "id": "kling_lip_sync", + "name": "kling_lip_sync", + "capabilities": ["video-generation"], + "ownedBy": "kling" + }, + { + "id": "mj_relax_custom_zoom", + "name": "mj_relax_custom_zoom", + "ownedBy": "dmxapi" + }, + { + "id": "mj_fast_pan", + "name": "mj_fast_pan", + "ownedBy": "dmxapi" + }, + { + "id": "mj_relax_upscale", + "name": "mj_relax_upscale", + "ownedBy": "dmxapi" + }, + { + "id": "qwen-plus-2024-11-27", + "name": "qwen-plus-2024-11-27", + "capabilities": ["function-call", "reasoning"], + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 0, + "max": 81920 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "gemini-3-pro-image-preview-dfsx-0-3", + "name": "gemini-3-pro-image-preview-dfsx-0-3", + "capabilities": ["function-call", "image-recognition", "image-generation", "web-search"], + "ownedBy": "google" + }, + { + "id": "gpt-image-1-dmx01", + "name": "gpt-image-1-dmx01", + "capabilities": ["image-generation"], + "ownedBy": "openai" + }, + { + "id": "mj_relax_video", + "name": "mj_relax_video", + "capabilities": ["video-generation"], + "ownedBy": "dmxapi" + }, + { + "id": "mj_turbo_variation", + "name": "mj_turbo_variation", + "ownedBy": "dmxapi" + }, + { + "id": "mj_fast_imagine", + "name": "mj_fast_imagine", + "ownedBy": "dmxapi" + }, + { + "id": "kling_extend", + "name": "kling_extend", + "capabilities": ["video-generation"], + "ownedBy": "kling" + }, + { + "id": "paiwo-picture", + "name": "paiwo-picture", + "ownedBy": "dmxapi" + }, + { + "id": "mj_relax_blend", + "name": "mj_relax_blend", + "ownedBy": "dmxapi" + }, + { + "id": "g-p-t-5-codex", + "name": "g-p-t-5-codex", + "ownedBy": "dmxapi" + }, + { + "id": "glm-4-0520", + "name": "glm-4-0520", + "capabilities": ["function-call"], + "ownedBy": "zhipu" + }, + { + "id": "clo-45", + "name": "clo-45", + "ownedBy": "dmxapi" + }, + { + "id": "cls45-0929", + "name": "cls45-0929", + "ownedBy": "dmxapi" + }, + { + "id": "mj_relax_background_eraser", + "name": "mj_relax_background_eraser", + "ownedBy": "dmxapi" + }, + { + "id": "mj_relax_high_variation", + "name": "mj_relax_high_variation", + "ownedBy": "dmxapi" + }, + { + "id": "doubao-seededit-3-0-i2i-250628", + "name": "doubao-seededit-3-0-i2i-250628", + "capabilities": ["function-call"], + "ownedBy": "bytedance" + }, + { + "id": "mj_fast_prompt_analyzer_extended", + "name": "mj_fast_prompt_analyzer_extended", + "ownedBy": "dmxapi" + }, + { + "id": "qwen-coder-turbo-2024-09-19", + "name": "qwen-coder-turbo-2024-09-19", + "capabilities": ["function-call"], + "ownedBy": "alibaba" + }, + { + "id": "viduq2-pro", + "name": "viduq2-pro", + "capabilities": ["video-generation"], + "ownedBy": "vidu" + }, + { + "id": "paiwo-v5-6-ttv", + "name": "paiwo-v5-6-ttv", + "ownedBy": "dmxapi" + }, + { + "id": "kling_multi_elements_init", + "name": "kling_multi_elements_init", + "capabilities": ["video-generation"], + "ownedBy": "kling" + }, + { + "id": "clh45-20251001", + "name": "clh45-20251001", + "ownedBy": "dmxapi" + }, + { + "id": "do-sd-wan", + "name": "do-sd-wan", + "capabilities": ["video-generation"], + "ownedBy": "dmxapi" + }, + { + "id": "qwen-plus-2024-12-20", + "name": "qwen-plus-2024-12-20", + "capabilities": ["function-call", "reasoning"], + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 0, + "max": 81920 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "speech-2-5-hd-preview", + "name": "speech-2-5-hd-preview", + "ownedBy": "dmxapi" + }, + { + "id": "yi-vision", + "name": "yi-vision", + "capabilities": ["image-recognition"], + "ownedBy": "01ai" + }, + { + "id": "mj_fast_upload", + "name": "mj_fast_upload", + "ownedBy": "dmxapi" + }, + { + "id": "grok-imagine-0-9", + "name": "grok-imagine-0-9", + "capabilities": ["web-search"], + "ownedBy": "xai" + }, + { + "id": "mj_fast_background_eraser", + "name": "mj_fast_background_eraser", + "ownedBy": "dmxapi" + }, + { + "id": "qwen-vl-max-2024-02-01", + "name": "qwen-vl-max-2024-02-01", + "capabilities": ["function-call", "image-recognition"], + "ownedBy": "alibaba" + }, + { + "id": "imagen4", + "name": "imagen4", + "capabilities": ["image-generation"], + "ownedBy": "google" + }, + { + "id": "mj_relax_variation", + "name": "mj_relax_variation", + "ownedBy": "dmxapi" + }, + { + "id": "tts-pro", + "name": "tts-pro", + "capabilities": ["audio-generation"], + "ownedBy": "openai" + }, + { + "id": "qwen-plus-2024-08-06", + "name": "qwen-plus-2024-08-06", + "capabilities": ["function-call", "reasoning"], + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 0, + "max": 81920 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "mj_fast_shorten", + "name": "mj_fast_shorten", + "ownedBy": "dmxapi" + }, + { + "id": "qwen-vl-v1", + "name": "qwen-vl-v1", + "capabilities": ["function-call", "image-recognition"], + "ownedBy": "alibaba" + }, + { + "id": "ernie-lite-8k-0922", + "name": "ernie-lite-8k-0922", + "ownedBy": "baidu" + }, + { + "id": "mj_turbo_zoom", + "name": "mj_turbo_zoom", + "ownedBy": "dmxapi" + }, + { + "id": "qh-4k", + "name": "qh-4k", + "ownedBy": "dmxapi" + }, + { + "id": "qwen-plus-2024-09-19", + "name": "qwen-plus-2024-09-19", + "capabilities": ["function-call", "reasoning"], + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 0, + "max": 81920 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "mj_relax_upscale_4x", + "name": "mj_relax_upscale_4x", + "ownedBy": "dmxapi" + }, + { + "id": "mj_turbo_imagine", + "name": "mj_turbo_imagine", + "ownedBy": "dmxapi" + }, + { + "id": "qwen-math-turbo-2024-09-19", + "name": "qwen-math-turbo-2024-09-19", + "capabilities": ["function-call"], + "ownedBy": "alibaba" + }, + { + "id": "minimax-clone-upload", + "name": "minimax-clone-upload", + "ownedBy": "minimax" + }, + { + "id": "mj_relax_upscale_2x", + "name": "mj_relax_upscale_2x", + "ownedBy": "dmxapi" + }, + { + "id": "mj_fast_custom_zoom", + "name": "mj_fast_custom_zoom", + "ownedBy": "dmxapi" + }, + { + "id": "mj_fast_zoom", + "name": "mj_fast_zoom", + "ownedBy": "dmxapi" + }, + { + "id": "mj_turbo_background_eraser", + "name": "mj_turbo_background_eraser", + "ownedBy": "dmxapi" + }, + { + "id": "chat-seedream-3-0", + "name": "chat-seedream-3-0", + "ownedBy": "dmxapi" + }, + { + "id": "gpt-5-1-cfi", + "name": "gpt-5-1-cfi", + "capabilities": ["function-call", "image-recognition", "web-search", "reasoning"], + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"] + }, + "ownedBy": "openai" + }, + { + "id": "mj_turbo_inpaint", + "name": "mj_turbo_inpaint", + "ownedBy": "dmxapi" + }, + { + "id": "qwen-plus-2024-06-24", + "name": "qwen-plus-2024-06-24", + "capabilities": ["function-call", "reasoning"], + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 0, + "max": 81920 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "viduq2-ctv", + "name": "viduq2-ctv", + "capabilities": ["video-generation"], + "ownedBy": "vidu" + }, + { + "id": "mj_fast_upscale_creative", + "name": "mj_fast_upscale_creative", + "ownedBy": "dmxapi" + }, + { + "id": "baichuan-m3-plus", + "name": "baichuan-m3-plus", + "capabilities": ["reasoning"], + "ownedBy": "baichuan" + }, + { + "id": "dmxapl-cls45-0929", + "name": "dmxapl-cls45-0929", + "ownedBy": "dmxapi" + }, + { + "id": "kling_virtual_try_on", + "name": "kling_virtual_try_on", + "capabilities": ["video-generation"], + "ownedBy": "kling" + }, + { + "id": "speech-2-6-turbo", + "name": "speech-2-6-turbo", + "ownedBy": "dmxapi" + }, + { + "id": "speech-2-8-hd", + "name": "speech-2-8-hd", + "ownedBy": "dmxapi" + }, + { + "id": "minimax_minimax-hailuo-02", + "name": "minimax_minimax-hailuo-02", + "ownedBy": "minimax" + }, + { + "id": "music-2-0", + "name": "music-2-0", + "ownedBy": "dmxapi" + }, + { + "id": "qwen-vl-ocr-2024-10-28", + "name": "qwen-vl-ocr-2024-10-28", + "capabilities": ["function-call", "image-recognition", "file-input"], + "ownedBy": "alibaba" + }, + { + "id": "glm-4-alltools", + "name": "glm-4-alltools", + "capabilities": ["function-call"], + "ownedBy": "zhipu" + }, + { + "id": "musesteamer-air-image", + "name": "musesteamer-air-image", + "capabilities": ["image-generation"], + "ownedBy": "dmxapi" + }, + { + "id": "qvq-max-2025-03-25", + "name": "qvq-max-2025-03-25", + "capabilities": ["reasoning"], + "ownedBy": "alibaba" + }, + { + "id": "paiwo-get", + "name": "paiwo-get", + "ownedBy": "dmxapi" + }, + { + "id": "wan2-6-r2v", + "name": "wan2-6-r2v", + "capabilities": ["reasoning", "video-generation"], + "ownedBy": "alibaba" + }, + { + "id": "kling_multi_image2image", + "name": "kling_multi_image2image", + "capabilities": ["image-generation", "video-generation"], + "ownedBy": "kling" + }, + { + "id": "mj_turbo_pic_reader", + "name": "mj_turbo_pic_reader", + "ownedBy": "dmxapi" + }, + { + "id": "mj_turbo_upscale_subtle", + "name": "mj_turbo_upscale_subtle", + "ownedBy": "dmxapi" + }, + { + "id": "deepseek-r1-long", + "name": "deepseek-r1-long", + "capabilities": ["reasoning", "function-call"], + "ownedBy": "deepseek" + }, + { + "id": "g-l-m-4-6", + "name": "g-l-m-4-6", + "ownedBy": "dmxapi" + }, + { + "id": "doubao-seed-2-0-mini-260215", + "name": "doubao-seed-2-0-mini-260215", + "capabilities": ["function-call", "reasoning"], + "reasoning": { + "supportedEfforts": ["minimal", "low", "medium", "high"] + }, + "ownedBy": "bytedance" + }, + { + "id": "mj_fast_describe", + "name": "mj_fast_describe", + "ownedBy": "dmxapi" + }, + { + "id": "music-2-5", + "name": "music-2-5", + "ownedBy": "dmxapi" + }, + { + "id": "doubao-seed-2-0-code-preview-260215", + "name": "doubao-seed-2-0-code-preview-260215", + "capabilities": ["function-call", "reasoning"], + "reasoning": { + "supportedEfforts": ["minimal", "low", "medium", "high"] + }, + "ownedBy": "bytedance" + }, + { + "id": "doubao-seed-2-0-pro-260215", + "name": "doubao-seed-2-0-pro-260215", + "capabilities": ["function-call", "reasoning"], + "reasoning": { + "supportedEfforts": ["minimal", "low", "medium", "high"] + }, + "ownedBy": "bytedance" + }, + { + "id": "minimax-m2-5-guan", + "name": "minimax-m2-5-guan", + "capabilities": ["reasoning", "function-call"], + "reasoning": { + "supportedEfforts": [], + "interleaved": true + }, + "ownedBy": "minimax" + }, + { + "id": "mj_relax_inpaint", + "name": "mj_relax_inpaint", + "ownedBy": "dmxapi" + }, + { + "id": "qwen-turbo-2024-06-24", + "name": "qwen-turbo-2024-06-24", + "capabilities": ["function-call", "reasoning"], + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 0, + "max": 38912 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "gpt-image-1-dmx00", + "name": "gpt-image-1-dmx00", + "capabilities": ["image-generation"], + "ownedBy": "openai" + }, + { + "id": "dmxapl-cls45-0929-sk", + "name": "dmxapl-cls45-0929-sk", + "ownedBy": "dmxapi" + }, + { + "id": "gpt-5-2-cdx", + "name": "gpt-5-2-cdx", + "capabilities": ["function-call", "image-recognition", "web-search", "reasoning"], + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high", "max"] + }, + "ownedBy": "openai" + }, + { + "id": "kling-image2video-get", + "name": "kling-image2video-get", + "capabilities": ["image-generation", "video-generation"], + "ownedBy": "kling" + }, + { + "id": "mj_relax_describe", + "name": "mj_relax_describe", + "ownedBy": "dmxapi" + }, + { + "id": "kling_multi_elements_clear", + "name": "kling_multi_elements_clear", + "capabilities": ["video-generation"], + "ownedBy": "kling" + }, + { + "id": "mj_relax_low_variation", + "name": "mj_relax_low_variation", + "ownedBy": "dmxapi" + }, + { + "id": "qwen-audio-turbo", + "name": "qwen-audio-turbo", + "capabilities": ["function-call"], + "ownedBy": "alibaba" + }, + { + "id": "g-p-t-5-2-codex", + "name": "g-p-t-5-2-codex", + "ownedBy": "dmxapi" + }, + { + "id": "kling_multi_elements_preview", + "name": "kling_multi_elements_preview", + "capabilities": ["video-generation"], + "ownedBy": "kling" + }, + { + "id": "hunyuan-2-0-instruct-20251111", + "name": "hunyuan-2-0-instruct-20251111", + "capabilities": ["function-call"], + "ownedBy": "tencent" + }, + { + "id": "minimax_t2v-01-director", + "name": "minimax_t2v-01-director", + "ownedBy": "minimax" + }, + { + "id": "mj_fast_video", + "name": "mj_fast_video", + "capabilities": ["video-generation"], + "ownedBy": "dmxapi" + }, + { + "id": "mj_fast_pic_reader", + "name": "mj_fast_pic_reader", + "ownedBy": "dmxapi" + }, + { + "id": "qwen-audio-turbo-2024-08-07", + "name": "qwen-audio-turbo-2024-08-07", + "capabilities": ["function-call"], + "ownedBy": "alibaba" + }, + { + "id": "hunyuan-standard-256k", + "name": "hunyuan-standard-256k", + "capabilities": ["function-call"], + "ownedBy": "tencent" + }, + { + "id": "qwen-math-plus-2024-08-16", + "name": "qwen-math-plus-2024-08-16", + "capabilities": ["function-call"], + "ownedBy": "alibaba" + }, + { + "id": "mj_fast_reroll", + "name": "mj_fast_reroll", + "ownedBy": "dmxapi" + }, + { + "id": "mj_turbo_blend", + "name": "mj_turbo_blend", + "ownedBy": "dmxapi" + }, + { + "id": "qwen-longcontext-chat", + "name": "qwen-longcontext-chat", + "capabilities": ["function-call"], + "ownedBy": "alibaba" + }, + { + "id": "qwen3-omni-flash-all", + "name": "qwen3-omni-flash-all", + "capabilities": ["function-call", "image-recognition", "reasoning"], + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 38912 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "kling_video", + "name": "kling_video", + "capabilities": ["video-generation"], + "ownedBy": "kling" + }, + { + "id": "minimax-hailuo-2-3", + "name": "minimax-hailuo-2-3", + "ownedBy": "minimax" + }, + { + "id": "mj_fast_upscale_4x", + "name": "mj_fast_upscale_4x", + "ownedBy": "dmxapi" + }, + { + "id": "paiwo-v5-6-itv2", + "name": "paiwo-v5-6-itv2", + "ownedBy": "dmxapi" + }, + { + "id": "baichuan-m3", + "name": "baichuan-m3", + "capabilities": ["reasoning"], + "ownedBy": "baichuan" + }, + { + "id": "deepseek-r1-250120", + "name": "deepseek-r1-250120", + "capabilities": ["reasoning", "function-call"], + "ownedBy": "deepseek" + }, + { + "id": "mj_fast_prompt_analyzer", + "name": "mj_fast_prompt_analyzer", + "ownedBy": "dmxapi" + }, + { + "id": "mj_fast_edits", + "name": "mj_fast_edits", + "ownedBy": "dmxapi" + }, + { + "id": "paiwo-v5-6-itv", + "name": "paiwo-v5-6-itv", + "ownedBy": "dmxapi" + }, + { + "id": "qwen2-audio-instruct", + "name": "qwen2-audio-instruct", + "capabilities": ["function-call"], + "ownedBy": "alibaba" + }, + { + "id": "gpt-5-nano-ssvip", + "name": "gpt-5-nano-ssvip", + "capabilities": ["function-call", "image-recognition", "web-search", "reasoning"], + "reasoning": { + "supportedEfforts": ["minimal", "low", "medium", "high"] + }, + "ownedBy": "openai" + }, + { + "id": "kling-v2-6-text2video", + "name": "kling-v2-6-text2video", + "capabilities": ["video-generation"], + "ownedBy": "kling" + }, + { + "id": "kling_image", + "name": "kling_image", + "capabilities": ["image-generation", "video-generation"], + "ownedBy": "kling" + }, + { + "id": "minimax_files_retrieve", + "name": "minimax_files_retrieve", + "ownedBy": "minimax" + }, + { + "id": "mj_relax_prompt_analyzer_extended", + "name": "mj_relax_prompt_analyzer_extended", + "ownedBy": "dmxapi" + }, + { + "id": "mj_relax_shorten", + "name": "mj_relax_shorten", + "ownedBy": "dmxapi" + }, + { + "id": "doubao-seedream-3-0-t2i-250415", + "name": "doubao-seedream-3-0-t2i-250415", + "capabilities": ["function-call"], + "ownedBy": "bytedance" + }, + { + "id": "gpt-5-2-pro-chat", + "name": "gpt-5-2-pro-chat", + "capabilities": ["function-call", "image-recognition", "web-search", "reasoning"], + "reasoning": { + "supportedEfforts": ["medium", "high", "max"] + }, + "ownedBy": "openai" + }, + { + "id": "g2-5-flash", + "name": "g2-5-flash", + "ownedBy": "dmxapi" + }, + { + "id": "hunyuan-2-0-thinking-20251109", + "name": "hunyuan-2-0-thinking-20251109", + "capabilities": ["reasoning", "function-call"], + "ownedBy": "tencent" + }, + { + "id": "mj_turbo_pan", + "name": "mj_turbo_pan", + "ownedBy": "dmxapi" + }, + { + "id": "wan2-6-t2i", + "name": "wan2-6-t2i", + "capabilities": ["video-generation"], + "ownedBy": "alibaba" + }, + { + "id": "clo45thinking-20251101", + "name": "clo45thinking-20251101", + "ownedBy": "dmxapi" + }, + { + "id": "huoshan-deepseek-r1-64k", + "name": "huoshan-deepseek-r1-64k", + "capabilities": ["reasoning", "function-call"], + "ownedBy": "dmxapi" + }, + { + "id": "doubao-seedance-1-5-pro-responses", + "name": "doubao-seedance-1-5-pro-responses", + "capabilities": ["function-call"], + "ownedBy": "bytedance" + }, + { + "id": "g-l-m-4-7", + "name": "g-l-m-4-7", + "ownedBy": "dmxapi" + }, + { + "id": "huoshan-deepseek-v3", + "name": "huoshan-deepseek-v3", + "capabilities": ["reasoning", "function-call"], + "ownedBy": "dmxapi" + }, + { + "id": "mj_turbo_upscale", + "name": "mj_turbo_upscale", + "ownedBy": "dmxapi" + }, + { + "id": "qwen-image-edit-plus", + "name": "qwen-image-edit-plus", + "capabilities": ["function-call", "image-generation"], + "ownedBy": "alibaba" + }, + { + "id": "qwen-plus-2024-07-23", + "name": "qwen-plus-2024-07-23", + "capabilities": ["function-call", "reasoning"], + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 0, + "max": 81920 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "qwen3-rerank", + "name": "qwen3-rerank", + "capabilities": ["rerank", "reasoning"], + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 38912 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "mj_relax_upscale_creative", + "name": "mj_relax_upscale_creative", + "ownedBy": "dmxapi" + }, + { + "id": "mj_turbo_describe", + "name": "mj_turbo_describe", + "ownedBy": "dmxapi" + }, + { + "id": "kling_multi_elements_delete", + "name": "kling_multi_elements_delete", + "capabilities": ["video-generation"], + "ownedBy": "kling" + }, + { + "id": "deepseek-v3-1-uc", + "name": "deepseek-v3-1-uc", + "capabilities": ["reasoning", "function-call"], + "reasoning": { + "supportedEfforts": ["none"] + }, + "ownedBy": "deepseek" + }, + { + "id": "qwen-audio-chat", + "name": "qwen-audio-chat", + "capabilities": ["function-call"], + "ownedBy": "alibaba" + }, + { + "id": "mj_turbo_modal", + "name": "mj_turbo_modal", + "ownedBy": "dmxapi" + }, + { + "id": "qwen-vl-plus-2023-12-01", + "name": "qwen-vl-plus-2023-12-01", + "capabilities": ["function-call", "image-recognition"], + "ownedBy": "alibaba" + }, + { + "id": "yi-medium-200k", + "name": "yi-medium-200k", + "ownedBy": "01ai" + }, + { + "id": "cl35s1022", + "name": "cl35s1022", + "ownedBy": "dmxapi" + }, + { + "id": "g3-pro-preview", + "name": "g3-pro-preview", + "ownedBy": "dmxapi" + }, + { + "id": "gpt-image-1-dmx03", + "name": "gpt-image-1-dmx03", + "capabilities": ["image-generation"], + "ownedBy": "openai" + }, + { + "id": "mj_turbo_upscale_2x", + "name": "mj_turbo_upscale_2x", + "ownedBy": "dmxapi" + }, + { + "id": "suno_music", + "name": "suno_music", + "ownedBy": "suno" + }, + { + "id": "minimax_s2v-01", + "name": "minimax_s2v-01", + "ownedBy": "minimax" + }, + { + "id": "suno_lyrics", + "name": "suno_lyrics", + "ownedBy": "suno" + }, + { + "id": "claude-code-1", + "name": "claude-code-1", + "capabilities": ["function-call"], + "ownedBy": "anthropic" + }, + { + "id": "gpt-image-1-dmx02", + "name": "gpt-image-1-dmx02", + "capabilities": ["image-generation"], + "ownedBy": "openai" + }, + { + "id": "mj_relax_upload", + "name": "mj_relax_upload", + "ownedBy": "dmxapi" + }, + { + "id": "baichuan2", + "name": "baichuan2", + "ownedBy": "baichuan" + }, + { + "id": "deepclaude-liu", + "name": "deepclaude-liu", + "ownedBy": "dmxapi" + }, + { + "id": "g2-5-pro", + "name": "g2-5-pro", + "ownedBy": "dmxapi" + }, + { + "id": "qwen-plus-2024-02-06", + "name": "qwen-plus-2024-02-06", + "capabilities": ["function-call", "reasoning"], + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 0, + "max": 81920 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "mj_relax_reroll", + "name": "mj_relax_reroll", + "ownedBy": "dmxapi" + }, + { + "id": "mj_turbo_low_variation", + "name": "mj_turbo_low_variation", + "ownedBy": "dmxapi" + }, + { + "id": "mj_turbo_upscale_4x", + "name": "mj_turbo_upscale_4x", + "ownedBy": "dmxapi" + }, + { + "id": "gemini-3-pro-image-preview-0-3", + "name": "gemini-3-pro-image-preview-0-3", + "capabilities": ["function-call", "image-recognition", "image-generation", "web-search"], + "ownedBy": "google" + }, + { + "id": "glm-4-1v-thinking-flash", + "name": "glm-4-1v-thinking-flash", + "capabilities": ["reasoning", "function-call"], + "ownedBy": "zhipu" + }, + { + "id": "mj_fast_low_variation", + "name": "mj_fast_low_variation", + "ownedBy": "dmxapi" + }, + { + "id": "mj_turbo_high_variation", + "name": "mj_turbo_high_variation", + "ownedBy": "dmxapi" + }, + { + "id": "suno_uploads", + "name": "suno_uploads", + "ownedBy": "suno" + }, + { + "id": "gpt-5-2-responses", + "name": "gpt-5-2-responses", + "capabilities": ["function-call", "image-recognition", "web-search", "reasoning"], + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high", "max"] + }, + "ownedBy": "openai" + }, + { + "id": "g-p-t-5", + "name": "g-p-t-5", + "ownedBy": "dmxapi" + }, + { + "id": "kling_effects", + "name": "kling_effects", + "capabilities": ["video-generation"], + "ownedBy": "kling" + }, + { + "id": "veo2", + "name": "veo2", + "capabilities": ["video-generation"], + "ownedBy": "google" + }, + { + "id": "t2v-01-director", + "name": "t2v-01-director", + "ownedBy": "aionly" + }, + { + "id": "doubao-seedance-1-0-pro", + "name": "doubao-seedance-1-0-pro", + "capabilities": ["function-call"], + "ownedBy": "bytedance" + }, + { + "id": "doubao-tts01", + "name": "doubao-tts01", + "ownedBy": "bytedance" + }, + { + "id": "gpt-o1", + "name": "gpt-o1", + "capabilities": ["function-call", "image-recognition"], + "ownedBy": "openai" + }, + { + "id": "minimax-s2v-01", + "name": "minimax-s2v-01", + "ownedBy": "minimax" + }, + { + "id": "claude-sonnet4", + "name": "claude-sonnet4", + "capabilities": ["function-call"], + "ownedBy": "anthropic" + }, + { + "id": "veo-3-0-generate-001", + "name": "veo-3-0-generate-001", + "capabilities": ["video-generation"], + "ownedBy": "google" + }, + { + "id": "ah-claude-sonnet4", + "name": "ah-claude-sonnet4", + "capabilities": ["function-call"], + "ownedBy": "aionly" + }, + { + "id": "seedream4-0", + "name": "seedream4-0", + "ownedBy": "bytedance" + }, + { + "id": "gemini-2-5-flash-image-text", + "name": "gemini-2-5-flash-image-text", + "capabilities": ["function-call", "image-recognition", "image-generation", "web-search", "reasoning"], + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 0, + "max": 24576 + } + }, + "ownedBy": "google" + }, + { + "id": "veo-3-0-fast-generate-001", + "name": "veo-3-0-fast-generate-001", + "capabilities": ["video-generation"], + "ownedBy": "google" + }, + { + "id": "gpt-5-codex-azure", + "name": "gpt-5-codex-azure", + "capabilities": ["function-call", "image-recognition", "web-search", "reasoning"], + "reasoning": { + "supportedEfforts": ["low", "medium", "high"] + }, + "ownedBy": "openai" + }, + { + "id": "gpt-5-pro-openai", + "name": "gpt-5-pro-openai", + "capabilities": ["function-call", "image-recognition", "web-search", "reasoning"], + "reasoning": { + "supportedEfforts": ["high"] + }, + "ownedBy": "openai" + }, + { + "id": "claude-sonnet-4-5-20250929-02", + "name": "claude-sonnet-4-5-20250929-02", + "capabilities": ["reasoning", "function-call", "image-recognition", "web-search", "computer-use"], + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 64000 + } + }, + "ownedBy": "anthropic" + }, + { + "id": "claude-haiku-4-5-20251001-02", + "name": "claude-haiku-4-5-20251001-02", + "capabilities": ["reasoning", "function-call", "image-recognition", "web-search", "computer-use"], + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 64000 + } + }, + "ownedBy": "anthropic" + }, + { + "id": "doubao-seedance-1-0-pro-fast", + "name": "doubao-seedance-1-0-pro-fast", + "capabilities": ["function-call"], + "ownedBy": "bytedance" + }, + { + "id": "gemini-3-pro-image-preview-e", + "name": "gemini-3-pro-image-preview-e", + "capabilities": ["function-call", "image-recognition", "image-generation", "web-search"], + "ownedBy": "google" + }, + { + "id": "gpt-image-1-2025-04", + "name": "gpt-image-1-2025-04", + "capabilities": ["image-generation"], + "ownedBy": "openai" + }, + { + "id": "gemini-3-pro-image-preview-text", + "name": "gemini-3-pro-image-preview-text", + "capabilities": ["function-call", "image-recognition", "image-generation", "web-search"], + "ownedBy": "google" + }, + { + "id": "claude-opus-4-5-20251101-02", + "name": "claude-opus-4-5-20251101-02", + "capabilities": ["reasoning", "function-call", "image-recognition", "web-search", "computer-use"], + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 64000 + } + }, + "ownedBy": "anthropic" + }, + { + "id": "doubao-seedance-1-5-pro", + "name": "doubao-seedance-1-5-pro", + "capabilities": ["function-call"], + "ownedBy": "bytedance" + }, + { + "id": "seed-tts-2-0", + "name": "seed-tts-2-0", + "capabilities": ["audio-generation"], + "ownedBy": "bytedance" + }, + { + "id": "gemini-3-pro-image-vip", + "name": "gemini-3-pro-image-vip", + "capabilities": ["function-call", "image-recognition", "image-generation", "web-search"], + "ownedBy": "google" + }, + { + "id": "wan2-6-t2i-vip", + "name": "wan2-6-t2i-vip", + "capabilities": ["video-generation"], + "ownedBy": "alibaba" + }, + { + "id": "qwen3-max-vip", + "name": "qwen3-max-vip", + "capabilities": ["function-call", "reasoning"], + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 0, + "max": 81920 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "vq2", + "name": "vq2", + "ownedBy": "aionly" + }, + { + "id": "vq2-turbo", + "name": "vq2-turbo", + "ownedBy": "aionly" + }, + { + "id": "vq2-pro", + "name": "vq2-pro", + "ownedBy": "aionly" + }, + { + "id": "qwen-image-max", + "name": "qwen-image-max", + "capabilities": ["function-call", "image-generation"], + "ownedBy": "alibaba" + }, + { + "id": "qwen-image-plus-vip", + "name": "qwen-image-plus-vip", + "capabilities": ["function-call", "image-generation"], + "ownedBy": "alibaba" + }, + { + "id": "qwen-image-edit-plus-vip", + "name": "qwen-image-edit-plus-vip", + "capabilities": ["function-call", "image-generation"], + "ownedBy": "alibaba" + }, + { + "id": "nemotron3-nano", + "name": "nemotron3-nano", + "ownedBy": "nvidia" + }, + { + "id": "claude-sonnet4-5", + "name": "claude-sonnet4-5", + "capabilities": ["function-call"], + "ownedBy": "anthropic" + }, + { + "id": "claude-opus4-5", + "name": "claude-opus4-5", + "capabilities": ["function-call"], + "ownedBy": "anthropic" + }, + { + "id": "claude-haiku4-5", + "name": "claude-haiku4-5", + "capabilities": ["function-call"], + "ownedBy": "anthropic" + }, + { + "id": "claude-opus4", + "name": "claude-opus4", + "capabilities": ["function-call"], + "ownedBy": "anthropic" + }, + { + "id": "claude-opus4-1", + "name": "claude-opus4-1", + "capabilities": ["function-call"], + "ownedBy": "anthropic" + }, + { + "id": "claude-haiku3-5", + "name": "claude-haiku3-5", + "capabilities": ["function-call"], + "ownedBy": "anthropic" + }, + { + "id": "gpt-o3", + "name": "gpt-o3", + "capabilities": ["function-call", "image-recognition", "web-search"], + "ownedBy": "openai" + }, + { + "id": "wan2-6-i2v-flash", + "name": "wan2-6-i2v-flash", + "capabilities": ["video-generation"], + "ownedBy": "alibaba" + }, + { + "id": "wan2-6-i2v-flash-vip", + "name": "wan2-6-i2v-flash-vip", + "capabilities": ["video-generation"], + "ownedBy": "alibaba" + }, + { + "id": "flux2-dev", + "name": "flux2-dev", + "capabilities": ["image-generation"], + "ownedBy": "bfl" + }, + { + "id": "claude-opus4-6", + "name": "claude-opus4-6", + "capabilities": ["function-call"], + "ownedBy": "anthropic" + }, + { + "id": "kld-o-4-6", + "name": "kld-o-4-6", + "ownedBy": "aionly" + }, + { + "id": "vq3-pro", + "name": "vq3-pro", + "ownedBy": "aionly" + }, + { + "id": "wan2-6-t2v-vip", + "name": "wan2-6-t2v-vip", + "capabilities": ["video-generation"], + "ownedBy": "alibaba" + }, + { + "id": "speech-2-8-turbo", + "name": "speech-2-8-turbo", + "ownedBy": "aionly" + }, + { + "id": "gemini-2-5-flash-preview-09-2025-thinking-*", + "name": "gemini-2-5-flash-preview-09-2025-thinking-*", + "capabilities": ["reasoning", "function-call", "image-recognition", "web-search"], + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 0, + "max": 24576 + } + }, + "ownedBy": "google" + }, + { + "id": "gemini-2-5-flash-thinking-*", + "name": "gemini-2-5-flash-thinking-*", + "capabilities": ["reasoning", "function-call", "image-recognition", "web-search"], + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 0, + "max": 24576 + } + }, + "ownedBy": "google" + }, + { + "id": "gemini-2-5-flash-thinking-24576", + "name": "gemini-2-5-flash-thinking-24576", + "capabilities": ["reasoning", "function-call", "image-recognition", "web-search"], + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 0, + "max": 24576 + } + }, + "ownedBy": "google" + }, + { + "id": "gemini-2-5-flash-thinking-512", + "name": "gemini-2-5-flash-thinking-512", + "capabilities": ["reasoning", "function-call", "image-recognition", "web-search"], + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 0, + "max": 24576 + } + }, + "ownedBy": "google" + }, + { + "id": "gemini-2-5-pro-thinking-*", + "name": "gemini-2-5-pro-thinking-*", + "capabilities": ["reasoning", "function-call", "image-recognition", "web-search"], + "reasoning": { + "supportedEfforts": ["low", "medium", "high"], + "thinkingTokenLimits": { + "min": 128, + "max": 32768 + } + }, + "ownedBy": "google" + }, + { + "id": "gemini-3-pro-image-preview-flatfee", + "name": "gemini-3-pro-image-preview-flatfee", + "capabilities": ["function-call", "image-recognition", "image-generation", "web-search"], + "ownedBy": "google" + }, + { + "id": "gpt-5-1-chat-2025-11-13", + "name": "gpt-5-1-chat-2025-11-13", + "capabilities": ["function-call", "image-recognition", "web-search", "reasoning"], + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"] + }, + "ownedBy": "openai" + }, + { + "id": "gpt-5-1-codex-mini-2025-11-13", + "name": "gpt-5-1-codex-mini-2025-11-13", + "capabilities": ["function-call", "image-recognition", "web-search", "reasoning"], + "reasoning": { + "supportedEfforts": ["none", "medium", "high"] + }, + "ownedBy": "openai" + }, + { + "id": "gpt-5-2-2025-12-11-none", + "name": "gpt-5-2-2025-12-11-none", + "capabilities": ["function-call", "image-recognition", "web-search", "reasoning"], + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high", "max"] + }, + "ownedBy": "openai" + }, + { + "id": "gpt-5-2-2025-12-11-xhigh", + "name": "gpt-5-2-2025-12-11-xhigh", + "capabilities": ["function-call", "image-recognition", "web-search", "reasoning"], + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high", "max"] + }, + "ownedBy": "openai" + }, + { + "id": "gpt-5-2-chat-2025-12-11", + "name": "gpt-5-2-chat-2025-12-11", + "capabilities": ["function-call", "image-recognition", "web-search", "reasoning"], + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high", "max"] + }, + "ownedBy": "openai" + }, + { + "id": "gpt-5-2-none", + "name": "gpt-5-2-none", + "capabilities": ["function-call", "image-recognition", "web-search", "reasoning"], + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high", "max"] + }, + "ownedBy": "openai" + }, + { + "id": "gpt-5-2-xhigh", + "name": "gpt-5-2-xhigh", + "capabilities": ["function-call", "image-recognition", "web-search", "reasoning"], + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high", "max"] + }, + "ownedBy": "openai" + }, + { + "id": "gpt-realtime-2025-08-28", + "name": "gpt-realtime-2025-08-28", + "ownedBy": "openai" + }, + { + "id": "babbage-002", + "name": "babbage-002", + "ownedBy": "openai" + }, + { + "id": "gemini-2-5-pro-flatfee", + "name": "gemini-2-5-pro-flatfee", + "capabilities": ["reasoning", "function-call", "image-recognition", "web-search"], + "reasoning": { + "supportedEfforts": ["low", "medium", "high"], + "thinkingTokenLimits": { + "min": 128, + "max": 32768 + } + }, + "ownedBy": "google" + }, + { + "id": "gpt-3-5-turbo-instruct-0914", + "name": "gpt-3-5-turbo-instruct-0914", + "ownedBy": "openai" + }, + { + "id": "gpt-35-turbo", + "name": "gpt-35-turbo", + "ownedBy": "openai" + }, + { + "id": "gpt-35-turbo-instruct", + "name": "gpt-35-turbo-instruct", + "ownedBy": "openai" + }, + { + "id": "gpt-4-dalle", + "name": "gpt-4-dalle", + "capabilities": ["function-call", "image-recognition"], + "ownedBy": "openai" + }, + { + "id": "gpt-4-1-mini-2024-05-14", + "name": "gpt-4-1-mini-2024-05-14", + "capabilities": ["function-call", "image-recognition", "web-search"], + "ownedBy": "openai" + }, + { + "id": "gpt-4o-audio-preview-2025-06-03", + "name": "gpt-4o-audio-preview-2025-06-03", + "capabilities": ["function-call", "image-recognition", "web-search"], + "ownedBy": "openai" + }, + { + "id": "gpt-4o-mini-search-preview-2025-03-11", + "name": "gpt-4o-mini-search-preview-2025-03-11", + "capabilities": ["function-call", "image-recognition", "web-search"], + "ownedBy": "openai" + }, + { + "id": "gpt-4o-search-preview-2025-03-11", + "name": "gpt-4o-search-preview-2025-03-11", + "capabilities": ["function-call", "image-recognition", "web-search"], + "ownedBy": "openai" + }, + { + "id": "gpt-5-search-api", + "name": "gpt-5-search-api", + "capabilities": ["function-call", "image-recognition", "web-search", "reasoning"], + "reasoning": { + "supportedEfforts": ["minimal", "low", "medium", "high"] + }, + "ownedBy": "openai" + }, + { + "id": "gpt-5-search-api-2025-10-14", + "name": "gpt-5-search-api-2025-10-14", + "capabilities": ["function-call", "image-recognition", "web-search", "reasoning"], + "reasoning": { + "supportedEfforts": ["minimal", "low", "medium", "high"] + }, + "ownedBy": "openai" + }, + { + "id": "gpt-audio-2025-08-28", + "name": "gpt-audio-2025-08-28", + "ownedBy": "openai" + }, + { + "id": "gpt-audio-mini-2025-10-06", + "name": "gpt-audio-mini-2025-10-06", + "ownedBy": "openai" + }, + { + "id": "gpt-image-1-5-all", + "name": "gpt-image-1-5-all", + "capabilities": ["image-generation"], + "ownedBy": "openai" + }, + { + "id": "o1-mini-all", + "name": "o1-mini-all", + "capabilities": ["reasoning"], + "reasoning": { + "supportedEfforts": ["low", "medium", "high"] + }, + "ownedBy": "openai" + }, + { + "id": "o1-preview-all", + "name": "o1-preview-all", + "capabilities": ["reasoning"], + "reasoning": { + "supportedEfforts": ["low", "medium", "high"] + }, + "ownedBy": "openai" + }, + { + "id": "o3-all", + "name": "o3-all", + "capabilities": ["reasoning", "function-call", "image-recognition", "web-search"], + "ownedBy": "openai" + }, + { + "id": "o3-deep-research-2025-06-26", + "name": "o3-deep-research-2025-06-26", + "capabilities": ["reasoning", "function-call", "image-recognition", "web-search"], + "reasoning": { + "supportedEfforts": ["medium"] + }, + "ownedBy": "openai" + }, + { + "id": "o4-mini-all", + "name": "o4-mini-all", + "capabilities": ["reasoning", "function-call", "image-recognition", "web-search"], + "reasoning": { + "supportedEfforts": ["low", "medium", "high"] + }, + "ownedBy": "openai" + }, + { + "id": "o4-mini-deep-research-2025-06-26", + "name": "o4-mini-deep-research-2025-06-26", + "capabilities": ["reasoning", "function-call", "image-recognition", "web-search"], + "reasoning": { + "supportedEfforts": ["medium"] + }, + "ownedBy": "openai" + }, + { + "id": "o4-mini-high-all", + "name": "o4-mini-high-all", + "capabilities": ["reasoning", "function-call", "image-recognition", "web-search"], + "reasoning": { + "supportedEfforts": ["low", "medium", "high"] + }, + "ownedBy": "openai" + }, + { + "id": "claude-3-5-sonnet-latest", + "name": "claude-3-5-sonnet-latest", + "capabilities": ["function-call", "image-recognition", "web-search", "computer-use"], + "ownedBy": "anthropic" + }, + { + "id": "gpt-3-5-turbo-instruct-09-14", + "name": "gpt-3-5-turbo-instruct-09-14", + "ownedBy": "openai" + }, + { + "id": "gpt-4o-transcribe-diarize", + "name": "gpt-4o-transcribe-diarize", + "capabilities": ["function-call", "image-recognition", "web-search"], + "ownedBy": "openai" + }, + { + "id": "gpt-realtime", + "name": "gpt-realtime", + "ownedBy": "openai" + }, + { + "id": "gpt-realtime-mini", + "name": "gpt-realtime-mini", + "ownedBy": "openai" + }, + { + "id": "gpt-realtime-mini-2025-10-06", + "name": "gpt-realtime-mini-2025-10-06", + "ownedBy": "openai" + }, + { + "id": "grok-3-nx", + "name": "grok-3-nx", + "capabilities": ["function-call", "web-search"], + "ownedBy": "xai" + }, + { + "id": "kling-v1", + "name": "kling-v1", + "capabilities": ["video-generation"], + "ownedBy": "kling" + }, + { + "id": "kling-v1-6", + "name": "kling-v1-6", + "capabilities": ["video-generation"], + "ownedBy": "kling" + }, + { + "id": "kling-v2-1-master", + "name": "kling-v2-1-master", + "capabilities": ["video-generation"], + "ownedBy": "kling" + }, + { + "id": "kling-v2-5-turbo", + "name": "kling-v2-5-turbo", + "capabilities": ["video-generation"], + "ownedBy": "kling" + }, + { + "id": "kling-v2-master", + "name": "kling-v2-master", + "capabilities": ["video-generation"], + "ownedBy": "kling" + }, + { + "id": "o1-pro-2025-03-19", + "name": "o1-pro-2025-03-19", + "capabilities": ["reasoning", "function-call", "image-recognition"], + "reasoning": { + "supportedEfforts": ["low", "medium", "high"] + }, + "ownedBy": "openai" + }, + { + "id": "o3-pro-all", + "name": "o3-pro-all", + "capabilities": ["reasoning", "function-call", "image-recognition", "web-search"], + "reasoning": { + "supportedEfforts": ["low", "medium", "high"] + }, + "ownedBy": "openai" + }, + { + "id": "computer-use-preview-2025-03-11", + "name": "computer-use-preview-2025-03-11", + "capabilities": ["computer-use"], + "ownedBy": "burncloud" + }, + { + "id": "doubao-1-5-ui-tars-250428", + "name": "doubao-1-5-ui-tars-250428", + "ownedBy": "bytedance" + }, + { + "id": "doubao-lite-128k-240828", + "name": "doubao-lite-128k-240828", + "ownedBy": "bytedance" + }, + { + "id": "doubao-lite-32k-240828", + "name": "doubao-lite-32k-240828", + "ownedBy": "bytedance" + }, + { + "id": "doubao-pro-32k-241215", + "name": "doubao-pro-32k-241215", + "ownedBy": "bytedance" + }, + { + "id": "doubao-seedance-1-0-lite-i2v-250428", + "name": "doubao-seedance-1-0-lite-i2v-250428", + "capabilities": ["function-call"], + "ownedBy": "bytedance" + }, + { + "id": "doubao-seedance-1-0-lite-t2v-250428", + "name": "doubao-seedance-1-0-lite-t2v-250428", + "capabilities": ["function-call"], + "ownedBy": "bytedance" + }, + { + "id": "doubao-seedance-1-0-pro-250528", + "name": "doubao-seedance-1-0-pro-250528", + "capabilities": ["function-call"], + "ownedBy": "bytedance" + }, + { + "id": "gemini-2-5-flash-lite-thinking-*", + "name": "gemini-2-5-flash-lite-thinking-*", + "capabilities": ["reasoning", "function-call", "image-recognition", "web-search"], + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 512, + "max": 24576 + } + }, + "ownedBy": "google" + }, + { + "id": "gpt-5-flatfee", + "name": "gpt-5-flatfee", + "capabilities": ["function-call", "image-recognition", "web-search", "reasoning"], + "reasoning": { + "supportedEfforts": ["minimal", "low", "medium", "high"] + }, + "ownedBy": "openai" + }, + { + "id": "gpt-5-nano-high", + "name": "gpt-5-nano-high", + "capabilities": ["function-call", "image-recognition", "web-search", "reasoning"], + "reasoning": { + "supportedEfforts": ["minimal", "low", "medium", "high"] + }, + "ownedBy": "openai" + }, + { + "id": "gpt-5-nano-low", + "name": "gpt-5-nano-low", + "capabilities": ["function-call", "image-recognition", "web-search", "reasoning"], + "reasoning": { + "supportedEfforts": ["minimal", "low", "medium", "high"] + }, + "ownedBy": "openai" + }, + { + "id": "gpt-5-nano-medium", + "name": "gpt-5-nano-medium", + "capabilities": ["function-call", "image-recognition", "web-search", "reasoning"], + "reasoning": { + "supportedEfforts": ["minimal", "low", "medium", "high"] + }, + "ownedBy": "openai" + }, + { + "id": "gpt-5-nano-minimal", + "name": "gpt-5-nano-minimal", + "capabilities": ["function-call", "image-recognition", "web-search", "reasoning"], + "reasoning": { + "supportedEfforts": ["minimal", "low", "medium", "high"] + }, + "ownedBy": "openai" + }, + { + "id": "qwen-coder-plus-1106", + "name": "qwen-coder-plus-1106", + "capabilities": ["function-call"], + "ownedBy": "alibaba" + }, + { + "id": "qwen-coder-turbo-0919", + "name": "qwen-coder-turbo-0919", + "capabilities": ["function-call"], + "ownedBy": "alibaba" + }, + { + "id": "qwen-math-plus-0919", + "name": "qwen-math-plus-0919", + "capabilities": ["function-call"], + "ownedBy": "alibaba" + }, + { + "id": "qwen-math-turbo-0919", + "name": "qwen-math-turbo-0919", + "capabilities": ["function-call"], + "ownedBy": "alibaba" + }, + { + "id": "qwen-max-0403", + "name": "qwen-max-0403", + "capabilities": ["function-call"], + "ownedBy": "alibaba" + }, + { + "id": "qwen-max-0428", + "name": "qwen-max-0428", + "capabilities": ["function-call"], + "ownedBy": "alibaba" + }, + { + "id": "qwen-max-0919", + "name": "qwen-max-0919", + "capabilities": ["function-call"], + "ownedBy": "alibaba" + }, + { + "id": "qwen-plus-0919", + "name": "qwen-plus-0919", + "capabilities": ["function-call", "reasoning"], + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 0, + "max": 81920 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "qwen-plus-2025-07-14", + "name": "qwen-plus-2025-07-14", + "capabilities": ["function-call", "reasoning"], + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 0, + "max": 38912 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "sora-2-15s", + "name": "sora-2-15s", + "capabilities": ["video-generation"], + "ownedBy": "openai" + }, + { + "id": "sora-2-characters", + "name": "sora-2-characters", + "capabilities": ["video-generation"], + "ownedBy": "openai" + }, + { + "id": "sora-2-landscape", + "name": "sora-2-landscape", + "capabilities": ["video-generation"], + "ownedBy": "openai" + }, + { + "id": "sora-2-landscape-pro-25s", + "name": "sora-2-landscape-pro-25s", + "capabilities": ["video-generation"], + "ownedBy": "openai" + }, + { + "id": "sora-2-portrait-pro-25s", + "name": "sora-2-portrait-pro-25s", + "capabilities": ["video-generation"], + "ownedBy": "openai" + }, + { + "id": "baichuan-text-embedding", + "name": "Baichuan-Text-Embedding", + "capabilities": ["embedding"], + "ownedBy": "baichuan" + }, + { + "id": "baichuan2-turbo", + "name": "Baichuan2-Turbo", + "contextWindow": 32000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.32 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.32 + } + }, + "ownedBy": "baichuan" + }, + { + "id": "baichuan2-turbo-192k", + "name": "Baichuan2-Turbo-192k", + "contextWindow": 192000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2.64 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2.64 + } + }, + "ownedBy": "baichuan" + }, + { + "id": "tavily", + "name": "tavily", + "ownedBy": "302ai" + }, + { + "id": "searchapi", + "name": "searchapi", + "ownedBy": "302ai" + }, + { + "id": "clipdrop", + "name": "clipdrop", + "ownedBy": "302ai" + }, + { + "id": "vectorizer", + "name": "vectorizer", + "ownedBy": "302ai" + }, + { + "id": "302", + "name": "302 API", + "ownedBy": "302ai" + }, + { + "id": "zhipu-embedding-2", + "name": "zhipu-embedding-2", + "capabilities": ["embedding"], + "ownedBy": "302ai" + }, + { + "id": "luma", + "name": "luma", + "capabilities": ["video-generation"], + "ownedBy": "302ai" + }, + { + "id": "generalv3-5", + "name": "Spark Max", + "contextWindow": 8000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 4.73 + }, + "output": { + "currency": "USD", + "perMillionTokens": 4.73 + } + }, + "ownedBy": "302ai" + }, + { + "id": "doc2x", + "name": "doc2x", + "ownedBy": "302ai" + }, + { + "id": "glif", + "name": "glif", + "ownedBy": "302ai" + }, + { + "id": "sensechat-5", + "name": "SenseChat-5", + "contextWindow": 128000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 6.6 + }, + "output": { + "currency": "USD", + "perMillionTokens": 15.4 + } + }, + "ownedBy": "302ai" + }, + { + "id": "sensechat-turbo", + "name": "SenseChat-Turbo", + "contextWindow": 32000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.33 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.77 + } + }, + "ownedBy": "302ai" + }, + { + "id": "azure_tts", + "name": "azure_tts", + "ownedBy": "302ai" + }, + { + "id": "mistral-large-2", + "name": "Mistral-Large-2", + "capabilities": ["function-call"], + "contextWindow": 128000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 10 + } + }, + "ownedBy": "mistral" + }, + { + "id": "deepl-en", + "name": "DeepL-EN", + "contextWindow": 32000, + "ownedBy": "302ai" + }, + { + "id": "deepl-zh", + "name": "DeepL-ZH", + "contextWindow": 32000, + "ownedBy": "302ai" + }, + { + "id": "deepl-ja", + "name": "DeepL-JA", + "contextWindow": 32000, + "ownedBy": "302ai" + }, + { + "id": "klingai", + "name": "klingai", + "capabilities": ["video-generation"], + "ownedBy": "kling" + }, + { + "id": "gpt-4-plus", + "name": "gpt-4-plus", + "capabilities": ["function-call", "image-recognition"], + "contextWindow": 128000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 30 + }, + "output": { + "currency": "USD", + "perMillionTokens": 60 + } + }, + "ownedBy": "openai" + }, + { + "id": "exaai", + "name": "exaai", + "ownedBy": "302ai" + }, + { + "id": "cogvideox", + "name": "cogvideox", + "capabilities": ["video-generation"], + "ownedBy": "zhipu" + }, + { + "id": "farui-plus", + "name": "farui-plus", + "contextWindow": 12000, + "ownedBy": "302ai" + }, + { + "id": "doubao_tts_hd", + "name": "doubao_tts_hd", + "ownedBy": "bytedance" + }, + { + "id": "bochaai", + "name": "bochaai", + "ownedBy": "302ai" + }, + { + "id": "gpt-3-5-sonnet-cursor", + "name": "claude-3.5-sonnet", + "contextWindow": 200000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 15 + } + }, + "ownedBy": "openai" + }, + { + "id": "minimaxi_video-01", + "name": "minimaxi_video-01", + "capabilities": ["video-generation"], + "ownedBy": "minimax" + }, + { + "id": "deepl", + "name": "DeepL", + "ownedBy": "302ai" + }, + { + "id": "suno-api", + "name": "suno-api", + "ownedBy": "suno" + }, + { + "id": "fish-audio", + "name": "fish-audio", + "ownedBy": "302ai" + }, + { + "id": "llama3-2", + "name": "Llama3.2-90B", + "contextWindow": 131072, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2 + } + }, + "ownedBy": "meta" + }, + { + "id": "minimaxi_text2voice", + "name": "minimaxi_text2voice", + "ownedBy": "minimax" + }, + { + "id": "dubbingx", + "name": "dubbingx", + "contextWindow": 32768, + "ownedBy": "302ai" + }, + { + "id": "pika", + "name": "pika", + "capabilities": ["video-generation"], + "contextWindow": 32768, + "ownedBy": "302ai" + }, + { + "id": "hedra", + "name": "hedra", + "ownedBy": "302ai" + }, + { + "id": "pix", + "name": "pix", + "contextWindow": 32768, + "ownedBy": "302ai" + }, + { + "id": "gpt-4o-plus", + "name": "gpt-4o-plus", + "capabilities": ["function-call", "image-recognition", "web-search"], + "contextWindow": 128000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 15 + } + }, + "ownedBy": "openai" + }, + { + "id": "luma-photon", + "name": "Luma-Photon", + "capabilities": ["video-generation"], + "ownedBy": "302ai" + }, + { + "id": "coder-claude-3-5-sonnet-20240620", + "name": "coder-claude-3-5-sonnet-20240620", + "capabilities": ["function-call", "image-recognition", "web-search", "computer-use"], + "contextWindow": 200000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 15 + } + }, + "ownedBy": "302ai" + }, + { + "id": "coder-claude-3-5-sonnet-20241022", + "name": "coder-claude-3-5-sonnet-20241022", + "capabilities": ["function-call", "image-recognition", "web-search", "computer-use"], + "contextWindow": 200000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 15 + } + }, + "ownedBy": "302ai" + }, + { + "id": "minimaxi_music", + "name": "minimaxi_music", + "ownedBy": "minimax" + }, + { + "id": "llama3-3", + "name": "Llama3.3-70B", + "contextWindow": 128000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.9 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.9 + } + }, + "ownedBy": "meta" + }, + { + "id": "runway_exapnd", + "name": "runway_exapnd", + "capabilities": ["video-generation"], + "ownedBy": "runway" + }, + { + "id": "o1-plus", + "name": "o1-plus", + "capabilities": ["reasoning", "function-call", "image-recognition"], + "contextWindow": 128000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 100 + }, + "output": { + "currency": "USD", + "perMillionTokens": 200 + } + }, + "reasoning": { + "supportedEfforts": ["low", "medium", "high"] + }, + "ownedBy": "openai" + }, + { + "id": "tripo3d", + "name": "tripo3d", + "ownedBy": "302ai" + }, + { + "id": "vidu", + "name": "vidu", + "capabilities": ["video-generation"], + "ownedBy": "vidu" + }, + { + "id": "doubao-vision-lite-32k", + "name": "Doubao-Vision-Lite-32k", + "capabilities": ["image-recognition"], + "contextWindow": 32000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.5 + } + }, + "ownedBy": "bytedance" + }, + { + "id": "general_v2-1_l", + "name": "general_v2.1_L", + "ownedBy": "302ai" + }, + { + "id": "general_v2-0_l", + "name": "general_v2.0_L", + "ownedBy": "302ai" + }, + { + "id": "general_v2-0", + "name": "general_v2.0", + "ownedBy": "302ai" + }, + { + "id": "general_v2-0_l_seededit", + "name": "general_v2.0_L_seededit", + "ownedBy": "302ai" + }, + { + "id": "general_v2-0_l_character", + "name": "general_v2.0_L_character", + "ownedBy": "302ai" + }, + { + "id": "doubao", + "name": "Doubao", + "ownedBy": "bytedance" + }, + { + "id": "kolors-virtual-try-on-v1", + "name": "kolors-virtual-try-on-v1", + "ownedBy": "kolors" + }, + { + "id": "kolors-virtual-try-on-v1-5", + "name": "kolors-virtual-try-on-v1-5", + "ownedBy": "kolors" + }, + { + "id": "memobase", + "name": "memobase", + "ownedBy": "302ai" + }, + { + "id": "gpt-3-5-sonnet-20241022-cursor", + "name": "claude-3.5-sonnet", + "contextWindow": 200000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 15 + } + }, + "ownedBy": "openai" + }, + { + "id": "gpt-3-5-sonnet-20240620-cursor", + "name": "claude-3.5-sonnet", + "contextWindow": 200000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 15 + } + }, + "ownedBy": "openai" + }, + { + "id": "gpt-4o-sonnet-cursor", + "name": "claude-3.5-sonnet", + "capabilities": ["function-call", "image-recognition", "web-search"], + "contextWindow": 200000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 15 + } + }, + "ownedBy": "openai" + }, + { + "id": "deepseek-r1-baidu", + "name": "Deepseek-R1-Baidu", + "capabilities": ["reasoning", "function-call"], + "contextWindow": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.6 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2.3 + } + }, + "ownedBy": "deepseek" + }, + { + "id": "deepseek-v3-baidu", + "name": "Deepseek-V3-Baidu", + "capabilities": ["reasoning", "function-call"], + "contextWindow": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.2 + } + }, + "reasoning": { + "supportedEfforts": ["none"] + }, + "ownedBy": "deepseek" + }, + { + "id": "minimaxi_video-01-live2d", + "name": "minimaxi_video-01-live2d", + "capabilities": ["video-generation"], + "ownedBy": "minimax" + }, + { + "id": "minimaxi_s2v-01", + "name": "minimaxi_S2V-01", + "ownedBy": "minimax" + }, + { + "id": "zzkj", + "name": "WiseDiag-Z1", + "contextWindow": 32000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 18.7 + }, + "output": { + "currency": "USD", + "perMillionTokens": 75.9 + } + }, + "ownedBy": "302ai" + }, + { + "id": "zzkj-lite", + "name": "WiseDiag-Z1 Lite", + "contextWindow": 32000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 3.85 + }, + "output": { + "currency": "USD", + "perMillionTokens": 15.4 + } + }, + "ownedBy": "302ai" + }, + { + "id": "zzkj-genetics", + "name": "WiseDiag-Genetics", + "contextWindow": 32000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 24.2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 94.6 + } + }, + "ownedBy": "302ai" + }, + { + "id": "gpt-3-7-sonnet-20250219-cursor", + "name": "claude-3.7-sonnet", + "contextWindow": 200000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 15 + } + }, + "ownedBy": "openai" + }, + { + "id": "minimaxi-image-01", + "name": "minimaxi-image-01", + "capabilities": ["image-generation"], + "ownedBy": "minimax" + }, + { + "id": "cogview-4", + "name": "cogview-4", + "ownedBy": "zhipu" + }, + { + "id": "cogview-4-250304", + "name": "cogview-4-250304", + "ownedBy": "zhipu" + }, + { + "id": "doubao-seededit", + "name": "Doubao-Seededit", + "capabilities": ["function-call"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0 + }, + "output": { + "currency": "USD", + "perMillionTokens": 50 + } + }, + "ownedBy": "bytedance" + }, + { + "id": "ernie-4-5-8k-preview", + "name": "ernie-4.5-8k-preview", + "contextWindow": 8000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.66 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2.53 + } + }, + "ownedBy": "baidu" + }, + { + "id": "baidubce-irag-1-0", + "name": "baidubce-irag-1.0", + "ownedBy": "302ai" + }, + { + "id": "gpt-4o-image-generation", + "name": "gpt-4o-image-generation", + "capabilities": ["function-call", "image-generation"], + "contextWindow": 128000, + "ownedBy": "openai" + }, + { + "id": "high_aes_general_v30l_zt2i", + "name": "high_aes_general_v30l_zt2i", + "ownedBy": "302ai" + }, + { + "id": "general_v3-0", + "name": "general_v3.0", + "ownedBy": "302ai" + }, + { + "id": "glm-4-air-250414", + "name": "GLM-4-Air-250414", + "capabilities": ["function-call"], + "contextWindow": 32000, + "maxOutputTokens": 4000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.07 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.07 + } + }, + "ownedBy": "zhipu" + }, + { + "id": "glm-4-flash-250414", + "name": "GLM-4-Flash-250414", + "capabilities": ["function-call"], + "contextWindow": 128000, + "maxOutputTokens": 4000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "ownedBy": "zhipu" + }, + { + "id": "glm-z1-rumination-0414", + "name": "THUDM/GLM-Z1-Rumination-32B-0414", + "contextWindow": 32000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.07 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.07 + } + }, + "ownedBy": "zhipu" + }, + { + "id": "doubao-1-5-thinking-pro-vision-250415", + "name": "Doubao-1.5-Thinking-Pro-Vision", + "capabilities": ["reasoning", "image-recognition"], + "contextWindow": 96000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.6 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2.3 + } + }, + "reasoning": { + "supportedEfforts": ["none", "high"] + }, + "ownedBy": "bytedance" + }, + { + "id": "firecrawl", + "name": "firecrawl", + "ownedBy": "302ai" + }, + { + "id": "doubao-1-5-ui-tars-250328", + "name": "Doubao-1.5-UI-Tars-250328", + "contextWindow": 32000, + "maxOutputTokens": 4000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.7 + } + }, + "ownedBy": "bytedance" + }, + { + "id": "sensenova-v6-pro", + "name": "SenseNova-V6-Pro", + "contextWindow": 32000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.55 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.43 + } + }, + "ownedBy": "sensenova" + }, + { + "id": "sensenova-v6-turbo", + "name": "SenseNova-V6-Turbo", + "contextWindow": 32000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.275 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.715 + } + }, + "ownedBy": "sensenova" + }, + { + "id": "sensenova-v6-reasoner", + "name": "SenseNova-V6-Reasoner", + "capabilities": ["reasoning"], + "contextWindow": 32000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.66 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2.53 + } + }, + "ownedBy": "sensenova" + }, + { + "id": "ernie-x1-turbo-32k", + "name": "ERNIE-X1-Turbo-32k", + "contextWindow": 32000, + "maxOutputTokens": 16384, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.165 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.66 + } + }, + "ownedBy": "baidu" + }, + { + "id": "ernie-4-5-turbo-vl-32k", + "name": "ERNIE-4.5-Turbo-VL-32k", + "capabilities": ["image-recognition"], + "contextWindow": 8000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.495 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.43 + } + }, + "ownedBy": "baidu" + }, + { + "id": "higgsfield", + "name": "higgsfield", + "ownedBy": "302ai" + }, + { + "id": "higgsfield-shortads", + "name": "higgsfield-shortads", + "ownedBy": "302ai" + }, + { + "id": "deepseek-r1-huoshan-0528", + "name": "DeepSeek-R1-Huoshan-0528", + "capabilities": ["reasoning", "function-call"], + "contextWindow": 128000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.6 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2.3 + } + }, + "ownedBy": "deepseek" + }, + { + "id": "hedra-app", + "name": "hedra-app", + "ownedBy": "302ai" + }, + { + "id": "kling-v1-5", + "name": "kling-v1-5", + "capabilities": ["video-generation"], + "ownedBy": "kling" + }, + { + "id": "gemini-2-5-flash-deepsearch", + "name": "gemini-2.5-flash-deepsearch", + "capabilities": ["reasoning", "function-call", "image-recognition", "web-search"], + "contextWindow": 1000000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 25 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 0, + "max": 24576 + } + }, + "ownedBy": "google" + }, + { + "id": "gemini-2-5-pro-deepsearch", + "name": "gemini-2.5-pro-deepsearch", + "capabilities": ["reasoning", "function-call", "image-recognition", "web-search"], + "contextWindow": 1000000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 35 + } + }, + "reasoning": { + "supportedEfforts": ["low", "medium", "high"], + "thinkingTokenLimits": { + "min": 128, + "max": 32768 + } + }, + "ownedBy": "google" + }, + { + "id": "community", + "name": "deepseek/deepseek-v3/community", + "contextWindow": 64000, + "maxOutputTokens": 4000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1 + } + }, + "ownedBy": "302ai" + }, + { + "id": "v1beta1-text-synthesize", + "name": "v1beta1-text-synthesize", + "ownedBy": "302ai" + }, + { + "id": "qwenlong-l1", + "name": "Tongyi-Zhiwen/QwenLong-L1-32B", + "capabilities": ["function-call"], + "contextWindow": 128000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.14 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.6 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "t2v-01", + "name": "T2V-01", + "ownedBy": "302ai" + }, + { + "id": "i2v-01", + "name": "I2V-01", + "ownedBy": "302ai" + }, + { + "id": "i2v-01-live", + "name": "I2V-01-live", + "ownedBy": "302ai" + }, + { + "id": "i2v-01-director", + "name": "I2V-01-Director", + "ownedBy": "302ai" + }, + { + "id": "chanjing-video", + "name": "chanjing-video", + "capabilities": ["video-generation"], + "ownedBy": "302ai" + }, + { + "id": "chanjing-cicada1-0", + "name": "chanjing-cicada1.0", + "ownedBy": "302ai" + }, + { + "id": "chanjing-cicada3-0", + "name": "chanjing-cicada3.0", + "ownedBy": "302ai" + }, + { + "id": "kling-v2-1", + "name": "kling-v2-1", + "capabilities": ["video-generation"], + "ownedBy": "kling" + }, + { + "id": "gpt-4-sonnet-20250514-cursor", + "name": "gpt-4-sonnet-20250514-cursor", + "capabilities": ["function-call", "image-recognition"], + "contextWindow": 200000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 15 + } + }, + "ownedBy": "openai" + }, + { + "id": "gpt-4-opus-4-20250514-cursor", + "name": "gpt-4-opus-4-20250514-cursor", + "capabilities": ["function-call", "image-recognition"], + "contextWindow": 200000, + "ownedBy": "openai" + }, + { + "id": "gpt-4-opus-20250514-cursor", + "name": "gpt-4-opus-20250514-cursor", + "capabilities": ["function-call", "image-recognition"], + "contextWindow": 200000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 15 + }, + "output": { + "currency": "USD", + "perMillionTokens": 75 + } + }, + "ownedBy": "openai" + }, + { + "id": "higgsfield-soul", + "name": "higgsfield-soul", + "ownedBy": "302ai" + }, + { + "id": "seededit_v3-0", + "name": "seededit_v3.0", + "ownedBy": "bytedance" + }, + { + "id": "glm-4-1v-thinking-flashx", + "name": "glm-4.1v-thinking-flashx", + "capabilities": ["reasoning", "function-call"], + "contextWindow": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.3 + } + }, + "ownedBy": "zhipu" + }, + { + "id": "higgsfield-character", + "name": "higgsfield-character", + "ownedBy": "302ai" + }, + { + "id": "playai-tts", + "name": "playai-tts", + "ownedBy": "302ai" + }, + { + "id": "runway-aleph", + "name": "runway-aleph", + "capabilities": ["video-generation"], + "ownedBy": "runway" + }, + { + "id": "sonnet-4-20250514", + "name": "cc-sonnet-4-20250514", + "contextWindow": 200000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.9 + }, + "output": { + "currency": "USD", + "perMillionTokens": 4.5 + } + }, + "ownedBy": "302ai" + }, + { + "id": "speech-01-turbo-240228", + "name": "speech-01-turbo-240228", + "ownedBy": "302ai" + }, + { + "id": "sensenova-v6-5-turbo", + "name": "SenseNova-V6-5-Turbo", + "contextWindow": 32000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.473 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.419 + } + }, + "ownedBy": "sensenova" + }, + { + "id": "sensenova-v6-5-pro", + "name": "SenseNova-V6-5-Pro", + "contextWindow": 32000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.946 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2.838 + } + }, + "ownedBy": "sensenova" + }, + { + "id": "doubao-seed-1-6-flash-250715", + "name": "doubao-seed-1-6-flash-250715", + "capabilities": ["reasoning", "function-call", "image-recognition"], + "contextWindow": 256000, + "maxOutputTokens": 16000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.023 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.231 + } + }, + "ownedBy": "bytedance" + }, + { + "id": "kimi-k2-250711", + "name": "kimi-k2-250711", + "capabilities": ["function-call"], + "contextWindow": 128000, + "maxOutputTokens": 32000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.632 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2.53 + } + }, + "ownedBy": "moonshot" + }, + { + "id": "hunyuan-turbos-20250716", + "name": "hunyuan-turbos-20250716", + "capabilities": ["function-call"], + "contextWindow": 32000, + "maxOutputTokens": 16000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.132 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.33 + } + }, + "ownedBy": "tencent" + }, + { + "id": "hunyuan-t1-20250711", + "name": "hunyuan-t1-20250711", + "capabilities": ["reasoning", "function-call"], + "contextWindow": 28000, + "maxOutputTokens": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.132 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.33 + } + }, + "ownedBy": "tencent" + }, + { + "id": "slides_glm_agent", + "name": "slides_glm_agent", + "ownedBy": "302ai" + }, + { + "id": "cogito-v2-preview-deepseek", + "name": "deepcogito/cogito-v2-preview-deepseek-671b", + "capabilities": ["function-call"], + "contextWindow": 64000, + "ownedBy": "cogito" + }, + { + "id": "cogito-v2-preview-llama", + "name": "deepcogito/cogito-v2-preview-llama-405B", + "contextWindow": 64000, + "ownedBy": "cogito" + }, + { + "id": "u1-pro", + "name": "U1-Pro", + "contextWindow": 32000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.2 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.2 + } + }, + "ownedBy": "302ai" + }, + { + "id": "u1", + "name": "U1", + "contextWindow": 32000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.6 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.6 + } + }, + "ownedBy": "302ai" + }, + { + "id": "opus-4-1-20250805", + "name": "cc-opus-4-1-20250805", + "contextWindow": 200000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 4.5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 22.5 + } + }, + "ownedBy": "302ai" + }, + { + "id": "3-5-haiku-20241022", + "name": "cc-3-5-haiku-20241022", + "contextWindow": 200000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.24 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.2 + } + }, + "ownedBy": "302ai" + }, + { + "id": "dop-lite", + "name": "dop-lite", + "ownedBy": "302ai" + }, + { + "id": "dop-preview", + "name": "dop-preview", + "ownedBy": "302ai" + }, + { + "id": "dop-turbo", + "name": "dop-turbo", + "ownedBy": "302ai" + }, + { + "id": "higgsfield-api-soul", + "name": "higgsfield-api-soul", + "ownedBy": "302ai" + }, + { + "id": "higgsfield-api-speak", + "name": "higgsfield-api-speak", + "ownedBy": "302ai" + }, + { + "id": "higgsfield-apps", + "name": "higgsfield-apps", + "ownedBy": "302ai" + }, + { + "id": "img", + "name": "sophnet2", + "ownedBy": "302ai" + }, + { + "id": "sonnet-4-5-20250929", + "name": "cc-sonnet-4-5-20250929", + "contextWindow": 200000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.9 + }, + "output": { + "currency": "USD", + "perMillionTokens": 4.5 + } + }, + "ownedBy": "302ai" + }, + { + "id": "haiku-4-5-20251001", + "name": "cc-haiku-4-5-20251001", + "contextWindow": 200000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.5 + } + }, + "ownedBy": "302ai" + }, + { + "id": "s1", + "name": "S1", + "contextWindow": 32000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.3 + } + }, + "ownedBy": "302ai" + }, + { + "id": "viduq2", + "name": "viduq2", + "capabilities": ["video-generation"], + "ownedBy": "vidu" + }, + { + "id": "doubao-seedance-1-0-pro-fast-251015", + "name": "doubao-seedance-1-0-pro-fast-251015", + "capabilities": ["function-call"], + "ownedBy": "bytedance" + }, + { + "id": "minimax-image-01", + "name": "minimax-image-01", + "capabilities": ["image-generation"], + "ownedBy": "minimax" + }, + { + "id": "minimaxi-image-01-live", + "name": "minimax-image-01", + "capabilities": ["image-generation"], + "ownedBy": "minimax" + }, + { + "id": "minimax-image-01-live", + "name": "minimax-image-01", + "capabilities": ["image-generation"], + "ownedBy": "minimax" + }, + { + "id": "voyage-context-3", + "name": "voyage-context-3", + "ownedBy": "voyage" + }, + { + "id": "rerank-2-5", + "name": "rerank-2.5", + "capabilities": ["rerank"], + "ownedBy": "cohere" + }, + { + "id": "rerank-2-5-lite", + "name": "rerank-2.5-lite", + "capabilities": ["rerank"], + "ownedBy": "cohere" + }, + { + "id": "music-1-5", + "name": "music-1.5", + "ownedBy": "302ai" + }, + { + "id": "gpt-5-1-plus", + "name": "gpt-5.1-plus", + "capabilities": ["function-call", "image-recognition", "web-search", "reasoning"], + "contextWindow": 400000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.25 + }, + "output": { + "currency": "USD", + "perMillionTokens": 10 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"] + }, + "ownedBy": "openai" + }, + { + "id": "gpt-5-1-thinking-plus", + "name": "gpt-5.1-thinking-plus", + "capabilities": ["reasoning", "function-call", "image-recognition", "web-search"], + "contextWindow": 400000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.25 + }, + "output": { + "currency": "USD", + "perMillionTokens": 10 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"] + }, + "ownedBy": "openai" + }, + { + "id": "ernie-5-0-thinking-latest", + "name": "ernie-5.0-thinking-latest", + "capabilities": ["reasoning"], + "contextWindow": 128000, + "maxOutputTokens": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.946 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.573 + } + }, + "ownedBy": "baidu" + }, + { + "id": "opus-4-5-20251101", + "name": "cc-opus-4-5-20251101", + "contextWindow": 200000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 7.5 + } + }, + "ownedBy": "302ai" + }, + { + "id": "glm-4-6-flash", + "name": "GLM-4.6V-Flash", + "capabilities": ["function-call", "reasoning"], + "contextWindow": 64000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "reasoning": { + "supportedEfforts": ["none"], + "interleaved": true + }, + "ownedBy": "zhipu" + }, + { + "id": "glm-asr-2512", + "name": "glm-asr-2512", + "ownedBy": "zhipu" + }, + { + "id": "glm-tts-clone", + "name": "glm-tts-clone", + "capabilities": ["audio-generation"], + "ownedBy": "zhipu" + }, + { + "id": "glm-tts", + "name": "glm-tts", + "ownedBy": "zhipu" + }, + { + "id": "gpt-4o-mini-tts-2025-12-15", + "name": "gpt-4o-mini-tts-2025-12-15", + "capabilities": ["audio-generation"], + "ownedBy": "openai" + }, + { + "id": "glm-for-coding", + "name": "glm-for-coding", + "capabilities": ["reasoning"], + "contextWindow": 200000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.18 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.78 + } + }, + "ownedBy": "zhipu" + }, + { + "id": "higgsfield-soul-standard", + "name": "higgsfield-soul-standard", + "ownedBy": "302ai" + }, + { + "id": "higgsfield-dop-standard", + "name": "higgsfield-dop-standard", + "ownedBy": "302ai" + }, + { + "id": "glm-4-7-preview", + "name": "glm-4.7-preview", + "capabilities": ["function-call", "reasoning"], + "contextWindow": 200000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "reasoning": { + "supportedEfforts": ["none"], + "interleaved": true + }, + "ownedBy": "zhipu" + }, + { + "id": "glm-4-7-coding-preview", + "name": "glm-4.7-coding-preview", + "capabilities": ["function-call", "reasoning"], + "contextWindow": 200000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "reasoning": { + "supportedEfforts": ["none"], + "interleaved": true + }, + "ownedBy": "zhipu" + }, + { + "id": "minimax-m2-1-highspeed", + "name": "MiniMax-M2.1-highspeed", + "capabilities": ["reasoning", "function-call"], + "contextWindow": 1000000, + "maxOutputTokens": 80000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.6 + }, + "output": { + "currency": "USD", + "perMillionTokens": 4.8 + } + }, + "reasoning": { + "supportedEfforts": [], + "interleaved": true + }, + "ownedBy": "minimax" + }, + { + "id": "doubao-seedance-1-5-pro-251215", + "name": "doubao-seedance-1-5-pro-251215", + "capabilities": ["function-call"], + "ownedBy": "bytedance" + }, + { + "id": "qwq-plus-latest", + "name": "QwQ-Plus-Latest", + "capabilities": ["reasoning"], + "contextWindow": 128000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.23 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.58 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "qwq-plus-2025-03-05", + "name": "QwQ-Plus-2025-03-05", + "capabilities": ["reasoning"], + "contextWindow": 128000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.23 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.58 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "qvq-max-latest", + "name": "QvQ-Max-Latest", + "capabilities": ["reasoning"], + "contextWindow": 128000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.15 + }, + "output": { + "currency": "USD", + "perMillionTokens": 4.58 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "qvq-max-2025-05-15", + "name": "QvQ-Max-2025-05-15", + "capabilities": ["reasoning"], + "contextWindow": 128000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.15 + }, + "output": { + "currency": "USD", + "perMillionTokens": 4.58 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "qvq-plus", + "name": "QvQ-Plus", + "capabilities": ["reasoning"], + "contextWindow": 128000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.29 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.72 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "qvq-plus-latest", + "name": "QvQ-Plus-Latest", + "capabilities": ["reasoning"], + "contextWindow": 128000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.29 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.72 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "qvq-plus-2025-05-15", + "name": "QvQ-Plus-2025-05-15", + "capabilities": ["reasoning"], + "contextWindow": 128000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.29 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.72 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "qwen2-5-instruct-1m", + "name": "Qwen2.5-14B", + "capabilities": ["function-call"], + "contextWindow": 128000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.143 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.43 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "qwen3-coder-plus-2025-09-23", + "name": "qwen3-coder-plus-2025-09-23", + "capabilities": ["function-call"], + "contextWindow": 1048576, + "maxOutputTokens": 65536, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.572 + }, + "output": { + "currency": "USD", + "perMillionTokens": 2.29 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "qwen-mt-lite", + "name": "qwen-mt-lite", + "contextWindow": 4096, + "maxOutputTokens": 2048, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.086 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.23 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "qwen-plus-2025-12-01", + "name": "Qwen-Plus-2025-12-01", + "capabilities": ["function-call", "reasoning"], + "contextWindow": 128000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.12 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.2 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 0, + "max": 81920 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "qwen-plus-2025-09-11", + "name": "Qwen-Plus-2025-09-11", + "capabilities": ["function-call", "reasoning"], + "contextWindow": 128000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.12 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.2 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 0, + "max": 81920 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "qwen-plus-2025-01-25", + "name": "Qwen-Plus-2025-01-25", + "capabilities": ["function-call", "reasoning"], + "contextWindow": 128000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.12 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.286 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 0, + "max": 81920 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "qwen-plus-2025-01-12", + "name": "Qwen-Plus-2025-01-12", + "capabilities": ["function-call", "reasoning"], + "contextWindow": 128000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.12 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.286 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 0, + "max": 81920 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "qwen-max-2024-04-28", + "name": "Qwen-Max-2024-04-28", + "capabilities": ["function-call"], + "contextWindow": 131072, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 5.72 + }, + "output": { + "currency": "USD", + "perMillionTokens": 17.143 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "qwen-max-2024-04-03", + "name": "Qwen-Max-2024-04-03", + "capabilities": ["function-call"], + "contextWindow": 131072, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 5.72 + }, + "output": { + "currency": "USD", + "perMillionTokens": 17.143 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "qwen-long-latest", + "name": "Qwen-Long-Latest", + "capabilities": ["function-call", "file-input"], + "contextWindow": 1000000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.072 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.286 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "qwen-long-2025-01-25", + "name": "Qwen-Long-2025-01-25", + "capabilities": ["function-call", "file-input"], + "contextWindow": 1000000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.072 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.286 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "qwen-vl-plus-2025-08-15", + "name": "Qwen-VL-Plus-2025-08-15", + "capabilities": ["function-call", "image-recognition"], + "contextWindow": 32000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.12 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.3 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "qwen-vl-plus-2025-07-10", + "name": "Qwen-VL-Plus-2025-07-10", + "capabilities": ["function-call", "image-recognition"], + "contextWindow": 32000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.022 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.22 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "qwen-vl-plus-2025-05-07", + "name": "Qwen-VL-Plus-2025-05-07", + "capabilities": ["function-call", "image-recognition"], + "contextWindow": 32000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.22 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.65 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "qwen-vl-plus-2025-01-25", + "name": "Qwen-VL-Plus-2025-01-25", + "capabilities": ["function-call", "image-recognition"], + "contextWindow": 32000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.22 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.65 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "qwen-vl-plus-2025-01-02", + "name": "Qwen-VL-Plus-2025-01-02", + "capabilities": ["function-call", "image-recognition"], + "contextWindow": 32000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.22 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.65 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "qwen-turbo-2025-07-15", + "name": "Qwen-Turbo-2025-07-15", + "capabilities": ["function-call", "reasoning"], + "contextWindow": 1000000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.05 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.43 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 0, + "max": 38912 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "qwen-vl-max-2025-08-13", + "name": "Qwen-VL-Max-2025-08-13", + "capabilities": ["function-call", "image-recognition"], + "contextWindow": 32000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.23 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.58 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "qwen-vl-max-2025-04-08", + "name": "Qwen-VL-Max-2025-04-08", + "capabilities": ["function-call", "image-recognition"], + "contextWindow": 32000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.43 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.29 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "qwen-vl-max-2025-04-02", + "name": "Qwen-VL-Max-2025-04-02", + "capabilities": ["function-call", "image-recognition"], + "contextWindow": 32000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.43 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.29 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "qwen-vl-max-2024-12-30", + "name": "Qwen-VL-Max-2024-12-30", + "capabilities": ["function-call", "image-recognition"], + "contextWindow": 32000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.43 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.29 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "qwen-vl-ocr-2025-11-20", + "name": "Qwen-VL-OCR-2025-11-20", + "capabilities": ["function-call", "image-recognition", "file-input"], + "contextWindow": 38000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.043 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.072 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "qwen-vl-ocr-2025-08-28", + "name": "Qwen-VL-OCR-2025-08-28", + "capabilities": ["function-call", "image-recognition", "file-input"], + "contextWindow": 34000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.72 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.72 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "qwen-vl-ocr-2025-04-13", + "name": "Qwen-VL-OCR-2025-04-13", + "capabilities": ["function-call", "image-recognition", "file-input"], + "contextWindow": 34000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.72 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.72 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "glm-image", + "name": "glm-image", + "capabilities": ["image-generation"], + "ownedBy": "zhipu" + }, + { + "id": "s2", + "name": "S2", + "contextWindow": 32000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.3 + } + }, + "ownedBy": "302ai" + }, + { + "id": "u2", + "name": "U2", + "contextWindow": 32000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.6 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.6 + } + }, + "ownedBy": "302ai" + }, + { + "id": "minimax-for-coding", + "name": "minimax-for-coding", + "capabilities": ["reasoning"], + "contextWindow": 200000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.09 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.36 + } + }, + "ownedBy": "minimax" + }, + { + "id": "glm-ocr", + "name": "glm-ocr", + "capabilities": ["file-input"], + "contextWindow": 32000, + "ownedBy": "zhipu" + }, + { + "id": "opus-4-6", + "name": "cc-opus-4-6", + "contextWindow": 200000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 7.5 + } + }, + "ownedBy": "302ai" + }, + { + "id": "sonnet-4-6", + "name": "cc-sonnet-4-6", + "contextWindow": 1000000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.9 + }, + "output": { + "currency": "USD", + "perMillionTokens": 4.5 + } + }, + "ownedBy": "302ai" + }, + { + "id": "gemma3", + "name": "gemma3", + "capabilities": ["image-recognition"], + "ownedBy": "google" + }, + { + "id": "speech_paraformer-large", + "name": "speech_paraformer-large", + "ownedBy": "lanyun" + }, + { + "id": "kimi-k2-thinking-251104", + "name": "kimi-k2-thinking-251104", + "capabilities": ["reasoning", "function-call"], + "reasoning": { + "supportedEfforts": [] + }, + "ownedBy": "moonshot" + }, + { + "id": "ph-data-image-1-0", + "name": "ph-data-image-1-0", + "capabilities": ["image-generation"], + "ownedBy": "ph8" + }, + { + "id": "ph-ppt-image-1-0", + "name": "ph-ppt-image-1-0", + "capabilities": ["image-generation"], + "ownedBy": "ph8" + }, + { + "id": "longcat-flash-lite", + "name": "longcat-flash-lite", + "ownedBy": "meituan" + }, + { + "id": "gpt-realtime-mini-2025-12-15", + "name": "gpt-realtime-mini-2025-12-15", + "ownedBy": "openai" + }, + { + "id": "gpt-audio-mini-2025-12-15", + "name": "gpt-audio-mini-2025-12-15", + "ownedBy": "openai" + }, + { + "id": "chatgpt-image-latest", + "name": "chatgpt-image-latest", + "capabilities": ["image-generation"], + "ownedBy": "openai" + }, + { + "id": "gpt-5-2-pro-2025-12-11", + "name": "gpt-5-2-pro-2025-12-11", + "capabilities": ["function-call", "image-recognition", "web-search", "reasoning"], + "reasoning": { + "supportedEfforts": ["medium", "high", "max"] + }, + "ownedBy": "openai" + }, + { + "id": "gpt-4o-mini-transcribe-2025-12-15", + "name": "gpt-4o-mini-transcribe-2025-12-15", + "capabilities": ["function-call", "image-recognition", "web-search"], + "ownedBy": "openai" + }, + { + "id": "gemini-pro-latest", + "name": "Gemini Pro Latest", + "capabilities": ["function-call", "reasoning"], + "contextWindow": 1048576, + "maxOutputTokens": 65536, + "reasoning": { + "supportedEfforts": ["low", "medium", "high"], + "thinkingTokenLimits": { + "min": 128, + "max": 32768 + } + }, + "ownedBy": "google" + }, + { + "id": "nano-banana-pro-preview", + "name": "Nano Banana Pro", + "capabilities": ["reasoning"], + "contextWindow": 131072, + "maxOutputTokens": 32768, + "ownedBy": "google" + }, + { + "id": "gemini-robotics-er-1-5-preview", + "name": "Gemini Robotics-ER 1.5 Preview", + "capabilities": ["reasoning"], + "contextWindow": 1048576, + "maxOutputTokens": 65536, + "ownedBy": "google" + }, + { + "id": "deep-research-pro-preview-12-2025", + "name": "Deep Research Pro Preview (Dec-12-2025)", + "capabilities": ["reasoning"], + "contextWindow": 131072, + "maxOutputTokens": 65536, + "ownedBy": "gemini" + }, + { + "id": "aqa", + "name": "Model that performs Attributed Question Answering.", + "contextWindow": 7168, + "maxOutputTokens": 1024, + "ownedBy": "gemini" + }, + { + "id": "gemini-2-5-flash-native-audio-latest", + "name": "Gemini 2.5 Flash Native Audio Latest", + "capabilities": ["reasoning", "function-call", "image-recognition", "web-search"], + "contextWindow": 131072, + "maxOutputTokens": 8192, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 0, + "max": 24576 + } + }, + "ownedBy": "google" + }, + { + "id": "gemini-2-5-flash-native-audio-preview-09-2025", + "name": "Gemini 2.5 Flash Native Audio Preview 09-2025", + "capabilities": ["reasoning", "function-call", "image-recognition", "web-search"], + "contextWindow": 131072, + "maxOutputTokens": 8192, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 0, + "max": 24576 + } + }, + "ownedBy": "google" + }, + { + "id": "gemini-2-5-flash-native-audio-preview-12-2025", + "name": "Gemini 2.5 Flash Native Audio Preview 12-2025", + "capabilities": ["reasoning", "function-call", "image-recognition", "web-search"], + "contextWindow": 131072, + "maxOutputTokens": 8192, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 0, + "max": 24576 + } + }, + "ownedBy": "google" + }, + { + "id": "moonshot-v1-auto", + "name": "moonshot-v1-auto", + "ownedBy": "moonshot" + }, + { + "id": "qwen3-asr-flash-realtime-2026-02-10", + "name": "qwen3-asr-flash-realtime-2026-02-10", + "capabilities": ["function-call", "reasoning"], + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 38912 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "qwen3-tts-vd-2026-01-26", + "name": "qwen3-tts-vd-2026-01-26", + "capabilities": ["audio-generation", "reasoning"], + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 38912 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "qwen3-tts-instruct-flash-2026-01-26", + "name": "qwen3-tts-instruct-flash-2026-01-26", + "capabilities": ["audio-generation"], + "ownedBy": "alibaba" + }, + { + "id": "qwen3-tts-instruct-flash", + "name": "qwen3-tts-instruct-flash", + "capabilities": ["audio-generation"], + "ownedBy": "alibaba" + }, + { + "id": "qwen3-tts-vc-2026-01-22", + "name": "qwen3-tts-vc-2026-01-22", + "capabilities": ["audio-generation", "reasoning"], + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 38912 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "qwen3-tts-instruct-flash-realtime-2026-01-22", + "name": "qwen3-tts-instruct-flash-realtime-2026-01-22", + "capabilities": ["audio-generation"], + "ownedBy": "alibaba" + }, + { + "id": "qwen3-tts-instruct-flash-realtime", + "name": "qwen3-tts-instruct-flash-realtime", + "capabilities": ["audio-generation"], + "ownedBy": "alibaba" + }, + { + "id": "qwen3-tts-vd-realtime-2026-01-15", + "name": "qwen3-tts-vd-realtime-2026-01-15", + "capabilities": ["audio-generation", "reasoning"], + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 38912 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "tongyi-xiaomi-analysis-flash", + "name": "tongyi-xiaomi-analysis-flash", + "ownedBy": "dashscope" + }, + { + "id": "tongyi-xiaomi-analysis-pro", + "name": "tongyi-xiaomi-analysis-pro", + "ownedBy": "dashscope" + }, + { + "id": "qwen3-tts-vc-realtime-2026-01-15", + "name": "qwen3-tts-vc-realtime-2026-01-15", + "capabilities": ["audio-generation", "reasoning"], + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 38912 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "qwen-image-edit-max-2026-01-16", + "name": "qwen-image-edit-max-2026-01-16", + "capabilities": ["function-call", "image-generation"], + "ownedBy": "alibaba" + }, + { + "id": "qwen-image-edit-max", + "name": "qwen-image-edit-max", + "capabilities": ["function-call", "image-generation"], + "ownedBy": "alibaba" + }, + { + "id": "qwen-image-plus-2026-01-09", + "name": "qwen-image-plus-2026-01-09", + "capabilities": ["function-call", "image-generation"], + "ownedBy": "alibaba" + }, + { + "id": "qwen-flash-character", + "name": "qwen-flash-character", + "capabilities": ["function-call", "reasoning"], + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 0, + "max": 81920 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "qwen-image-max-2025-12-30", + "name": "qwen-image-max-2025-12-30", + "capabilities": ["function-call", "image-generation"], + "ownedBy": "alibaba" + }, + { + "id": "z-image-turbo", + "name": "z-image-turbo", + "capabilities": ["image-generation"], + "ownedBy": "dashscope" + }, + { + "id": "qwen3-vl-plus-2025-12-19", + "name": "qwen3-vl-plus-2025-12-19", + "capabilities": ["function-call", "image-recognition", "reasoning"], + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 38912 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "qwen3-tts-vd-realtime-2025-12-16", + "name": "qwen3-tts-vd-realtime-2025-12-16", + "capabilities": ["audio-generation", "reasoning"], + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 38912 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "qwen-image-edit-plus-2025-12-15", + "name": "qwen-image-edit-plus-2025-12-15", + "capabilities": ["function-call", "image-generation"], + "ownedBy": "alibaba" + }, + { + "id": "qwen3-omni-flash-2025-12-01", + "name": "qwen3-omni-flash-2025-12-01", + "capabilities": ["function-call", "image-recognition", "reasoning"], + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 38912 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "qwen3-omni-flash-realtime-2025-12-01", + "name": "qwen3-omni-flash-realtime-2025-12-01", + "capabilities": ["function-call", "image-recognition", "reasoning"], + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 38912 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "qwen3-livetranslate-flash-2025-12-01", + "name": "qwen3-livetranslate-flash-2025-12-01", + "capabilities": ["function-call", "reasoning"], + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 38912 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "qwen3-livetranslate-flash", + "name": "qwen3-livetranslate-flash", + "capabilities": ["function-call", "reasoning"], + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 38912 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "qwen3-tts-vc-realtime-2025-11-27", + "name": "qwen3-tts-vc-realtime-2025-11-27", + "capabilities": ["audio-generation", "reasoning"], + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 38912 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "qwen3-tts-flash-2025-11-27", + "name": "qwen3-tts-flash-2025-11-27", + "capabilities": ["audio-generation", "reasoning"], + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 38912 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "qwen3-tts-flash-realtime-2025-11-27", + "name": "qwen3-tts-flash-realtime-2025-11-27", + "capabilities": ["audio-generation", "reasoning"], + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 38912 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "qwen-plus-2025-11-05", + "name": "qwen-plus-2025-11-05", + "capabilities": ["function-call", "reasoning"], + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 0, + "max": 81920 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "qwen-image-edit-plus-2025-10-30", + "name": "qwen-image-edit-plus-2025-10-30", + "capabilities": ["function-call", "image-generation"], + "ownedBy": "alibaba" + }, + { + "id": "qwen-deep-search-planning", + "name": "qwen-deep-search-planning", + "capabilities": ["function-call", "web-search"], + "ownedBy": "alibaba" + }, + { + "id": "qwen3-asr-flash-realtime-2025-10-27", + "name": "qwen3-asr-flash-realtime-2025-10-27", + "capabilities": ["function-call", "reasoning"], + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 38912 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "qwen3-asr-flash-realtime", + "name": "qwen3-asr-flash-realtime", + "capabilities": ["function-call", "reasoning"], + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 38912 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "qwen3-vl-flash-2025-10-15", + "name": "qwen3-vl-flash-2025-10-15", + "capabilities": ["function-call", "image-recognition", "reasoning"], + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 38912 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "qwen3-tts-flash", + "name": "qwen3-tts-flash", + "capabilities": ["audio-generation", "reasoning"], + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 38912 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "qwen3-tts-flash-2025-09-18", + "name": "qwen3-tts-flash-2025-09-18", + "capabilities": ["audio-generation", "reasoning"], + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 38912 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "qwen3-tts-flash-realtime-2025-09-18", + "name": "qwen3-tts-flash-realtime-2025-09-18", + "capabilities": ["audio-generation", "reasoning"], + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 38912 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "qwen3-tts-flash-realtime", + "name": "qwen3-tts-flash-realtime", + "capabilities": ["audio-generation", "reasoning"], + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 38912 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "qwen3-omni-flash-2025-09-15", + "name": "qwen3-omni-flash-2025-09-15", + "capabilities": ["function-call", "image-recognition", "reasoning"], + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 38912 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "qwen3-omni-flash-realtime-2025-09-15", + "name": "qwen3-omni-flash-realtime-2025-09-15", + "capabilities": ["function-call", "image-recognition", "reasoning"], + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 38912 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "qwen3-s2s-flash-realtime-2025-09-22", + "name": "qwen3-s2s-flash-realtime-2025-09-22", + "capabilities": ["function-call", "reasoning"], + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 38912 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "qwen3-livetranslate-flash-realtime-2025-09-22", + "name": "qwen3-livetranslate-flash-realtime-2025-09-22", + "capabilities": ["function-call", "reasoning"], + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 38912 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "qwen3-vl-plus-2025-09-23", + "name": "qwen3-vl-plus-2025-09-23", + "capabilities": ["function-call", "image-recognition", "reasoning"], + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 38912 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "qwen-tts-2025-05-22", + "name": "qwen-tts-2025-05-22", + "capabilities": ["audio-generation"], + "ownedBy": "alibaba" + }, + { + "id": "qwen-turbo-0919", + "name": "qwen-turbo-0919", + "capabilities": ["function-call", "reasoning"], + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 0, + "max": 38912 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "codeqwen1-5-chat", + "name": "codeqwen1-5-chat", + "ownedBy": "dashscope" + }, + { + "id": "qwen-max-1201", + "name": "qwen-max-1201", + "capabilities": ["function-call"], + "ownedBy": "alibaba" + }, + { + "id": "qwen-max-0107", + "name": "qwen-max-0107", + "capabilities": ["function-call"], + "ownedBy": "alibaba" + }, + { + "id": "step-1x", + "name": "step-1x", + "ownedBy": "stepfun" + }, + { + "id": "step-2-16k-202411", + "name": "step-2-16k-202411", + "ownedBy": "stepfun" + }, + { + "id": "step-asr", + "name": "step-asr", + "ownedBy": "stepfun" + }, + { + "id": "step-1o-audio", + "name": "step-1o-audio", + "ownedBy": "stepfun" + }, + { + "id": "step-tts-vivid", + "name": "step-tts-vivid", + "capabilities": ["audio-generation"], + "ownedBy": "stepfun" + }, + { + "id": "step-1x-edit", + "name": "step-1x-edit", + "ownedBy": "stepfun" + }, + { + "id": "step-1f-audio", + "name": "step-1f-audio", + "ownedBy": "stepfun" + }, + { + "id": "step-2x-large", + "name": "step-2x-large", + "ownedBy": "stepfun" + }, + { + "id": "step-audio-2", + "name": "step-audio-2", + "ownedBy": "stepfun" + }, + { + "id": "step-audio-2-mini", + "name": "step-audio-2-mini", + "ownedBy": "stepfun" + }, + { + "id": "step-tts-2", + "name": "step-tts-2", + "capabilities": ["audio-generation"], + "ownedBy": "stepfun" + }, + { + "id": "dr-search-api", + "name": "dr-search-api", + "capabilities": ["web-search"], + "ownedBy": "stepfun" + }, + { + "id": "step-gui", + "name": "step-gui", + "ownedBy": "stepfun" + }, + { + "id": "step-3-agent-lite", + "name": "step-3-agent-lite", + "capabilities": ["reasoning"], + "ownedBy": "stepfun" + }, + { + "id": "megrez-instruct", + "name": "megrez-instruct", + "ownedBy": "infini" + }, + { + "id": "seedance-1-0", + "name": "seedance-1-0", + "ownedBy": "bytedance" + }, + { + "id": "dracarys-llama-3-1-instruct", + "name": "dracarys-llama-3-1-instruct", + "ownedBy": "nvidia" + }, + { + "id": "fuyu", + "name": "fuyu", + "ownedBy": "nvidia" + }, + { + "id": "jamba-1-5-large-instruct", + "name": "jamba-1-5-large-instruct", + "ownedBy": "ai21" + }, + { + "id": "jamba-1-5-mini-instruct", + "name": "jamba-1-5-mini-instruct", + "ownedBy": "ai21" + }, + { + "id": "sea-lion-instruct", + "name": "sea-lion-instruct", + "ownedBy": "nvidia" + }, + { + "id": "baichuan2-chat", + "name": "baichuan2-chat", + "ownedBy": "baichuan" + }, + { + "id": "starcoder2", + "name": "starcoder2", + "ownedBy": "nvidia" + }, + { + "id": "dbrx-instruct", + "name": "dbrx-instruct", + "ownedBy": "nvidia" + }, + { + "id": "deplot", + "name": "deplot", + "ownedBy": "nvidia" + }, + { + "id": "gemma", + "name": "gemma", + "ownedBy": "google" + }, + { + "id": "paligemma", + "name": "paligemma", + "ownedBy": "nvidia" + }, + { + "id": "recurrentgemma", + "name": "recurrentgemma", + "ownedBy": "nvidia" + }, + { + "id": "shieldgemma", + "name": "shieldgemma", + "ownedBy": "nvidia" + }, + { + "id": "gemma-2-cpt-sahabatai-instruct", + "name": "gemma-2-cpt-sahabatai-instruct", + "ownedBy": "google" + }, + { + "id": "granite-3-0-a800m-instruct", + "name": "granite-3-0-a800m-instruct", + "ownedBy": "nvidia" + }, + { + "id": "granite-3-0-instruct", + "name": "granite-3-0-instruct", + "ownedBy": "nvidia" + }, + { + "id": "granite-3-3-instruct", + "name": "granite-3-3-instruct", + "ownedBy": "nvidia" + }, + { + "id": "granite-code-instruct", + "name": "granite-code-instruct", + "ownedBy": "nvidia" + }, + { + "id": "granite-guardian-3-0", + "name": "granite-guardian-3-0", + "ownedBy": "nvidia" + }, + { + "id": "colosseum_355b_instruct_16k", + "name": "colosseum_355b_instruct_16k", + "ownedBy": "nvidia" + }, + { + "id": "italia_10b_instruct_16k", + "name": "italia_10b_instruct_16k", + "ownedBy": "nvidia" + }, + { + "id": "llama-3-1-swallow-instruct-v0-1", + "name": "llama-3-1-swallow-instruct-v0-1", + "ownedBy": "meta" + }, + { + "id": "marin-instruct", + "name": "marin-instruct", + "ownedBy": "nvidia" + }, + { + "id": "breeze-instruct", + "name": "breeze-instruct", + "ownedBy": "nvidia" + }, + { + "id": "llama2", + "name": "llama2", + "ownedBy": "meta" + }, + { + "id": "kosmos-2", + "name": "kosmos-2", + "ownedBy": "nvidia" + }, + { + "id": "phi-4-mini-flash", + "name": "phi-4-mini-flash", + "ownedBy": "microsoft" + }, + { + "id": "mathstral-v0-1", + "name": "mathstral-v0-1", + "ownedBy": "nvidia" + }, + { + "id": "mistral-medium-3-instruct", + "name": "mistral-medium-3-instruct", + "ownedBy": "mistral" + }, + { + "id": "mistral-nemotron", + "name": "mistral-nemotron", + "ownedBy": "mistral" + }, + { + "id": "mistral-small-instruct", + "name": "mistral-small-instruct", + "ownedBy": "mistral" + }, + { + "id": "mixtral-8x22b-instruct-v0-1", + "name": "mixtral-8x22b-instruct-v0-1", + "ownedBy": "mistral" + }, + { + "id": "mixtral-8x22b-v0-1", + "name": "mixtral-8x22b-v0-1", + "ownedBy": "mistral" + }, + { + "id": "cosmos-reason2", + "name": "cosmos-reason2", + "ownedBy": "nvidia" + }, + { + "id": "embed-qa-4", + "name": "embed-qa-4", + "capabilities": ["embedding"], + "ownedBy": "cohere" + }, + { + "id": "llama-3-1-nemoguard-content-safety", + "name": "llama-3-1-nemoguard-content-safety", + "ownedBy": "meta" + }, + { + "id": "llama-3-1-nemoguard-topic-control", + "name": "llama-3-1-nemoguard-topic-control", + "ownedBy": "meta" + }, + { + "id": "llama-3-1-nemotron-reward", + "name": "llama-3-1-nemotron-reward", + "ownedBy": "meta" + }, + { + "id": "llama-3-1-nemotron-nano-v1-1", + "name": "llama-3-1-nemotron-nano-v1-1", + "ownedBy": "meta" + }, + { + "id": "llama-3-1-nemotron-nano-v1", + "name": "llama-3-1-nemotron-nano-v1", + "ownedBy": "meta" + }, + { + "id": "llama-3-1-nemotron-nano-vl-v1", + "name": "llama-3-1-nemotron-nano-vl-v1", + "capabilities": ["image-recognition"], + "ownedBy": "meta" + }, + { + "id": "llama-3-1-nemotron-safety-guard-v3", + "name": "llama-3-1-nemotron-safety-guard-v3", + "ownedBy": "meta" + }, + { + "id": "llama-3-2-nemoretriever-vlm-embed-v1", + "name": "llama-3-2-nemoretriever-vlm-embed-v1", + "capabilities": ["embedding"], + "ownedBy": "meta" + }, + { + "id": "llama-3-2-nemoretriever-300m-embed-v1", + "name": "llama-3-2-nemoretriever-300m-embed-v1", + "capabilities": ["embedding"], + "ownedBy": "meta" + }, + { + "id": "llama-3-2-nemoretriever-300m-embed-v2", + "name": "llama-3-2-nemoretriever-300m-embed-v2", + "capabilities": ["embedding"], + "ownedBy": "meta" + }, + { + "id": "llama-3-2-nv-embedqa-v1", + "name": "llama-3-2-nv-embedqa-v1", + "capabilities": ["embedding"], + "ownedBy": "meta" + }, + { + "id": "llama-3-2-nv-embedqa-v2", + "name": "llama-3-2-nv-embedqa-v2", + "capabilities": ["embedding"], + "ownedBy": "meta" + }, + { + "id": "llama-nemotron-embed-vl-v2", + "name": "llama-nemotron-embed-vl-v2", + "capabilities": ["embedding"], + "ownedBy": "meta" + }, + { + "id": "mistral-nemo-minitron-8k-instruct", + "name": "mistral-nemo-minitron-8k-instruct", + "ownedBy": "mistral" + }, + { + "id": "mistral-nemo-minitron-base", + "name": "mistral-nemo-minitron-base", + "ownedBy": "mistral" + }, + { + "id": "nemoretriever-parse", + "name": "nemoretriever-parse", + "ownedBy": "nvidia" + }, + { + "id": "nemotron-4-reward", + "name": "nemotron-4-reward", + "ownedBy": "nvidia" + }, + { + "id": "nemotron-4-mini-hindi-instruct", + "name": "nemotron-4-mini-hindi-instruct", + "ownedBy": "nvidia" + }, + { + "id": "nemotron-content-safety-reasoning", + "name": "nemotron-content-safety-reasoning", + "capabilities": ["reasoning"], + "ownedBy": "nvidia" + }, + { + "id": "nemotron-mini-instruct", + "name": "nemotron-mini-instruct", + "ownedBy": "nvidia" + }, + { + "id": "nemotron-nano-3-a3b", + "name": "nemotron-nano-3-a3b", + "ownedBy": "nvidia" + }, + { + "id": "nemotron-parse", + "name": "nemotron-parse", + "ownedBy": "nvidia" + }, + { + "id": "neva", + "name": "neva", + "ownedBy": "nvidia" + }, + { + "id": "nv-embed-v1", + "name": "nv-embed-v1", + "capabilities": ["embedding"], + "ownedBy": "nvidia" + }, + { + "id": "nv-embedcode-v1", + "name": "nv-embedcode-v1", + "capabilities": ["embedding"], + "ownedBy": "nvidia" + }, + { + "id": "nv-embedqa-e5-v5", + "name": "nv-embedqa-e5-v5", + "capabilities": ["embedding"], + "ownedBy": "nvidia" + }, + { + "id": "nv-embedqa-mistral-v2", + "name": "nv-embedqa-mistral-v2", + "capabilities": ["embedding"], + "ownedBy": "nvidia" + }, + { + "id": "nvclip", + "name": "nvclip", + "ownedBy": "nvidia" + }, + { + "id": "riva-translate-instruct", + "name": "riva-translate-instruct", + "ownedBy": "nvidia" + }, + { + "id": "riva-translate-instruct-v1-1", + "name": "riva-translate-instruct-v1-1", + "ownedBy": "nvidia" + }, + { + "id": "streampetr", + "name": "streampetr", + "ownedBy": "nvidia" + }, + { + "id": "usdcode-llama-3-1-instruct", + "name": "usdcode-llama-3-1-instruct", + "ownedBy": "nvidia" + }, + { + "id": "vila", + "name": "vila", + "ownedBy": "nvidia" + }, + { + "id": "teuken-instruct-commercial-v0-4", + "name": "teuken-instruct-commercial-v0-4", + "ownedBy": "nvidia" + }, + { + "id": "rakutenai-chat", + "name": "rakutenai-chat", + "ownedBy": "nvidia" + }, + { + "id": "rakutenai-instruct", + "name": "rakutenai-instruct", + "ownedBy": "nvidia" + }, + { + "id": "arctic-embed-l", + "name": "arctic-embed-l", + "capabilities": ["embedding"], + "ownedBy": "nvidia" + }, + { + "id": "bielik-v2-3-instruct", + "name": "bielik-v2-3-instruct", + "ownedBy": "nvidia" + }, + { + "id": "stockmark-2-instruct", + "name": "stockmark-2-instruct", + "ownedBy": "nvidia" + }, + { + "id": "chatglm3", + "name": "chatglm3", + "ownedBy": "nvidia" + }, + { + "id": "falcon3-instruct", + "name": "falcon3-instruct", + "ownedBy": "nvidia" + }, + { + "id": "llama-3-swallow-instruct-v0-1", + "name": "llama-3-swallow-instruct-v0-1", + "ownedBy": "meta" + }, + { + "id": "solar-instruct", + "name": "solar-instruct", + "ownedBy": "upstageai" + }, + { + "id": "eurollm-instruct", + "name": "eurollm-instruct", + "ownedBy": "nvidia" + }, + { + "id": "palmyra-creative", + "name": "palmyra-creative", + "ownedBy": "google" + }, + { + "id": "palmyra-fin-32k", + "name": "palmyra-fin-32k", + "ownedBy": "google" + }, + { + "id": "palmyra-med", + "name": "palmyra-med", + "ownedBy": "google" + }, + { + "id": "palmyra-med-32k", + "name": "palmyra-med-32k", + "ownedBy": "google" + }, + { + "id": "llama-3-taiwan-instruct", + "name": "llama-3-taiwan-instruct", + "capabilities": ["video-generation"], + "ownedBy": "meta" + }, + { + "id": "zamba2-instruct", + "name": "zamba2-instruct", + "ownedBy": "nvidia" + }, + { + "id": "grok-2-image-1212", + "name": "grok-2-image-1212", + "capabilities": ["image-generation", "web-search"], + "ownedBy": "xai" + }, + { + "id": "grok-imagine-video", + "name": "grok-imagine-video", + "capabilities": ["video-generation", "web-search"], + "ownedBy": "xai" + }, + { + "id": "gpt-oss-turbo", + "name": "gpt-oss-turbo", + "capabilities": ["reasoning"], + "contextWindow": 131072, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.3 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.3 + } + }, + "reasoning": { + "supportedEfforts": ["low", "medium", "high"] + }, + "ownedBy": "openai" + }, + { + "id": "stablediffusion", + "name": "stablediffusion", + "ownedBy": "hyperbolic" + }, + { + "id": "tts", + "name": "tts", + "ownedBy": "hyperbolic" + }, + { + "id": "mistral-vibe-cli-with-tools", + "name": "mistral-medium-2508", + "contextWindow": 131072, + "ownedBy": "mistral" + }, + { + "id": "open-mistral-nemo", + "name": "open-mistral-nemo", + "contextWindow": 131072, + "ownedBy": "mistral" + }, + { + "id": "open-mistral-nemo-2407", + "name": "open-mistral-nemo", + "contextWindow": 131072, + "ownedBy": "mistral" + }, + { + "id": "mistral-tiny-2407", + "name": "open-mistral-nemo", + "contextWindow": 131072, + "ownedBy": "mistral" + }, + { + "id": "mistral-tiny-latest", + "name": "open-mistral-nemo", + "contextWindow": 131072, + "ownedBy": "mistral" + }, + { + "id": "mistral-large-pixtral-2411", + "name": "pixtral-large-2411", + "capabilities": ["function-call", "image-recognition"], + "contextWindow": 131072, + "ownedBy": "mistral" + }, + { + "id": "mistral-vibe-cli-latest", + "name": "devstral-2512", + "contextWindow": 262144, + "ownedBy": "mistral" + }, + { + "id": "devstral-latest", + "name": "devstral-2512", + "contextWindow": 262144, + "ownedBy": "mistral" + }, + { + "id": "devstral-small-latest", + "name": "labs-devstral-small-2512", + "contextWindow": 262144, + "ownedBy": "mistral" + }, + { + "id": "labs-mistral-small-creative", + "name": "labs-mistral-small-creative", + "contextWindow": 32768, + "ownedBy": "mistral" + }, + { + "id": "magistral-medium-2509", + "name": "magistral-medium-2509", + "capabilities": ["reasoning"], + "contextWindow": 131072, + "ownedBy": "mistral" + }, + { + "id": "magistral-small-latest", + "name": "magistral-small-2509", + "capabilities": ["reasoning"], + "contextWindow": 131072, + "ownedBy": "mistral" + }, + { + "id": "voxtral-mini-2507", + "name": "voxtral-mini-2507", + "contextWindow": 32768, + "ownedBy": "mistral" + }, + { + "id": "voxtral-mini-latest", + "name": "voxtral-mini-2507", + "contextWindow": 32768, + "ownedBy": "mistral" + }, + { + "id": "voxtral-small-latest", + "name": "voxtral-small-2507", + "contextWindow": 32768, + "ownedBy": "mistral" + }, + { + "id": "mistral-moderation-2411", + "name": "mistral-moderation-2411", + "contextWindow": 8192, + "ownedBy": "mistral" + }, + { + "id": "mistral-moderation-latest", + "name": "mistral-moderation-2411", + "contextWindow": 8192, + "ownedBy": "mistral" + }, + { + "id": "mistral-ocr-2512", + "name": "mistral-ocr-2512", + "capabilities": ["file-input"], + "contextWindow": 16384, + "ownedBy": "mistral" + }, + { + "id": "mistral-ocr-latest", + "name": "mistral-ocr-2512", + "capabilities": ["file-input"], + "contextWindow": 16384, + "ownedBy": "mistral" + }, + { + "id": "mistral-ocr-2505", + "name": "mistral-ocr-2505", + "capabilities": ["file-input"], + "contextWindow": 16384, + "ownedBy": "mistral" + }, + { + "id": "voxtral-mini-2602", + "name": "voxtral-mini-2602", + "contextWindow": 16384, + "ownedBy": "mistral" + }, + { + "id": "voxtral-mini-transcribe-2507", + "name": "voxtral-mini-transcribe-2507", + "contextWindow": 16384, + "ownedBy": "mistral" + }, + { + "id": "jina-vlm", + "name": "Jina AI: Jina VLM", + "contextWindow": 32768, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.049999999999999996 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "ownedBy": "jina" + }, + { + "id": "jina-code-embeddings", + "name": "Jina AI: Jina Code Embeddings 0.5b", + "capabilities": ["embedding"], + "contextWindow": 32768, + "maxOutputTokens": 896, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.049999999999999996 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "ownedBy": "jina" + }, + { + "id": "readerlm-v2", + "name": "Jina AI: ReaderLM v2", + "contextWindow": 524288, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.049999999999999996 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "ownedBy": "jina" + }, + { + "id": "reader-lm", + "name": "Jina AI: Reader LM 0.5b", + "contextWindow": 262144, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.049999999999999996 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "ownedBy": "jina" + }, + { + "id": "jina-embedding-b-en-v1", + "name": "Jina AI: Jina Embedding B EN v1", + "capabilities": ["embedding"], + "contextWindow": 512, + "maxOutputTokens": 768, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.049999999999999996 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "ownedBy": "jina" + }, + { + "id": "c4ai-command-r-plus-08-2024", + "name": "c4ai-command-r-plus-08-2024", + "ownedBy": "modelscope" + }, + { + "id": "ministral-instruct-2410", + "name": "ministral-instruct-2410", + "ownedBy": "mistral" + }, + { + "id": "mistral-large-instruct-2407", + "name": "mistral-large-instruct-2407", + "capabilities": ["function-call"], + "ownedBy": "mistral" + }, + { + "id": "mistral-small-instruct-2409", + "name": "mistral-small-instruct-2409", + "ownedBy": "mistral" + }, + { + "id": "compassjudger-1-instruct", + "name": "compassjudger-1-instruct", + "ownedBy": "modelscope" + }, + { + "id": "internvl3_5-a28b", + "name": "internvl3_5-a28b", + "capabilities": ["image-recognition"], + "ownedBy": "intern" + }, + { + "id": "ernie-4-5-pt", + "name": "ernie-4-5-pt", + "ownedBy": "baidu" + }, + { + "id": "ernie-4-5-a3b-pt", + "name": "ernie-4-5-a3b-pt", + "ownedBy": "baidu" + }, + { + "id": "ernie-4-5-a47b-pt", + "name": "ernie-4-5-a47b-pt", + "ownedBy": "baidu" + }, + { + "id": "ernie-4-5-vl-a3b-pt", + "name": "ernie-4-5-vl-a3b-pt", + "capabilities": ["image-recognition"], + "ownedBy": "baidu" + }, + { + "id": "intern-s1", + "name": "intern-s1", + "ownedBy": "intern" + }, + { + "id": "intern-s1-mini", + "name": "intern-s1-mini", + "ownedBy": "intern" + }, + { + "id": "xiyansql-qwencoder-2412", + "name": "xiyansql-qwencoder-2412", + "capabilities": ["function-call"], + "ownedBy": "modelscope" + }, + { + "id": "xiyansql-qwencoder-2504", + "name": "xiyansql-qwencoder-2504", + "capabilities": ["function-call"], + "ownedBy": "modelscope" + }, + { + "id": "hunyuan-standard-32k", + "name": "hunyuan-standard-32k", + "capabilities": ["function-call"], + "ownedBy": "tencent" + }, + { + "id": "hunyuan-turbo-latest", + "name": "hunyuan-turbo-latest", + "capabilities": ["function-call"], + "ownedBy": "tencent" + }, + { + "id": "hunyuan-turbos-20250515", + "name": "hunyuan-turbos-20250515", + "capabilities": ["function-call"], + "ownedBy": "tencent" + }, + { + "id": "hunyuan-t1-vision", + "name": "hunyuan-t1-vision", + "capabilities": ["reasoning", "function-call", "image-recognition"], + "ownedBy": "tencent" + }, + { + "id": "hunyuan-large-vision", + "name": "hunyuan-large-vision", + "capabilities": ["function-call", "image-recognition"], + "ownedBy": "tencent" + }, + { + "id": "hunyuan-turbos-20250604", + "name": "hunyuan-turbos-20250604", + "capabilities": ["function-call"], + "ownedBy": "tencent" + }, + { + "id": "hunyuan-t1-20250529", + "name": "hunyuan-t1-20250529", + "capabilities": ["reasoning", "function-call"], + "ownedBy": "tencent" + }, + { + "id": "hunyuan-turbos-vision-20250619", + "name": "hunyuan-turbos-vision-20250619", + "capabilities": ["function-call", "image-recognition"], + "ownedBy": "tencent" + }, + { + "id": "hunyuan-t1-vision-20250619", + "name": "hunyuan-t1-vision-20250619", + "capabilities": ["reasoning", "function-call", "image-recognition"], + "ownedBy": "tencent" + }, + { + "id": "hunyuan-vision-20250720", + "name": "hunyuan-vision-20250720", + "capabilities": ["function-call", "image-recognition"], + "ownedBy": "tencent" + }, + { + "id": "hunyuan-large-role-20250822", + "name": "hunyuan-large-role-20250822", + "capabilities": ["function-call"], + "ownedBy": "tencent" + }, + { + "id": "hunyuan-t1-20250822", + "name": "hunyuan-t1-20250822", + "capabilities": ["reasoning", "function-call"], + "ownedBy": "tencent" + }, + { + "id": "hunyuan-t1-vision-20250916", + "name": "hunyuan-t1-vision-20250916", + "capabilities": ["reasoning", "function-call", "image-recognition"], + "ownedBy": "tencent" + }, + { + "id": "hunyuan-turbos-20250926", + "name": "hunyuan-turbos-20250926", + "capabilities": ["function-call"], + "ownedBy": "tencent" + }, + { + "id": "hunyuan-large-role-latest", + "name": "hunyuan-large-role-latest", + "capabilities": ["function-call"], + "ownedBy": "tencent" + }, + { + "id": "hunyuan-turbos-role-20251114", + "name": "hunyuan-turbos-role-20251114", + "capabilities": ["function-call"], + "ownedBy": "tencent" + }, + { + "id": "hunyuan-role-latest", + "name": "hunyuan-role-latest", + "capabilities": ["function-call"], + "ownedBy": "tencent" + }, + { + "id": "hunyuan-vision-1-5-instruct", + "name": "hunyuan-vision-1-5-instruct", + "capabilities": ["function-call", "image-recognition"], + "ownedBy": "tencent" + }, + { + "id": "qwen3-instruct-2507", + "name": "qwen3-instruct-2507", + "capabilities": ["function-call"], + "ownedBy": "alibaba" + }, + { + "id": "apriel-1-6-thinker", + "name": "apriel-1-6-thinker", + "ownedBy": "huggingface" + }, + { + "id": "qwen3-thinking-2507", + "name": "qwen3-thinking-2507", + "capabilities": ["reasoning", "function-call"], + "reasoning": { + "supportedEfforts": ["low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 38912 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "smollm3", + "name": "smollm3", + "ownedBy": "huggingface" + }, + { + "id": "molmo2", + "name": "molmo2", + "ownedBy": "ai2" + }, + { + "id": "aya-vision", + "name": "aya-vision", + "capabilities": ["image-recognition"], + "ownedBy": "huggingface" + }, + { + "id": "c4ai-command-r7b-12-2024", + "name": "c4ai-command-r7b-12-2024", + "capabilities": ["reasoning"], + "ownedBy": "huggingface" + }, + { + "id": "c4ai-command-a-03-2025", + "name": "c4ai-command-a-03-2025", + "ownedBy": "huggingface" + }, + { + "id": "apertus-instruct-2509", + "name": "apertus-instruct-2509", + "ownedBy": "huggingface" + }, + { + "id": "glm-4-5-air-fp8", + "name": "glm-4-5-air-fp8", + "capabilities": ["function-call", "reasoning"], + "reasoning": { + "supportedEfforts": ["none"], + "interleaved": true + }, + "ownedBy": "zhipu" + }, + { + "id": "eurollm-instruct-2512", + "name": "eurollm-instruct-2512", + "ownedBy": "huggingface" + }, + { + "id": "arch-router", + "name": "arch-router", + "ownedBy": "huggingface" + }, + { + "id": "llama-3-3-swallow-instruct-v0-4", + "name": "llama-3-3-swallow-instruct-v0-4", + "ownedBy": "meta" + }, + { + "id": "glm-4-5v-fp8", + "name": "glm-4-5v-fp8", + "capabilities": ["reasoning"], + "reasoning": { + "supportedEfforts": ["none"], + "interleaved": true + }, + "ownedBy": "zhipu" + }, + { + "id": "glm-4-6v-fp8", + "name": "glm-4-6v-fp8", + "capabilities": ["function-call", "reasoning"], + "reasoning": { + "supportedEfforts": ["none"], + "interleaved": true + }, + "ownedBy": "zhipu" + }, + { + "id": "l3-lunaris-v1", + "name": "l3-lunaris-v1", + "ownedBy": "huggingface" + }, + { + "id": "qwen-sea-lion-v4-it", + "name": "qwen-sea-lion-v4-it", + "capabilities": ["function-call"], + "ownedBy": "alibaba" + }, + { + "id": "ernie-4-5-a47b-base-pt", + "name": "ernie-4-5-a47b-base-pt", + "ownedBy": "baidu" + }, + { + "id": "ernie-4-5-vl-a47b-base-pt", + "name": "ernie-4-5-vl-a47b-base-pt", + "capabilities": ["image-recognition"], + "ownedBy": "baidu" + }, + { + "id": "cogito-v2-1-fp8", + "name": "cogito-v2-1-fp8", + "ownedBy": "cogito" + }, + { + "id": "dictalm-3-0", + "name": "dictalm-3-0", + "ownedBy": "huggingface" + }, + { + "id": "c4ai-command-r-08-2024", + "name": "c4ai-command-r-08-2024", + "ownedBy": "huggingface" + }, + { + "id": "aya-expanse", + "name": "aya-expanse", + "ownedBy": "huggingface" + }, + { + "id": "c4ai-command-r7b-arabic-02-2025", + "name": "c4ai-command-r7b-arabic-02-2025", + "capabilities": ["reasoning"], + "ownedBy": "huggingface" + }, + { + "id": "wan-v2-5-t2v-preview", + "name": "Wan v2.5 Text-to-Video Preview", + "capabilities": ["video-generation"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "wan-v2-6-i2v", + "name": "Wan v2.6 Image-to-Video", + "capabilities": ["video-generation"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "wan-v2-6-i2v-flash", + "name": "Wan v2.6 Image-to-Video Flash", + "capabilities": ["video-generation"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "wan-v2-6-r2v", + "name": "Wan v2.6 Reference-to-Video", + "capabilities": ["reasoning", "video-generation"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "wan-v2-6-r2v-flash", + "name": "Wan v2.6 Reference-to-Video Flash", + "capabilities": ["reasoning", "video-generation"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "wan-v2-6-t2v", + "name": "Wan v2.6 Text-to-Video", + "capabilities": ["video-generation"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "veo-3-1-fast-generate-001", + "name": "Veo 3.1 Fast Generate", + "capabilities": ["video-generation"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "ownedBy": "google" + }, + { + "id": "veo-3-1-generate-001", + "name": "Veo 3.1", + "capabilities": ["video-generation"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "ownedBy": "google" + }, + { + "id": "kling-v2-5-turbo-i2v", + "name": "Kling v2.5 Turbo Image-to-Video", + "capabilities": ["video-generation"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "ownedBy": "kling" + }, + { + "id": "kling-v2-5-turbo-t2v", + "name": "Kling v2.5 Turbo Text-to-Video", + "capabilities": ["video-generation"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "ownedBy": "kling" + }, + { + "id": "kling-v2-6-i2v", + "name": "Kling v2.6 Image-to-Video", + "capabilities": ["video-generation"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "ownedBy": "kling" + }, + { + "id": "kling-v2-6-t2v", + "name": "Kling v2.6 Text-to-Video", + "capabilities": ["video-generation"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "ownedBy": "kling" + }, + { + "id": "kling-v3-0-i2v", + "name": "Kling v3.0 Image-to-Video", + "capabilities": ["video-generation"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "ownedBy": "kling" + }, + { + "id": "kling-v3-0-t2v", + "name": "Kling v3.0 Text-to-Video", + "capabilities": ["video-generation"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "ownedBy": "kling" + }, + { + "id": "recraft-v4", + "name": "Recraft V4", + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "ownedBy": "recraft" + }, + { + "id": "recraft-v4-pro", + "name": "Recraft V4 Pro", + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "ownedBy": "recraft" + }, + { + "id": "qwen3-asr", + "name": "qwen3-asr", + "capabilities": ["function-call", "reasoning"], + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 38912 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "z-image", + "name": "z-image", + "capabilities": ["image-generation"], + "ownedBy": "gitee-ai" + }, + { + "id": "qwen3-tts", + "name": "qwen3-tts", + "capabilities": ["function-call", "reasoning"], + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 38912 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "legalone", + "name": "legalone", + "ownedBy": "gitee-ai" + }, + { + "id": "qwen-image-layered", + "name": "qwen-image-layered", + "capabilities": ["function-call", "image-generation"], + "ownedBy": "alibaba" + }, + { + "id": "qwen3-vl-reranker", + "name": "qwen3-vl-reranker", + "capabilities": ["function-call", "rerank", "image-recognition", "reasoning"], + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 38912 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "ltx-2", + "name": "ltx-2", + "ownedBy": "gitee-ai" + }, + { + "id": "mai-ui", + "name": "mai-ui", + "ownedBy": "gitee-ai" + }, + { + "id": "qwen-image-2512", + "name": "qwen-image-2512", + "capabilities": ["function-call", "image-generation"], + "ownedBy": "alibaba" + }, + { + "id": "sinong1-0", + "name": "sinong1-0", + "ownedBy": "gitee-ai" + }, + { + "id": "hy-mt1-5", + "name": "hy-mt1-5", + "ownedBy": "gitee-ai" + }, + { + "id": "vajrav1", + "name": "vajrav1", + "ownedBy": "gitee-ai" + }, + { + "id": "cosyvoice3", + "name": "cosyvoice3", + "ownedBy": "gitee-ai" + }, + { + "id": "sam3", + "name": "sam3", + "ownedBy": "gitee-ai" + }, + { + "id": "fun-asr-nano-2512", + "name": "fun-asr-nano-2512", + "ownedBy": "gitee-ai" + }, + { + "id": "qwen-image-edit-2511", + "name": "qwen-image-edit-2511", + "capabilities": ["function-call", "image-generation"], + "ownedBy": "alibaba" + }, + { + "id": "flux.2-dev", + "name": "flux.2-dev", + "capabilities": ["image-generation"], + "ownedBy": "bfl" + }, + { + "id": "infinitetalk", + "name": "infinitetalk", + "ownedBy": "gitee-ai" + }, + { + "id": "glm-asr", + "name": "glm-asr", + "ownedBy": "zhipu" + }, + { + "id": "longcat-image-edit", + "name": "longcat-image-edit", + "capabilities": ["image-generation"], + "ownedBy": "meituan" + }, + { + "id": "longcat-image", + "name": "longcat-image", + "capabilities": ["image-generation"], + "ownedBy": "meituan" + }, + { + "id": "hulu-med", + "name": "hulu-med", + "ownedBy": "gitee-ai" + }, + { + "id": "longcat-video", + "name": "longcat-video", + "capabilities": ["video-generation"], + "ownedBy": "meituan" + }, + { + "id": "hunyuanocr", + "name": "hunyuanocr", + "capabilities": ["function-call"], + "ownedBy": "tencent" + }, + { + "id": "ernie-5-0", + "name": "ernie-5-0", + "ownedBy": "baidu" + }, + { + "id": "huatuogpt-o1", + "name": "huatuogpt-o1", + "capabilities": ["function-call", "image-recognition"], + "ownedBy": "gitee-ai" + }, + { + "id": "hunyuanvideo-1-5", + "name": "hunyuanvideo-1-5", + "capabilities": ["function-call", "video-generation"], + "ownedBy": "tencent" + }, + { + "id": "search", + "name": "search", + "ownedBy": "gitee-ai" + }, + { + "id": "seedvr2", + "name": "seedvr2", + "ownedBy": "bytedance" + }, + { + "id": "comfyui", + "name": "comfyui", + "ownedBy": "gitee-ai" + }, + { + "id": "audiofly", + "name": "audiofly", + "ownedBy": "gitee-ai" + }, + { + "id": "youtu-embedding", + "name": "youtu-embedding", + "capabilities": ["embedding"], + "ownedBy": "gitee-ai" + }, + { + "id": "mineru2-5", + "name": "mineru2-5", + "ownedBy": "gitee-ai" + }, + { + "id": "deepseek-v3_1-terminus", + "name": "deepseek-v3_1-terminus", + "capabilities": ["function-call", "reasoning"], + "reasoning": { + "supportedEfforts": ["none"] + }, + "ownedBy": "deepseek" + }, + { + "id": "vibevoice-large", + "name": "vibevoice-large", + "ownedBy": "gitee-ai" + }, + { + "id": "hunyuan-mt-chimera", + "name": "hunyuan-mt-chimera", + "capabilities": ["function-call"], + "ownedBy": "tencent" + }, + { + "id": "deepseek-v3_1", + "name": "deepseek-v3_1", + "capabilities": ["function-call", "reasoning"], + "reasoning": { + "supportedEfforts": ["none"] + }, + "ownedBy": "deepseek" + }, + { + "id": "codesage-large-v2", + "name": "codesage-large-v2", + "ownedBy": "gitee-ai" + }, + { + "id": "glm-4_5v", + "name": "glm-4_5v", + "capabilities": ["function-call"], + "ownedBy": "zhipu" + }, + { + "id": "nonescape-v0", + "name": "nonescape-v0", + "ownedBy": "gitee-ai" + }, + { + "id": "flux_1-krea-dev", + "name": "flux_1-krea-dev", + "capabilities": ["image-generation"], + "ownedBy": "bfl" + }, + { + "id": "wan2_2-i2v-a14b", + "name": "wan2_2-i2v-a14b", + "capabilities": ["video-generation"], + "ownedBy": "alibaba" + }, + { + "id": "cogview4_6b", + "name": "cogview4_6b", + "ownedBy": "zhipu" + }, + { + "id": "glm-4_5-air", + "name": "glm-4_5-air", + "capabilities": ["function-call"], + "ownedBy": "zhipu" + }, + { + "id": "glm-4_5", + "name": "glm-4_5", + "capabilities": ["function-call"], + "ownedBy": "zhipu" + }, + { + "id": "seed-x-ppo", + "name": "seed-x-ppo", + "ownedBy": "bytedance" + }, + { + "id": "f5-tts", + "name": "f5-tts", + "ownedBy": "gitee-ai" + }, + { + "id": "bge-reranker-large", + "name": "bge-reranker-large", + "capabilities": ["embedding", "rerank"], + "ownedBy": "baai" + }, + { + "id": "clip-vit", + "name": "clip-vit", + "ownedBy": "gitee-ai" + }, + { + "id": "resnet-50", + "name": "resnet-50", + "ownedBy": "gitee-ai" + }, + { + "id": "flux.1-kontext-dev", + "name": "flux.1-kontext-dev", + "capabilities": ["image-generation"], + "ownedBy": "bfl" + }, + { + "id": "funasr", + "name": "funasr", + "ownedBy": "gitee-ai" + }, + { + "id": "yolov8", + "name": "yolov8", + "ownedBy": "gitee-ai" + }, + { + "id": "animesharp", + "name": "animesharp", + "ownedBy": "gitee-ai" + }, + { + "id": "duix.heygem", + "name": "duix.heygem", + "ownedBy": "gitee-ai" + }, + { + "id": "ernie-4-5-turbo", + "name": "ernie-4-5-turbo", + "ownedBy": "baidu" + }, + { + "id": "indextts-1-5", + "name": "indextts-1-5", + "capabilities": ["audio-generation"], + "ownedBy": "gitee-ai" + }, + { + "id": "lingshu", + "name": "lingshu", + "ownedBy": "bailing" + }, + { + "id": "hellomeme", + "name": "hellomeme", + "ownedBy": "gitee-ai" + }, + { + "id": "instantcharacter", + "name": "instantcharacter", + "ownedBy": "gitee-ai" + }, + { + "id": "omniconsistency", + "name": "omniconsistency", + "ownedBy": "gitee-ai" + }, + { + "id": "real-esrgan", + "name": "real-esrgan", + "ownedBy": "gitee-ai" + }, + { + "id": "medgemma-it", + "name": "medgemma-it", + "ownedBy": "gitee-ai" + }, + { + "id": "dreamo", + "name": "dreamo", + "ownedBy": "gitee-ai" + }, + { + "id": "step1x-3d", + "name": "step1x-3d", + "ownedBy": "stepfun" + }, + { + "id": "ace-step-v1", + "name": "ace-step-v1", + "ownedBy": "gitee-ai" + }, + { + "id": "hidream-e1-full", + "name": "hidream-e1-full", + "ownedBy": "gitee-ai" + }, + { + "id": "hi3dgen", + "name": "hi3dgen", + "ownedBy": "gitee-ai" + }, + { + "id": "hidream-i1-full", + "name": "hidream-i1-full", + "ownedBy": "gitee-ai" + }, + { + "id": "stable-diffusion-v1-5", + "name": "stable-diffusion-v1-5", + "capabilities": ["image-generation"], + "ownedBy": "stability" + }, + { + "id": "healthgpt-l14", + "name": "healthgpt-l14", + "ownedBy": "gitee-ai" + }, + { + "id": "dianjin-r1", + "name": "dianjin-r1", + "capabilities": ["reasoning"], + "ownedBy": "gitee-ai" + }, + { + "id": "nomic-embed-code", + "name": "nomic-embed-code", + "capabilities": ["embedding"], + "ownedBy": "gitee-ai" + }, + { + "id": "spark-tts", + "name": "spark-tts", + "ownedBy": "gitee-ai" + }, + { + "id": "moark-text-moderation", + "name": "moark-text-moderation", + "ownedBy": "gitee-ai" + }, + { + "id": "megatts3", + "name": "megatts3", + "ownedBy": "gitee-ai" + }, + { + "id": "hunyuan3d-2", + "name": "hunyuan3d-2", + "capabilities": ["function-call"], + "ownedBy": "tencent" + }, + { + "id": "moark-m1", + "name": "moark-m1", + "ownedBy": "gitee-ai" + }, + { + "id": "fin-r1", + "name": "fin-r1", + "capabilities": ["reasoning"], + "ownedBy": "gitee-ai" + }, + { + "id": "pdf-extract-kit-1-0", + "name": "pdf-extract-kit-1-0", + "ownedBy": "gitee-ai" + }, + { + "id": "internlm3-instruct", + "name": "internlm3-instruct", + "ownedBy": "intern" + }, + { + "id": "wan2-1-i2v-480p", + "name": "wan2-1-i2v-480p", + "capabilities": ["video-generation"], + "ownedBy": "alibaba" + }, + { + "id": "wan2-1-i2v-720p", + "name": "wan2-1-i2v-720p", + "capabilities": ["video-generation"], + "ownedBy": "alibaba" + }, + { + "id": "wan2-1-t2v", + "name": "wan2-1-t2v", + "capabilities": ["video-generation"], + "ownedBy": "alibaba" + }, + { + "id": "step-audio-tts", + "name": "step-audio-tts", + "ownedBy": "stepfun" + }, + { + "id": "stepvideo-t2v", + "name": "stepvideo-t2v", + "capabilities": ["video-generation"], + "ownedBy": "stepfun" + }, + { + "id": "align-ds-v", + "name": "align-ds-v", + "ownedBy": "gitee-ai" + }, + { + "id": "florence-2-large", + "name": "florence-2-large", + "ownedBy": "gitee-ai" + }, + { + "id": "security-semantic-filtering", + "name": "security-semantic-filtering", + "ownedBy": "gitee-ai" + }, + { + "id": "nsfw-classifier", + "name": "nsfw-classifier", + "ownedBy": "gitee-ai" + }, + { + "id": "got-ocr2_0", + "name": "got-ocr2_0", + "ownedBy": "gitee-ai" + }, + { + "id": "internvl2-5", + "name": "internvl2-5", + "capabilities": ["image-recognition"], + "ownedBy": "intern" + }, + { + "id": "uvdoc", + "name": "uvdoc", + "ownedBy": "gitee-ai" + }, + { + "id": "hunyuandit-v1-2-diffusers-distilled", + "name": "hunyuandit-v1-2-diffusers-distilled", + "capabilities": ["function-call"], + "ownedBy": "tencent" + }, + { + "id": "rmbg-2-0", + "name": "rmbg-2-0", + "ownedBy": "gitee-ai" + }, + { + "id": "qwen2-vl", + "name": "qwen2-vl", + "capabilities": ["function-call", "image-recognition"], + "ownedBy": "alibaba" + }, + { + "id": "internvl2", + "name": "internvl2", + "capabilities": ["image-recognition"], + "ownedBy": "intern" + }, + { + "id": "stable-diffusion-3-5-large-turbo", + "name": "stable-diffusion-3-5-large-turbo", + "capabilities": ["image-generation"], + "ownedBy": "stability" + }, + { + "id": "stable-diffusion-v1-4", + "name": "stable-diffusion-v1-4", + "capabilities": ["image-generation"], + "ownedBy": "stability" + }, + { + "id": "flux-1-schnell", + "name": "flux-1-schnell", + "capabilities": ["image-generation"], + "ownedBy": "bfl" + }, + { + "id": "fish-speech-1-2-sft", + "name": "fish-speech-1-2-sft", + "ownedBy": "gitee-ai" + }, + { + "id": "yi-chat", + "name": "yi-chat", + "ownedBy": "01ai" + }, + { + "id": "funaudiollm-cosyvoice-300m", + "name": "funaudiollm-cosyvoice-300m", + "ownedBy": "gitee-ai" + }, + { + "id": "code-raccoon-v1", + "name": "code-raccoon-v1", + "ownedBy": "gitee-ai" + }, + { + "id": "codegeex4-all", + "name": "codegeex4-all", + "ownedBy": "gitee-ai" + }, + { + "id": "stable-diffusion-3", + "name": "stable-diffusion-3", + "capabilities": ["image-generation"], + "ownedBy": "stability" + }, + { + "id": "chattts", + "name": "chattts", + "ownedBy": "gitee-ai" + }, + { + "id": "bge-small-zh-v1-5", + "name": "bge-small-zh-v1-5", + "capabilities": ["embedding"], + "ownedBy": "baai" + }, + { + "id": "whisper-large", + "name": "whisper-large", + "capabilities": ["audio-transcript"], + "ownedBy": "openai" + }, + { + "id": "speecht5_tts", + "name": "speecht5_tts", + "ownedBy": "gitee-ai" + }, + { + "id": "whisper-base", + "name": "whisper-base", + "capabilities": ["audio-transcript"], + "ownedBy": "openai" + }, + { + "id": "stable-diffusion-xl-base-1-0", + "name": "stable-diffusion-xl-base-1-0", + "capabilities": ["image-generation"], + "ownedBy": "stability" + }, + { + "id": "qwen3-5", + "name": "Qwen3.5-27B", + "capabilities": ["reasoning"], + "contextWindow": 262144, + "maxOutputTokens": 65536, + "pricing": { + "input": { + "currency": "CNY", + "perMillionTokens": 1.8 + }, + "output": { + "currency": "CNY", + "perMillionTokens": 14.4 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 38912 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "qwen3-5-a10b", + "name": "Qwen3.5-122B-A10B", + "capabilities": ["reasoning"], + "contextWindow": 262144, + "maxOutputTokens": 65536, + "pricing": { + "input": { + "currency": "CNY", + "perMillionTokens": 2 + }, + "output": { + "currency": "CNY", + "perMillionTokens": 16 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 38912 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "qwen3-5-a3b", + "name": "Qwen3.5-35B-A3B", + "capabilities": ["reasoning"], + "contextWindow": 262144, + "maxOutputTokens": 65536, + "pricing": { + "input": { + "currency": "CNY", + "perMillionTokens": 1.6 + }, + "output": { + "currency": "CNY", + "perMillionTokens": 12.8 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 38912 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "gpt-5-4", + "name": "gpt-5-4", + "ownedBy": "openai" + }, + { + "id": "gpt-5-4-pro", + "name": "OpenAI: GPT-5.4 Pro", + "contextWindow": 1050000, + "maxOutputTokens": 128000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 30 + }, + "output": { + "currency": "USD", + "perMillionTokens": 180 + } + }, + "ownedBy": "openai" + }, + { + "id": "mercury-2", + "name": "Inception: Mercury 2", + "contextWindow": 128000, + "maxOutputTokens": 50000, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.25 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.75 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.024999999999999998 + } + }, + "ownedBy": "inception" + }, + { + "id": "gpt-5-3-chat", + "name": "OpenAI: GPT-5.3 Chat", + "contextWindow": 128000, + "maxOutputTokens": 16384, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 1.75 + }, + "output": { + "currency": "USD", + "perMillionTokens": 14 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.175 + } + }, + "ownedBy": "openai" + }, + { + "id": "gemini-3-1-flash-lite-preview", + "name": "Google: Gemini 3.1 Flash Lite Preview", + "contextWindow": 1048576, + "maxOutputTokens": 65536, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.25 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.5 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.024999999999999998 + } + }, + "ownedBy": "google" + }, + { + "id": "seed-2-0-mini", + "name": "ByteDance Seed: Seed-2.0-Mini", + "contextWindow": 262144, + "maxOutputTokens": 131072, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.09999999999999999 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.39999999999999997 + } + }, + "ownedBy": "bytedance" + }, + { + "id": "gemini-3-1-flash-image-preview", + "name": "Google: Nano Banana 2 (Gemini 3.1 Flash Image Preview)", + "contextWindow": 65536, + "maxOutputTokens": 65536, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.5 + }, + "output": { + "currency": "USD", + "perMillionTokens": 3 + } + }, + "ownedBy": "google" + }, + { + "id": "qwen3-5-flash-02-23", + "name": "Qwen: Qwen3.5-Flash", + "capabilities": ["reasoning"], + "contextWindow": 1000000, + "maxOutputTokens": 65536, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.09999999999999999 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.39999999999999997 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 38912 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "lfm-2-a2b", + "name": "LiquidAI: LFM2-24B-A2B", + "contextWindow": 32768, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.03 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.12 + } + }, + "ownedBy": "liquidai" + }, + { + "id": "aion-2-0", + "name": "AionLabs: Aion-2.0", + "contextWindow": 131072, + "maxOutputTokens": 32768, + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.7999999999999999 + }, + "output": { + "currency": "USD", + "perMillionTokens": 1.5999999999999999 + }, + "cacheRead": { + "currency": "USD", + "perMillionTokens": 0.19999999999999998 + } + }, + "ownedBy": "aion" + }, + { + "id": "gliner-pii", + "name": "gliner-pii", + "ownedBy": "nvidia" + }, + { + "id": "llama-nemotron-embed-v2", + "name": "llama-nemotron-embed-v2", + "ownedBy": "meta" + }, + { + "id": "nemotron-content-safety-reasoning", + "name": "nemotron-content-safety-reasoning", + "ownedBy": "nvidia" + }, + { + "id": "tiny-aya-global", + "name": "tiny-aya-global", + "ownedBy": "huggingface" + }, + { + "id": "tiny-aya-fire", + "name": "tiny-aya-fire", + "ownedBy": "huggingface" + }, + { + "id": "tiny-aya-water", + "name": "tiny-aya-water", + "ownedBy": "huggingface" + }, + { + "id": "tiny-aya-earth", + "name": "tiny-aya-earth", + "ownedBy": "huggingface" + }, + { + "id": "qwen3-5-flash", + "name": "Qwen 3.5 Flash", + "capabilities": ["reasoning"], + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.09999999999999999 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0.39999999999999997 + } + }, + "reasoning": { + "supportedEfforts": ["none", "low", "medium", "high"], + "thinkingTokenLimits": { + "min": 1024, + "max": 38912 + } + }, + "ownedBy": "alibaba" + }, + { + "id": "voyage-4", + "name": "voyage-4", + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.06 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "ownedBy": "voyage" + }, + { + "id": "voyage-4-large", + "name": "voyage-4-large", + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.12 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "ownedBy": "voyage" + }, + { + "id": "voyage-4-lite", + "name": "voyage-4-lite", + "pricing": { + "input": { + "currency": "USD", + "perMillionTokens": 0.02 + }, + "output": { + "currency": "USD", + "perMillionTokens": 0 + } + }, + "ownedBy": "voyage" + } + ] +} diff --git a/packages/provider-registry/data/models.pb b/packages/provider-registry/data/models.pb deleted file mode 100644 index 4a9bc11654ec5db486506a5a703810e9511f4d53..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 792407 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 z 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�#8Ez~Al=F6N2uvD51@0|R>kE?MP}uvu z9hOa-1gHc_U{-oXb#=OAs!>Xp3}h7R^!N6|COgbv)<#fz;627W4jwJ{_MDiUoI1t- z5S(|$!gN|GcGr@<9DK^C1`DST#&ec@)xO3Zrqx2^?+zJ?lOgW+XR#>LL!P@3&Y+z* zV3kVZ=i$_&&5QnKif~p)aHV%c@$2SSJp0s>b^|*p5n0L&mO?VI5X_4<IzVx#ktJ zBzDTZ#%EPDVit$Q;qw}8Y-!nlY(J+5}aUXd}b$xCFId}QO!Kt&zc$}cRi~& zD2NWZb6DT3bPRLlfBCrs554s6T;Z5Nf~hNE?c2(?>o>r~sq-ea+e%;jgPPz%iQqo_ z(pYJHoV5eNyGjnFnbe(`E|?;fL0;a+@2#V~*QMkq_civ}h?0FaPb0rN0#(5)79XFh;hnbznOxW~9E954eM8M^sv5cFu|h^cC;3ilqvC zcmsHJX#bJZ>q>+vNKXq7R5ZL{)+B91g*+$@YL~Ds;Y7ny zG`!D3j{+)(>VowR;KuC0GZ%6G;Pvqlpxy%k@M!aCK)C_RH|ymRI_sQkB9-30+o}nq z@WWtL7s5>?ckF>zAgrBdaPE+B#C63nfGsk2Q5qmtisxLmXhG}`_BP8lPx-8WrUqb9 z%G_l;AqaeybI0fXHih~Z?z|6Lot5)YJoA&Vvv(#t6Unz8Y`nJO6eO?p&68?yRf3QF z@_*9@x#EfBfr0uMX;Gne zCzTFT)YcHnj#oxtv$Z6sKBac*bV>p4t9IGXW|bT`f$YM7kg&D$E6v{TLWN^&UOK!{ z%pg_*q86iIGlQ*A>ED!rzLmlDw`z2CiJf9#-=+&9!TfnU$?pyErL%e1Nou@rg*bT? zHTzguv%h7F85=zF6BFq9ZEJLD%8y)LT85l!`g&R^ZAE(17h7H_JuFaN3^>S_d+7(f zNnxQxNS*icBmI3Jy0W;sA^ZM&YV7-x zfA=A&dyQ^1j&5gLq`oSQj=B?db?bJiT$7o6Zm4m`O7HqC)eB-UP40M-7tIbO#_L^Z zbF(Q@Ejj;~|C7O4Q~zgJ$7xu1>hGUjp8Zg7MHBKJ+nW&o!dPw`yE7pXpITAKA1)=o zzzEmzJ662jsU?e|b-k0k;ey_5UXX#2t5&*L$+A+|GPykH9l1N_(+j<76kEv+46~BE zM;x7aFv^i<-1o^EevHZF>-IDnHoMDT};)o<0I|y z6Czu1?gaSD=iT}`HT^&qXIE&U4?T;@fA}gHg_SklBg(IIkMMYp+Y0?eTQMhap zvGx(!pcpd5+p}sWzr%yg#=CM!_ILl$+=~yti`wHf|NidAyY%z=1W?D>C93-ux_rdm zY`kk@6>uY0^*c5?M^EgN31 zt$LE8wKdf~)dm>ZN&eFQ#*y@qEyI-@IUmo&x!L~Z?#L(8LtzDQY4zXW(NN^?Mc?P~ zh)h7^qW@;w+7Q%;1OK2{T?Cs>=tnr9U;K{ zClZME2)S#kdsF(?<@>SpTgUl?n5A*wEz#w0Dkx<2;Wa|iYm~x4qIJF3N6#4aO4(>M zg&28g4K{T~aeQ8SR3yQS)E^E0PY}F;g2v$T(+&&=oyEzd%1? zG~n~}U?LXjk#`@maTQ(JfDfeErLXZurnD^49inbg&}Vv#Tdt86czzx_rE8_I=a_?K`mxII*{vb$dB#!X`5`?hnjpS6>7A~oTm2ILXj{e#^!@YlMkBHD}zfN4HpD8 zA(?Bz$@A=fl7(U!KztyF+9#W24VGj>rtJ+$#!=bJnHRUeII{V(J$~v;ky6_m;^iAg zEv~Hcy<6F#zk$`Q(Kxd~xwRFV$cD@DnA@)}^+d|z znN<he*^E1Q66SFj}B| z7W}H|%s71TjmxleF{E>F?X5^dfsIm6NQDtnuon-KEA=|Paaon=Ldf`BMa7+x1@igR zB=DA+Q&iHAccy9)BsJCIMKLq+CHk}!Lmj=KA@4pY#|HhkcDFF@&T4K)a1m>OdCgu zS-rCY`}AcMY3AdG-r5%4@vrX7^wvajpUnbgkrS{_q zz*C`>TAJt12bqnTXi|a`VFEIX2{irWh5AXgPYJfxN|P}Jp}M$YY(o;ym}VM`pC*kX z&YO{w z5F+*sw_KeZ$DVU8Vhsco)n7 z&gZY!gt_X^r?2a-*?o#}R^rJ$AUM7I9pyiIxN-NAxj&PwWhLyf>yx+nrk!k^5AV2j z(U~ck7vFQh_snE7ez0s|@RhJ#EyN(W0_j5HZqnhbD2gDe<*-SO=)_HnIn!;dG%Tof z28wX*L>lL%WZmV;Hy78z$XO+U(I;n|upgzNGM#-)-I?7~amL0pYg*XhMZ1Kph!Nr_ zUc!3|8zL>M{=Cc+BW1+8*}_P9s(@m0$RvDGp{KfO#S_`Yb0TxcHm7{W(BkHeK{@8S zS7?shqg#mU+BDhe!w+0jtlQ+{%E@5De7v#Vi>UMwDMXKs5^^>Pih-E z8pN!Ld`oBB1LCHV*BkbNq=&0&fmBTG!)K@4Yxk&$^*SrNY$_i^qo=)%HSv*7r));0 zR@XJ)rc2iTiV6s*I+@XC&P*63NE&Pq22?N&wr7;2bH-w+b4oCeQYHrtUcxO{YWZd; zn4gLv>j~2Ier|2D8V>se_c3$O<$lI&Rcpa`l=X*PLi!6;a12Pu&(v5jrM~vk%)v{{ z#LDN*i1~wKv%I@(v$Qlb9upauA2(Y%Gn?BjGZbsQDt5~c9Bhmb*MgGTI2}OetaR56 zKu*in@#HNJ+ciA!DIvVL);wnDa`9=5A0PSL@ZbkR6C!hsbQXo-l0(Kx#48})^OSrW z?Rvv#hU5%aK!IVw^F1P2Vt`zeyaMYjs#pcfjFMZ}I=Cx)$R!C3HIzdKU=dJVzkWsH z&nC*ByD5%1Ik9fj1%I z;F{~n^&M?k4&_L9EC&nVpoiFAf8wM;jpK_>b?Hj^l?R3zQ9M3fyZ=vJsqSRCKp$UZC~B+JjB~ zx;b==$_QrU`7O_0LmlFf=??q|#J^5WXuhEWmLwMns6_Q%(_Pb+QQ8qA3Fe8=;l46n zF0*zMCXr>3L;D2py(dCKg!>EWaL?!u;Qq(}XF!OncZQwsyS{&j0$6%DtS-*K_?oXC zdfni$xZeKQ&^9}}rMDW3w}IZHbh>sd8m+Zk9z>v;Xq-Ep#j4H+@zPzvojc$R;+yBF zs>Mt-3ioFZHtvqk2O|tHwQC#6-4DCWc#mF5a*TW;FD3l#)P4xu*rC%Inh;~rP^eb3 zg%`5aal!g z`hew$F$cT zgj&#dj(_E`vBA?2cZ;c-HkJJZ?+aM{+*vMUhW%jQ?P!iH1atLp*oU?U3rG25L*HLx zzm&l{@%ZP@tlh8Xq`tXvS86;yY__9y;j~EpL=Ah0%}MoP^-HaASRbg2y~KJlpYdG_ zv%I5hvW(O(St+hEF+P2A2LI{Ru}{9^j>bJDA0 zGCA*&XBZz3Xg3Gs&grOwU%vHmJj12g%WELDU6R2HfD>WSkW^sjPd9P=%7!H7P{rp!oq+mWeOl9HIs82EMMRM(lc(5`4y44 zq%!4qV>h`wa=WBtpnbGPqLkV+4jcmCaHDlJ05$d*LSv#{p3oFv(_W*OgXnnS_C?30 z2{b<{TUd5GlqZ8+p#xJn36#aA)1=7^3BwX}YI3sOo;szeu1cTnmNfZzBWLeLk2!f= zkRPeNH`Tn}8TpZYPj+uK!=+q=Ds}C=g8=Q8C>1bKly@$@%9(E5b~)VHBT}4d>@zWtD#jw(tl-Yq+3l@Vo%3Vq*?m}29{xo*;%^rgF zs*?JO5qgS-LeegyIvpZPCJ-Mq%d{M)A_6sQHRsr*m(dzbGXJ0dUh~-ED2+F&B38jI zh|RG=SioHM0RF~gygX}~!0gdN^a^w7{w$dP>V>jCHuR5c{NJq&ws-<}YOn!0@S>){ zXw{Yua3=`Zx7Q2USJU=7QFevjG$1RHHNg`t3CYf>si`S3rBw^bSBNqAjy()5#_A=j zi+0vT0S{;KlRSa(=IZ+DCffPCYZBxo2JgXXGKI3Utpi2ROpR_PtU~LoO0tV^Dfwv{ zMW!=kk(=a5&<`G$ni%=Lp{OuIs0u-TIvSn%N)e=r)BFlsOZeH1Gvr=lg`B6MOn71p zzkFku(dK*Al-KOC7-2^&fV-Eumorw00E%lsB8wH3PMBR;>G6lh-()PN00;dBne(;g z^E>koZ30SCCeMa5utf^})I@7dxI6>TZW?0y*&4X{+X{74Z>ZQKw-I35e3O8;tV7Y- zr7uW;?RZ(L)K5jx?jkli1Fm*F`MQJY>36fYc={(tex>MT$;V2!5Sx!cwRRiXm8Au9 zuFT4Chbj73GJA-BE6E-N_lYL4ECtu;ONEZb{VDF_fXwk^j`$l|{f!lCW943mHnOSH zEUjY!v$Zb!nQ%rnM02asz%_c)0{Z+g)|feK8bbuP#}iTT+m4;2#qf554M$$y?=PE^2Kz$@ zR0O1hE>j=eC)vCpT%2Ns(;m@R2B&dE7D3Q3KM)Ed zKS1WKhXmp{sBO28F3yqw)LQ+Xer#+7)IR)}& zrH_4N+K_IF9vz1;e3kN|MZ+ZsDP>WwbaVkk6=g1KRn8hH_DxQbi~-T47<&_!CE%>y z<;8gqF=gS7fPqs}Jw?u90AJ(twO&F^1>jo2S_0{j$?TLCP+gJr`=of-LTZqRQ;!WL zfy;6mOP~)y4ub&c{)%Z!nMEbF&GA!Sbe$A(dQRf z)JyOSeGNQm`O|Jho%);q^!g8e$H#=NI*gUKhWcXv%wOpjUcCqywXV@YDH5hr8xQRm z&u)@{Lpp#-UFps(ZmdD{`dY(Lu$I@m3+$s6Ilaj+gx=jc0%Ub1t^$-#tEFf)Wd)bO zvQCm7NNantn0tw*aN8s&CWKy{>Zbf!6sNH$c&Da26SPO@B;SCU+ns{Ym_3bg5i>o> zeYrP*p~v@+4jqWwq1x9^n$2xmplVovIo4T zSM5Jjf9q4c2!)9(|JT+yS5t-M|5ETG08RqJuwp%!#W|+DY>@>KQCn70O-zcV)~>0L zk5Zr!0y%r0-yCtYZHl1Zf>wU+a+h>;YjJ)lIdJB2py>~vE1WW`0Nm~o8=jk`VY9?; z%>gUXew;ad`s`RXf}PH!qx=U$VpMl-Do>NYu&42Q{j7xxLoHPcsLt|iC#k!Tn-%&g zyy5od<&!n@QtD`#&P&ArKW~%tjcP)pIe`~xkzT-vtmJi%hc`|kl)^!B=y00{ALN=50L5Zq&FPBf=c!CKFpoDnDf3T$-D@U||Vs zRhXMNmfM&dj1+@DmJby(ARg?^CI=po#s_b7R|0p;-S4%(Cpy$h*)}1ZdU#J%-x<-; zb|xfEetyp$2UPSAP&_#|J2$KC^PM%@r$k_)v5olvCpvStH#e1pvOYIfELf-4KhtQu z-hX-)onf99s;vdi;AHZqFj3=YTBp(B(U`Q0BQNjUGDWqL!40e^Z;5C z4p~hFGyf$ zw^anvhw>qFukwRYA}i;Nr40w7SEfK*UOMLjZ|2y~F0Bdlo$^djt0GfQe5^Smh)D`Y z5MwR)RP67|hE;VR+9B{{`~YmoF(YyMK787k@66^fXO(G)=_;yhkd)`##saZ(W(Mia z)OcnzE22rUebJ+m8@deL5wu94*GmaH_x@s_FAoW331Xp5&Rey3W-Q=Nhdk*1g*NUk z8A5BV3}&8+N}2rczdJy!kPPZ04I$yM4q8D#{Og*C8yhKBuAW^;n)+T85aV;Lfs^X(Aj>jMuJQ=vSi%*|xas3>U2E<3#pdEZf&z?!cV`m?G6t0~U&G!LIMXmU! z%F;JqB&`xC&1ig*G}_101=bwNHvNpEi~{HK=mnpYax<9W&|xma0gX^9YYNGD>vScb zi!A%7dCPBeaaJL`zWug^SA1aG?lr=z#!JNvyj}V9&LE#AO7iIsj{GUkOG*AfraygZ zdTRUf=P&MQB+?umN-!`E06>|i^m+*a@@Fk=<$epwm|z8$;upR*w&O8X?2TIJm|BX zNy~yhf2YPvm?;(c5BZPktX`Xm$c^vv-ASJXF@KfZDdStL1M81)GmoKAb4W4zv` z`w8PUK;_GU_1Z?Yv(vY!Os?kEjI)7^x_6=5T~$MvR-YjBRAORvJ zU#$+lb4gj2T^wXzAI_n^Q;@L}xPZa;agY!>S?qmHNk zCbn!Ew99gCAN(?fb^n3y@NfDL|HGG0{gHp6O?{-X|KMxxzIAI~sCz2;;*dhtaidoR z>q^6GyrI#Uy-aSIvbJ77Y2Xtm0@#{gsY}GM*v1E+-vsJV%KMcaO7(v7`$LT*HaUv_ z*btHB+h2(s=f5E4fgB#G?->46m8lkEdFhPZWW zkL85k@zMK!`qTeKEjbbXKf-+c##f${mK;0^b$sw8qAc5tg~5EDX5t^i9cn+WxLtC{ zCg2=@=2!o(zdRhx17Gp)_2tiGLdApsYR&SOx{{{9eVfl)vHQ&msOAWbmv%kN`b(G< zdK5W`CR3sXpS)31Dxb_m%SkdNXM=H{ZLM276*1`A->jM6Qa-IPsFS2b;=pcmzN=4~ z^@z?}5J3W;+I<=}+FHDExd$J_mEL;m8qEt^*GOH4DNWvZK3r-(!KLOCovPW+q*+0t ze`)*RU;Co>%ZR%=tQ+?a#@~@`^C!1A-|;I0L)&D&>-KQ1B`btU^4mj=*Ha6A`Q{>z zuZ{!=Gts+2+@HMuf?eR@bpaArU1(mMi1t2N+=C{_-Z*^mNb7t5`hYm!b91quQ@&z) zuIm+Fg0$8QX;;#P zW}A3-!TAGabt~$w%)4XB|L2-L|Biwg^XB?dwBWq1x9Dzf-~USlK&V#%!z$)2x?epM zaGzzC3tCTA7UjNrh5mIRsFGZ+eS!w(DQ5w{-jn_<@|&b!S}FLs?mVI4D#IxjC1}?J zAKE9ApV-rQLl{=ii_)haQei9Q@a^QTPyzIm0I`&RZ#}E(6r8%or+7i^$(`n((!%2`g=9gSbfX~$NI`= z5&T;v)RZaJCw*6mQg(LM2ZsL&=hXsY z)$_`wEocssf0Lc{IIxz>(D759Q-iu`8&Y&v=XPgjzv2;I`Rvy1T4tB-4V8te;ZlWu zYWuE*cp=@jK|i%CckQcH?X|N#8$>T6|FNZ$?75GbuYb{#QP;R8LphQ^HWzK)J)EyfnyY*nG!A zJXDqjP_R|**;mg}-I+9~c!XCtyQYn5Wo@S>*^D&MrR38jOp69z<^+_C)j=0Pp}b!{ zkmKdqrm?ymUY@^IV;PkEcTQ{kNhztSM2{y4D+NKgl>FG9#(jlBWhOz9nyPY} zzFMNS&YgN|!3C`u_;x4rb`1Y5jRDG@?wFfhoWHUGlg@G{xhwag1vvR=F6Tn}H3Ve5 z@$BDl8JfMgdN6*wpZ=w&)L*XIY$eYqet9hZ!GJV|2GQ7@v#FM*v;!tfTjySGl1QY% zVSI1piWkZ>72MYl5CZ0Zm*4@D>;C>j2z>y5YLsA-D%5{xRfcb5kAcE+q4`*>p?uhDDD4-l>he6`AS(;yE}(^DU#P{E zmMY|_cFKyKh|75zK5IsdD$QO*mid7a$ltE1cIp(4$y=*x_X>-l&TKp16V);YPp_`l zB3_ydIIpFs$>FE% z)l&~Y)4F(WZJ^IX(XCI|a zJfcES+T!oPJHIkJXOt4o3`n{5;A5*=fd}v!#+0do{%PsmBt255RSFoxoLL>W9zuOv z!;RH*!fC1ndj_@7@2k=P$O2&7S^hHdVNCHAW!ZeE&$=KVAg<+iLOV&xq751vGnE=G zmF~15(=YeCSEwu(J~h{2Qn=h3i_o&&R1G$j>9FX-qMfpn_~TfCvdSj1_ls+rkv2j^ zvfgd=E`d7kwqUGn9((qw3#X6gL9N0i8&UQuc>k6&*vEa8?n#?PUx4tD-0m}&Go9w%v>*6sfIUFyHvZvY8aJ3KlYXj(p6Gc6@#$h8}@ zi>r%Ez4fv5H|y0>bsFJRO?fCXMwgP0Lk*PPT=qzE=MzW;L-NWH8TsFa%MU3D`2-{5 zeMqm2=qlZTu|S6T$pkmYN#ZTN4;4?am?DwWu8IZGu41aveGH*)aQuuSn)zI686^?t zCUDm0sDI#hKw#20L}Dr>0XjJ`I{AQ+?m^-qdJx2m#wG?=gV(40ikT=8|IP(I<3d$E zb>~F4T`VnARY400mGOJuj2$XS%&E43!=>Vg9_11-Pb)o@t6&Jnckx3;`xB&VK``b? zC>giQPMTe*qCkY&m#wY5MF<{h&S)V_M?{qE zv1cE-MwQw)}8jz z+7<$9JJm^pto$`oqpJR*NjKd&ISE^9C;88Sqtc|Nuudfk?rKPR;<@%+q5Qw%$wRO)Vs!k^Vak!DhpVHRBD{5Wm!5;}ZW(h^! zYhLU$r%$%ejC}hZy@7>dEpBmhVY7#~r=ibV-Z}fcS(f@zu7=fw3TKgOQcNnjWN_uo zb>Xz3G*mCtX^ys}sv$CSOS%5Qn=?xN&^1{`nLjAf{bl5;|8~oNyLFLVH!dM;6|(0T zhFVWxe$Qc)nD}$ihOvv#Sxdi;46x!->apm^yV875V5WWyt|YUs^>u!`KtG7^#x|!V zkXv{;bkEC&9=i4NAu}Ed{S`eMI!uVPxG#ROW?z&_8RKGQJ90IyT8qiv#F4AmAgINd zmdoRK)GQ}n+I7&>-y`A_A0kDp3%)RsyfLn13RxGtLs5Y|azO>=1fE7xIs(a3q-;h< z5P)eJssS71hiayGyx=PzjU&sZH-hSIh_~Y@Gn19kIlW(>{IE-9;hKDQsBupmq9)H# zFt5wc+c5*e96ywwvd`{)=+=j~zZL6HPo->e97tiVY(zVfQp^L;`^qR~xRm_RzQ*DB zYb_*jayUM53x|8;Yxd?}x=UN_y*h)a3$OT{=IOI?^5DghJ;1@2se`6B_GN=lN3YSm z*<<{oGbd64A4;C(-lpF&69#TB96r^=XSNiQDD~s5_Y#e!DTtRrwgmEwVn)1acMYvu z8GmC&QS{H<%{oCCnhhnP7rT?CaRdY-k|2;ur(TphjNAwzZUHfgQ<-NyRXkFtWzE&} zE9U3UGKlB}pP%htTR?E;DFFgmv+f%DV_*DExGsE*`dRG<&okQ^%IA!KS zsC{4z5#$f%99F(MG0x+2QQn>P$RTKhF$8&5YMw;)tF*b57%cQP7ctNKV`gNBSKOwc z%D79LnlZnO`(bPrZkEh0E?c`Ji&XeI3qNaI^z_4EXl9c=%UNFOHLpY18fwzhwmZ3@ zYqAD7Fc{`zm@Y;VL8uCc3eQRYoN@WZXne#hM?nN#-uyZRJT;ioKOgQ?1GM_^3k;3!H*eLocoe5}VU`@U8`J${zL zrntza$Qh%!D8XdPCLWV1dr?}2wdzGEC78k@oMRErwETw|%V=fMna_45)FWIPIUXZRw+xpR5K} zMmDEibA0SlLaE@Pe*5}l3$}`Ga?i8ZZW5(Z(dos>);Zofx;Iakb?a_Is>5cA`sN$p z%es1C&2Ld!xuxpI`{IIJxF>vfEFDpmP(ahe$XEGP;ZpKv_BZanL1?PKu(S+I+RTfc z&RlZ$`@%b9E%nZsk&o~7wQZ3%dKx7#q zec?AmU7R&mA(?%l>cXOE1kp^NJ}+`Acs_kv=ws>M8w>QG6ab50MMR)BlgGK?g&t;g z+Tg9^n+I3?MDzK1@*mgA?cTyNj9w<|wJ|rk*c*$>&>7Rc?_ch9L)F9Y|Nh>GUV67} zIO-ZHZdBiHL|^uzw9o!rjRGp2BbuA~8jsy5ZIh}UXZY7@vol_52D9@GrMTI7R*8vw zRf1%jJVa6Pv?n^9sc96?cMUi0SiT9%`||9IttIdc=fjKUxlXe^ab{%WDc=$DYP6Zv zpeHb>$W+tDv>5s-fH<-=X)G2xYV0z%JnT-vq@juRm3)ZB9hzwUjW+K$o9<|qf}rl> zjkerbJfIL9C@uGo*DQAlYI3=MbM;DhEcfW38|Lbly3(EXrJic++~Vl$!t#G<+2Xjb z=AjBpKRq*JD?h${-jwg7*D(Zei(R1#$&HI|D|vI4?!9;pi>yeAH}a2$PiL9uTsE+R z^v;?Hn|x_{Z)EK&Zb$xS8uht_^S(dcjkz8Tz zQV$@|>xtAC5^}{-3NZ;GXMK@2B-iC2u+hc+)RQv+wxfBz_h{x^yG6}#DQaH7*;*v& zX(JKkIRhVUo-rPIv%8gF;JsGr)AJjb`XnlYqb?WS4i(v4ONNN|4dLJ!5R>_lc}gxt z%;6}d+b3ng(TUyKi;U;giCtM){NM(k$g(HoMNTvydHN|=CeJK^^1`3B-o>I)P zh77!)bE804+jZurJ<)z5bt0BXTT!p2;XZTj;xjo0C_SE~X%_iI;&rWM6DLIhF4^z9 z5IjK(h2%3tqk^bmYc$@zcWBRWXJ}{!cvWg`{MNBQ{HOlWZ+^ywfA*rk4{+4NhO(5u zS0f491qx*|- zKxo^Xaob6}9IK&)yIbL+{O>ge(=YY2RB~yirXb^(nV3%g45`^nYDi?{Sip%9(r4;V|p6I95GW1Pj~ZWZN)1$WyOVUZJ5MLD6(?XdydrcrEc zA}3t0Z6YV0^+qh=aPiauJz{6eWLoKjVL)HQ5V5pGNM#-l zOVTFz`HDO0pkxNY324KZ2k}5ogf(Vs@g;chr0Ie<*2O{sfs_qsxnjqaRm5$W> z5CNAUQf-_b!_j0daRUTfLAEHtp7yLESP_dxU;5;MXe?$ZOsofg@y`(+SNGW-&54bl zsSz8cd|vrLu%jh2>f>y+soHS^T;`cCLcV6Y9{gaJDy7m}f*854WOi;xKx`*7d#r4A zynb*dOnO$xg-PIc$@lJY){co*Z+T_WYzgPpZ9c{8rZ{qu3<82#r-q{kObP63ynT3R z@9-X1127o`9FJ$;0z;Nti2spgO$!vhC(sa=xed?{bqwG58xcBF;u^I9Pm~BpcSAX- zYNjTJZI?7@ft5H;T2?JzHG47Acl3ZX=)V1$z2UukI(vrpqD8lyzKYrUo|=gMHfJk( znL9oovazeoa#BfKP1?8I(YVLzsKw_H2aw#W9ApzNhi0C}VdN(c=zV+JLe(<2WZ9`7 zso{O(&e&HSi≶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 diff --git a/packages/provider-registry/data/providers.json b/packages/provider-registry/data/providers.json new file mode 100644 index 00000000000..7b0df9964ca --- /dev/null +++ b/packages/provider-registry/data/providers.json @@ -0,0 +1,391 @@ +{ + "version": "2026.03.09", + "providers": [ + { + "id": "cherryin", + "name": "CherryIN", + "description": "CherryIN - AI model provider", + "defaultChatEndpoint": "openai-chat-completions" + }, + { + "id": "silicon", + "name": "Silicon", + "description": "Silicon - AI model provider", + "defaultChatEndpoint": "openai-chat-completions" + }, + { + "id": "aihubmix", + "name": "AiHubMix", + "description": "AiHubMix - AI model provider", + "defaultChatEndpoint": "openai-chat-completions" + }, + { + "id": "ovms", + "name": "OpenVINO Model Server", + "description": "OpenVINO Model Server - AI model provider" + }, + { + "id": "ocoolai", + "name": "ocoolAI", + "description": "ocoolAI - AI model provider", + "defaultChatEndpoint": "openai-chat-completions" + }, + { + "id": "zhipu", + "name": "ZhiPu", + "description": "ZhiPu - AI model provider", + "defaultChatEndpoint": "openai-chat-completions" + }, + { + "id": "deepseek", + "name": "deepseek", + "description": "deepseek - AI model provider", + "defaultChatEndpoint": "openai-chat-completions", + "apiFeatures": { + "arrayContent": false + } + }, + { + "id": "alayanew", + "name": "AlayaNew", + "description": "AlayaNew - AI model provider", + "defaultChatEndpoint": "openai-chat-completions" + }, + { + "id": "dmxapi", + "name": "DMXAPI", + "description": "DMXAPI - AI model provider", + "defaultChatEndpoint": "openai-chat-completions" + }, + { + "id": "aionly", + "name": "AIOnly", + "description": "AIOnly - AI model provider", + "defaultChatEndpoint": "openai-chat-completions" + }, + { + "id": "burncloud", + "name": "BurnCloud", + "description": "BurnCloud - AI model provider", + "defaultChatEndpoint": "openai-chat-completions" + }, + { + "id": "tokenflux", + "name": "TokenFlux", + "description": "TokenFlux - AI model provider", + "defaultChatEndpoint": "openai-chat-completions" + }, + { + "id": "302ai", + "name": "302.AI", + "description": "302.AI - AI model provider", + "defaultChatEndpoint": "openai-chat-completions" + }, + { + "id": "cephalon", + "name": "Cephalon", + "description": "Cephalon - AI model provider", + "defaultChatEndpoint": "openai-chat-completions", + "apiFeatures": { + "arrayContent": false + } + }, + { + "id": "lanyun", + "name": "LANYUN", + "description": "LANYUN - AI model provider", + "defaultChatEndpoint": "openai-chat-completions" + }, + { + "id": "ph8", + "name": "PH8", + "description": "PH8 - AI model provider", + "defaultChatEndpoint": "openai-chat-completions" + }, + { + "id": "sophnet", + "name": "SophNet", + "description": "SophNet - AI model provider", + "defaultChatEndpoint": "openai-chat-completions" + }, + { + "id": "ppio", + "name": "PPIO", + "description": "PPIO - AI model provider", + "defaultChatEndpoint": "openai-chat-completions" + }, + { + "id": "qiniu", + "name": "Qiniu", + "description": "Qiniu - AI model provider", + "defaultChatEndpoint": "openai-chat-completions", + "apiFeatures": { + "developerRole": false + } + }, + { + "id": "openrouter", + "name": "OpenRouter", + "description": "OpenRouter - AI model provider", + "defaultChatEndpoint": "openai-chat-completions" + }, + { + "id": "ollama", + "name": "Ollama", + "description": "Ollama - AI model provider" + }, + { + "id": "new-api", + "name": "New API", + "description": "New API - AI model provider" + }, + { + "id": "lmstudio", + "name": "LM Studio", + "description": "LM Studio - AI model provider" + }, + { + "id": "anthropic", + "name": "Anthropic", + "description": "Anthropic - AI model provider", + "defaultChatEndpoint": "anthropic-messages" + }, + { + "id": "openai", + "name": "OpenAI", + "description": "OpenAI - AI model provider", + "defaultChatEndpoint": "openai-responses", + "apiFeatures": { + "serviceTier": true + } + }, + { + "id": "azure-openai", + "name": "Azure OpenAI", + "description": "Azure OpenAI - AI model provider" + }, + { + "id": "gemini", + "name": "Gemini", + "description": "Gemini - AI model provider", + "defaultChatEndpoint": "google-generate-content" + }, + { + "id": "vertexai", + "name": "VertexAI", + "description": "VertexAI - AI model provider" + }, + { + "id": "github", + "name": "Github Models", + "description": "Github Models - AI model provider", + "defaultChatEndpoint": "openai-chat-completions" + }, + { + "id": "copilot", + "name": "Github Copilot", + "description": "Github Copilot - AI model provider", + "defaultChatEndpoint": "openai-chat-completions" + }, + { + "id": "yi", + "name": "Yi", + "description": "Yi - AI model provider", + "defaultChatEndpoint": "openai-chat-completions" + }, + { + "id": "moonshot", + "name": "Moonshot AI", + "description": "Moonshot AI - AI model provider", + "defaultChatEndpoint": "openai-chat-completions" + }, + { + "id": "baichuan", + "name": "BAICHUAN AI", + "description": "BAICHUAN AI - AI model provider", + "defaultChatEndpoint": "openai-chat-completions", + "apiFeatures": { + "arrayContent": false + } + }, + { + "id": "dashscope", + "name": "Bailian", + "description": "Bailian - AI model provider", + "defaultChatEndpoint": "openai-chat-completions" + }, + { + "id": "stepfun", + "name": "StepFun", + "description": "StepFun - AI model provider", + "defaultChatEndpoint": "openai-chat-completions" + }, + { + "id": "doubao", + "name": "doubao", + "description": "doubao - AI model provider", + "defaultChatEndpoint": "openai-chat-completions" + }, + { + "id": "infini", + "name": "Infini", + "description": "Infini - AI model provider", + "defaultChatEndpoint": "openai-chat-completions" + }, + { + "id": "minimax", + "name": "MiniMax", + "description": "MiniMax - AI model provider", + "defaultChatEndpoint": "openai-chat-completions", + "apiFeatures": { + "arrayContent": false + } + }, + { + "id": "groq", + "name": "Groq", + "description": "Groq - AI model provider", + "defaultChatEndpoint": "openai-chat-completions" + }, + { + "id": "together", + "name": "Together", + "description": "Together - AI model provider", + "defaultChatEndpoint": "openai-chat-completions" + }, + { + "id": "fireworks", + "name": "Fireworks", + "description": "Fireworks - AI model provider", + "defaultChatEndpoint": "openai-chat-completions" + }, + { + "id": "nvidia", + "name": "nvidia", + "description": "nvidia - AI model provider", + "defaultChatEndpoint": "openai-chat-completions" + }, + { + "id": "grok", + "name": "Grok", + "description": "Grok - AI model provider", + "defaultChatEndpoint": "openai-chat-completions" + }, + { + "id": "hyperbolic", + "name": "Hyperbolic", + "description": "Hyperbolic - AI model provider", + "defaultChatEndpoint": "openai-chat-completions" + }, + { + "id": "mistral", + "name": "Mistral", + "description": "Mistral - AI model provider", + "defaultChatEndpoint": "openai-chat-completions", + "apiFeatures": { + "streamOptions": false + } + }, + { + "id": "jina", + "name": "Jina", + "description": "Jina - AI model provider", + "defaultChatEndpoint": "openai-chat-completions" + }, + { + "id": "perplexity", + "name": "Perplexity", + "description": "Perplexity - AI model provider", + "defaultChatEndpoint": "openai-chat-completions" + }, + { + "id": "modelscope", + "name": "ModelScope", + "description": "ModelScope - AI model provider", + "defaultChatEndpoint": "openai-chat-completions" + }, + { + "id": "xirang", + "name": "Xirang", + "description": "Xirang - AI model provider", + "defaultChatEndpoint": "openai-chat-completions", + "apiFeatures": { + "arrayContent": false + } + }, + { + "id": "hunyuan", + "name": "hunyuan", + "description": "hunyuan - AI model provider", + "defaultChatEndpoint": "openai-chat-completions" + }, + { + "id": "tencent-cloud-ti", + "name": "Tencent Cloud TI", + "description": "Tencent Cloud TI - AI model provider", + "defaultChatEndpoint": "openai-chat-completions" + }, + { + "id": "baidu-cloud", + "name": "Baidu Cloud", + "description": "Baidu Cloud - AI model provider", + "defaultChatEndpoint": "openai-chat-completions" + }, + { + "id": "gpustack", + "name": "GPUStack", + "description": "GPUStack - AI model provider" + }, + { + "id": "voyageai", + "name": "VoyageAI", + "description": "VoyageAI - AI model provider", + "defaultChatEndpoint": "openai-chat-completions" + }, + { + "id": "aws-bedrock", + "name": "AWS Bedrock", + "description": "AWS Bedrock - AI model provider" + }, + { + "id": "poe", + "name": "Poe", + "description": "Poe - AI model provider", + "defaultChatEndpoint": "openai-chat-completions", + "apiFeatures": { + "arrayContent": false, + "developerRole": false + } + }, + { + "id": "longcat", + "name": "LongCat", + "description": "LongCat - AI model provider", + "defaultChatEndpoint": "openai-chat-completions" + }, + { + "id": "huggingface", + "name": "Hugging Face", + "description": "Hugging Face - AI model provider", + "defaultChatEndpoint": "openai-responses" + }, + { + "id": "gateway", + "name": "Vercel AI Gateway", + "description": "Vercel AI Gateway - AI model provider", + "defaultChatEndpoint": "openai-chat-completions" + }, + { + "id": "cerebras", + "name": "Cerebras AI", + "description": "Cerebras AI - AI model provider", + "defaultChatEndpoint": "openai-chat-completions" + }, + { + "id": "mimo", + "name": "Xiaomi MiMo", + "description": "Xiaomi MiMo - AI model provider", + "defaultChatEndpoint": "openai-chat-completions" + } + ] +} diff --git a/packages/provider-registry/data/providers.pb b/packages/provider-registry/data/providers.pb deleted file mode 100644 index b312ae0d0290148a56cc40ef99d51daccbc41fa0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8539 zcmbVRTW=gm6}Fwpcw7#vQ0yj7`8Jt2iIdIr#11TpwF|Nhi5I?aoG};99|eS%4+#?D5+I65h}pJ`aQdys&BQbgO&TKrBQQ33d)(-;ITZvyGbDD$M~VIRb!1wnWXLp>0g?k6F&`wL1L0 z1Dx0=yZZ;lJ$os!PdC{0F@w~$_`fFE#Wzi>-Q^d*)3gq|;+k$q$*YhYpQtw4s@2ez279Q$L@I65 z>{NPRF5QLFZ$&UjPSf$5p4+x+fLvzppUE4pnlb99Q#g}wHdfepUcwVK=+Bjn+vch1$UbabQTwtF8xJWG z^mTw&`$jNV zT?*HrI(i7_e4QbDtDZ`8xF-%K`95lVPQpVU*dY3t4_rl^q0X*>0LPVqjrAq!6e>io{a^p7?f( zW*gclD&3Cs$~Ecu(u?J-PO3{GaliirzGa)vY1iTTXPf)4UhE4Iw4}Uc$eWM$)53LH zR=N%rP^kene4E`ogCRzejQ*dp3+?7Ze&KNEp}2}4QZ$F?ccPWS4ugd>!QzWD_>UCu zCzW2f?IsW+y#TQ6y-n^oHiBHSv3RPbX^M%Qdeqv5&@TSAu|y}yeakJwiuYdx18+pS99B+Rxn!9QdLR589B zK<$N3GzG;kL==TKu}xem`G)1AZsT8gqT8T2>WJ&9F0~AjkVLW))UHObnDR~BzS5IU zP2$c?NI;n!J6cC2|2Gj2Qh_rum@Jz{Yt$qgQ8Dq~0&mr{YR#_c@Y$`+-R+$hoBJAX zqC?`%C@@;5Y+8W=bqzP7#1SS+i8_$}7q@2J>@+*D1j(njOuVa@j+hS#646&+>T8k1 z#*+=xwvZy$v{P4bb++O4Ra=&okwboI4Z1C#GtF$bJ1ISHu!rYN$x6zY>C>sL3I1c1 z-}$nAj5K!o2*l$q_8Ai7J~NgQP7lp?4L%t zW{0L(QQ1Fm-D7v7LmbC7R%JiOv8l+@G6#Hm55%515c45HqRKc-I9+>-GQ>>1m|}>- zf2I!lKFc*c_chNw!LOK8zi$UX%~D1)%)3Xtb%cRZ21gZE@ZE;=@jS~5&qGQqL=*`n zUCQJ3jaD!Qr*CjF$x?n~lmvkgIdg1zvhR8)9X|6oBE@1%k~mV)QC;%!BBRmKVD}p5{c=5aKu5X zsG~I7Q&09siWW;lnv`-EyNsW)A*GB9I>%Z96s|h3P}w(l{-th(2wHLzDLd_39c1X`!J1L?k#7Kda8W0})ns`nC>-Hm*k?*QbfLAOh zIwZ~;z!^WXzeX~8Y&wQsu3D2iiuTrFfvYSw>oH836i1bZjdr)=n>BoAd2;yTNKs-T zq9`~CFZ?cwOZOB7tS&A!rHjjuqGp#e@i$r_Fpd1-^;}^B?_MK!;E*G$R+XaRLArtB zl}|qdv2EhzG$cp>sWVo-5l+V^WYZ6`q->g&VoIcGiS7RZrq}E?8px23&6?zwc7jgv zF?yny?2*7$@D3AawBgfQVm&AcR==7{*6`!FvU>Up^wdC((Kk=|7x5-h!{>B-A$=13 zC2nT^A|a+zDXSc=X!P`gMyIL#QfCm!uN`CgP^lr&ZzOd13nZE~#E7bAcKGaeNaG4zrI+6m7a1zD-{{X8X9RC0S diff --git a/packages/provider-registry/package.json b/packages/provider-registry/package.json index d5496706ee1..726cf80e763 100644 --- a/packages/provider-registry/package.json +++ b/packages/provider-registry/package.json @@ -7,11 +7,8 @@ "types": "dist/index.d.ts", "packageManager": "pnpm@10.27.0", "scripts": { - "proto:generate": "cd proto && buf generate .", - "proto:lint": "cd proto && buf lint .", - "proto:breaking": "cd proto && buf breaking . --against '../../../.git#subdir=packages/provider-registry/proto,branch=main'", - "build": "pnpm proto:generate && tsdown", - "dev": "pnpm proto:generate && tsc -w", + "build": "tsdown", + "dev": "tsc -w", "clean": "rm -rf dist", "test": "vitest run", "test:watch": "vitest", @@ -22,7 +19,6 @@ "sync:all": "tsx scripts/generate-providers.ts", "generate:provider-models": "tsx scripts/generate-provider-models.ts", "populate:reasoning": "tsx scripts/populate-reasoning-data.ts", - "migrate:json-to-pb": "tsx scripts/migrate-json-to-pb.ts", "pipeline": "pnpm import:all && pnpm sync:all && pnpm generate:provider-models && pnpm populate:reasoning" }, "author": "Cherry Studio", @@ -48,8 +44,6 @@ } }, "devDependencies": { - "@bufbuild/buf": "^1.66.0", - "@bufbuild/protoc-gen-es": "^2.11.0", "@types/json-schema": "^7.0.15", "@types/node": "^24.10.2", "dotenv": "^17.2.3", @@ -60,7 +54,6 @@ }, "peerDependencies": {}, "dependencies": { - "@bufbuild/protobuf": "^2.11.0", "class-variance-authority": "^0.7.1", "json-schema": "^0.4.0", "lucide-react": "^0.563.0" diff --git a/packages/provider-registry/proto/buf.gen.yaml b/packages/provider-registry/proto/buf.gen.yaml deleted file mode 100644 index 2dbb4bfe635..00000000000 --- a/packages/provider-registry/proto/buf.gen.yaml +++ /dev/null @@ -1,7 +0,0 @@ -version: v2 -inputs: - - directory: . -plugins: - - local: protoc-gen-es - opt: target=ts - out: ../src/gen diff --git a/packages/provider-registry/proto/buf.yaml b/packages/provider-registry/proto/buf.yaml deleted file mode 100644 index 63a38e8ebc4..00000000000 --- a/packages/provider-registry/proto/buf.yaml +++ /dev/null @@ -1,12 +0,0 @@ -version: v2 -modules: - - path: . - name: buf.build/cherrystudio/registry -lint: - use: - - STANDARD - except: - - PACKAGE_DIRECTORY_MATCH -breaking: - use: - - FILE diff --git a/packages/provider-registry/proto/v1/common.proto b/packages/provider-registry/proto/v1/common.proto deleted file mode 100644 index 5540d5eb30b..00000000000 --- a/packages/provider-registry/proto/v1/common.proto +++ /dev/null @@ -1,111 +0,0 @@ -syntax = "proto3"; -package registry.v1; - -// ═══════════════════════════════════════════════════════════════════════════════ -// Enums -// ═══════════════════════════════════════════════════════════════════════════════ - -enum EndpointType { - ENDPOINT_TYPE_UNSPECIFIED = 0; - ENDPOINT_TYPE_OPENAI_CHAT_COMPLETIONS = 1; - ENDPOINT_TYPE_OPENAI_TEXT_COMPLETIONS = 2; - ENDPOINT_TYPE_ANTHROPIC_MESSAGES = 3; - ENDPOINT_TYPE_OPENAI_RESPONSES = 4; - ENDPOINT_TYPE_GOOGLE_GENERATE_CONTENT = 5; - ENDPOINT_TYPE_OLLAMA_CHAT = 6; - ENDPOINT_TYPE_OLLAMA_GENERATE = 7; - ENDPOINT_TYPE_OPENAI_EMBEDDINGS = 8; - ENDPOINT_TYPE_JINA_RERANK = 9; - ENDPOINT_TYPE_OPENAI_IMAGE_GENERATION = 10; - ENDPOINT_TYPE_OPENAI_IMAGE_EDIT = 11; - ENDPOINT_TYPE_OPENAI_AUDIO_TRANSCRIPTION = 12; - ENDPOINT_TYPE_OPENAI_AUDIO_TRANSLATION = 13; - ENDPOINT_TYPE_OPENAI_TEXT_TO_SPEECH = 14; - ENDPOINT_TYPE_OPENAI_VIDEO_GENERATION = 15; -} - -enum ModelCapability { - MODEL_CAPABILITY_UNSPECIFIED = 0; - MODEL_CAPABILITY_FUNCTION_CALL = 1; - MODEL_CAPABILITY_REASONING = 2; - MODEL_CAPABILITY_IMAGE_RECOGNITION = 3; - MODEL_CAPABILITY_IMAGE_GENERATION = 4; - MODEL_CAPABILITY_AUDIO_RECOGNITION = 5; - MODEL_CAPABILITY_AUDIO_GENERATION = 6; - MODEL_CAPABILITY_EMBEDDING = 7; - MODEL_CAPABILITY_RERANK = 8; - MODEL_CAPABILITY_AUDIO_TRANSCRIPT = 9; - MODEL_CAPABILITY_VIDEO_RECOGNITION = 10; - MODEL_CAPABILITY_VIDEO_GENERATION = 11; - MODEL_CAPABILITY_STRUCTURED_OUTPUT = 12; - MODEL_CAPABILITY_FILE_INPUT = 13; - MODEL_CAPABILITY_WEB_SEARCH = 14; - MODEL_CAPABILITY_CODE_EXECUTION = 15; - MODEL_CAPABILITY_FILE_SEARCH = 16; - MODEL_CAPABILITY_COMPUTER_USE = 17; -} - -enum Modality { - MODALITY_UNSPECIFIED = 0; - MODALITY_TEXT = 1; - MODALITY_IMAGE = 2; - MODALITY_AUDIO = 3; - MODALITY_VIDEO = 4; - MODALITY_VECTOR = 5; -} - -enum Currency { - CURRENCY_UNSPECIFIED = 0; // defaults to USD - CURRENCY_USD = 1; - CURRENCY_CNY = 2; -} - -// Shared reasoning effort levels — superset of all providers' actual API values. -// Used in ReasoningCommon.supported_efforts to describe catalog-level capabilities. -// Per-provider params use their own specific effort enums below. -enum ReasoningEffort { - REASONING_EFFORT_UNSPECIFIED = 0; - REASONING_EFFORT_NONE = 1; - REASONING_EFFORT_MINIMAL = 2; - REASONING_EFFORT_LOW = 3; - REASONING_EFFORT_MEDIUM = 4; - REASONING_EFFORT_HIGH = 5; - REASONING_EFFORT_MAX = 6; - REASONING_EFFORT_AUTO = 7; -} - -// Per-provider reasoning effort enums — match actual API values - -enum OpenAIReasoningEffort { - OPENAI_REASONING_EFFORT_UNSPECIFIED = 0; - OPENAI_REASONING_EFFORT_LOW = 1; - OPENAI_REASONING_EFFORT_MEDIUM = 2; - OPENAI_REASONING_EFFORT_HIGH = 3; -} - -enum AnthropicReasoningEffort { - ANTHROPIC_REASONING_EFFORT_UNSPECIFIED = 0; - ANTHROPIC_REASONING_EFFORT_LOW = 1; - ANTHROPIC_REASONING_EFFORT_MEDIUM = 2; - ANTHROPIC_REASONING_EFFORT_HIGH = 3; - ANTHROPIC_REASONING_EFFORT_MAX = 4; -} - -// ═══════════════════════════════════════════════════════════════════════════════ -// Shared Messages -// ═══════════════════════════════════════════════════════════════════════════════ - -message NumericRange { - double min = 1; - double max = 2; -} - -message PricePerToken { - optional double per_million_tokens = 1; // nullable — absent means unknown - Currency currency = 2; -} - -// Generic key-value metadata (replaces Record) -message Metadata { - map entries = 1; -} diff --git a/packages/provider-registry/proto/v1/model.proto b/packages/provider-registry/proto/v1/model.proto deleted file mode 100644 index 36cf0ee0983..00000000000 --- a/packages/provider-registry/proto/v1/model.proto +++ /dev/null @@ -1,110 +0,0 @@ -syntax = "proto3"; -package registry.v1; - -import "v1/common.proto"; - -// ═══════════════════════════════════════════════════════════════════════════════ -// Reasoning Support (model-level capabilities only) -// -// Describes WHAT reasoning capabilities a model supports. -// HOW to invoke reasoning is defined by the provider's reasoning_format -// (see provider.proto ProviderReasoningFormat). -// ═══════════════════════════════════════════════════════════════════════════════ - -message ThinkingTokenLimits { - optional uint32 min = 1; - optional uint32 max = 2; - optional uint32 default = 3; -} - -// Model-level reasoning capabilities -message ReasoningSupport { - optional ThinkingTokenLimits thinking_token_limits = 1; - repeated ReasoningEffort supported_efforts = 2; - optional bool interleaved = 3; -} - -// ═══════════════════════════════════════════════════════════════════════════════ -// Parameter Support -// ═══════════════════════════════════════════════════════════════════════════════ - -message RangedParameterSupport { - bool supported = 1; - optional NumericRange range = 2; -} - -message ParameterSupport { - optional RangedParameterSupport temperature = 1; - optional RangedParameterSupport top_p = 2; - optional RangedParameterSupport top_k = 3; - optional bool frequency_penalty = 4; - optional bool presence_penalty = 5; - optional bool max_tokens = 6; - optional bool stop_sequences = 7; - optional bool system_message = 8; -} - -// ═══════════════════════════════════════════════════════════════════════════════ -// Pricing -// ═══════════════════════════════════════════════════════════════════════════════ - -enum ImagePriceUnit { - IMAGE_PRICE_UNIT_UNSPECIFIED = 0; - IMAGE_PRICE_UNIT_IMAGE = 1; - IMAGE_PRICE_UNIT_PIXEL = 2; -} - -message ImagePrice { - double price = 1; - Currency currency = 2; - ImagePriceUnit unit = 3; -} - -message MinutePrice { - double price = 1; - Currency currency = 2; -} - -message ModelPricing { - PricePerToken input = 1; - PricePerToken output = 2; - optional PricePerToken cache_read = 3; - optional PricePerToken cache_write = 4; - optional ImagePrice per_image = 5; - optional MinutePrice per_minute = 6; -} - -// ═══════════════════════════════════════════════════════════════════════════════ -// Model Config -// ═══════════════════════════════════════════════════════════════════════════════ - -message ModelConfig { - string id = 1; - optional string name = 2; - optional string description = 3; - - repeated ModelCapability capabilities = 4; - repeated Modality input_modalities = 5; - repeated Modality output_modalities = 6; - - optional uint32 context_window = 7; - optional uint32 max_output_tokens = 8; - optional uint32 max_input_tokens = 9; - - optional ModelPricing pricing = 10; - optional ReasoningSupport reasoning = 11; - optional ParameterSupport parameter_support = 12; - - optional string family = 13; - optional string owned_by = 14; - optional bool open_weights = 15; - - repeated string alias = 16; - optional Metadata metadata = 17; -} - -// Top-level container -message ModelRegistry { - string version = 1; - repeated ModelConfig models = 2; -} diff --git a/packages/provider-registry/proto/v1/provider.proto b/packages/provider-registry/proto/v1/provider.proto deleted file mode 100644 index 2ce8bbca8fe..00000000000 --- a/packages/provider-registry/proto/v1/provider.proto +++ /dev/null @@ -1,219 +0,0 @@ -syntax = "proto3"; -package registry.v1; - -import "v1/common.proto"; - -// ═══════════════════════════════════════════════════════════════════════════════ -// API Features -// -// Flags describing which API features a provider supports. -// These control request construction at the SDK level. -// ═══════════════════════════════════════════════════════════════════════════════ - -message ApiFeatures { - // --- Request format flags --- - - // Whether the provider supports array-formatted content in messages - optional bool array_content = 1; - // Whether the provider supports stream_options for usage data - optional bool stream_options = 2; - - // --- Provider-specific parameter flags --- - - // Whether the provider supports the 'developer' role (OpenAI-specific) - optional bool developer_role = 3; - // Whether the provider supports service tier selection (OpenAI/Groq-specific) - optional bool service_tier = 4; - // Whether the provider supports verbosity settings (Gemini-specific) - optional bool verbosity = 5; - // Whether the provider supports enable_thinking parameter format - optional bool enable_thinking = 6; -} - -// ═══════════════════════════════════════════════════════════════════════════════ -// Provider Reasoning Format -// -// Describes HOW a provider's API expects reasoning parameters to be formatted. -// This is a provider-level concern — every model on a given provider uses the -// same parameter format. Model-level reasoning capabilities (effort levels, -// token limits) are in model.proto ReasoningSupport. -// -// The type names describe API parameter shapes, not providers or model families: -// openai_chat — { reasoningEffort: 'low'|'medium'|'high' } -// openai_responses — { reasoningEffort, reasoningSummary } -// anthropic — { thinking: { type, budgetTokens }, effort } -// gemini — { thinkingConfig: { thinkingBudget, thinkingLevel } } -// openrouter — { reasoning: { effort, enabled, exclude } } -// enable_thinking — { enable_thinking: bool, thinking_budget?: number } -// thinking_type — { thinking: { type: 'enabled'|'disabled'|'auto' } } -// dashscope — { enable_thinking: bool, incremental_output?: bool } -// self_hosted — { chat_template_kwargs: { thinking, enable_thinking } } -// ═══════════════════════════════════════════════════════════════════════════════ - -// --- Per-provider reasoning param definitions --- - -message OpenAIChatReasoningFormat { - optional OpenAIReasoningEffort reasoning_effort = 1; -} - -enum ResponsesSummaryMode { - RESPONSES_SUMMARY_MODE_UNSPECIFIED = 0; - RESPONSES_SUMMARY_MODE_AUTO = 1; - RESPONSES_SUMMARY_MODE_CONCISE = 2; - RESPONSES_SUMMARY_MODE_DETAILED = 3; -} - -message OpenAIResponsesReasoningFormat { - optional OpenAIReasoningEffort effort = 1; - optional ResponsesSummaryMode summary = 2; -} - -enum AnthropicThinkingType { - ANTHROPIC_THINKING_TYPE_UNSPECIFIED = 0; - ANTHROPIC_THINKING_TYPE_ENABLED = 1; - ANTHROPIC_THINKING_TYPE_DISABLED = 2; - ANTHROPIC_THINKING_TYPE_ADAPTIVE = 3; -} - -message AnthropicReasoningFormat { - optional AnthropicThinkingType type = 1; - optional uint32 budget_tokens = 2; - optional AnthropicReasoningEffort effort = 3; -} - -message GeminiThinkingConfig { - optional bool include_thoughts = 1; - optional uint32 thinking_budget = 2; -} - -enum GeminiThinkingLevel { - GEMINI_THINKING_LEVEL_UNSPECIFIED = 0; - GEMINI_THINKING_LEVEL_MINIMAL = 1; - GEMINI_THINKING_LEVEL_LOW = 2; - GEMINI_THINKING_LEVEL_MEDIUM = 3; - GEMINI_THINKING_LEVEL_HIGH = 4; -} - -message GeminiReasoningFormat { - oneof config { - GeminiThinkingConfig thinking_config = 1; - GeminiThinkingLevel thinking_level = 2; - } -} - -message OpenRouterReasoningFormat { - optional OpenAIReasoningEffort effort = 1; - optional uint32 max_tokens = 2; - optional bool exclude = 3; -} - -// enable_thinking + thinking_budget format (Qwen, Hunyuan, Silicon, etc.) -message EnableThinkingReasoningFormat { - optional bool enable_thinking = 1; - optional uint32 thinking_budget = 2; -} - -enum ThinkingType { - THINKING_TYPE_UNSPECIFIED = 0; - THINKING_TYPE_ENABLED = 1; - THINKING_TYPE_DISABLED = 2; - THINKING_TYPE_AUTO = 3; -} - -// thinking: { type } format (Doubao, Zhipu, DeepSeek, many aggregators) -message ThinkingTypeReasoningFormat { - optional ThinkingType thinking_type = 1; -} - -// DashScope-specific: enable_thinking + incremental_output -message DashscopeReasoningFormat { - optional bool enable_thinking = 1; - optional bool incremental_output = 2; -} - -// Self-hosted/Nvidia: chat_template_kwargs -message SelfHostedReasoningFormat { - optional bool enable_thinking = 1; - optional bool thinking = 2; -} - -// Discriminated union: which API parameter shape this provider uses for reasoning -message ProviderReasoningFormat { - oneof format { - OpenAIChatReasoningFormat openai_chat = 1; - OpenAIResponsesReasoningFormat openai_responses = 2; - AnthropicReasoningFormat anthropic = 3; - GeminiReasoningFormat gemini = 4; - OpenRouterReasoningFormat openrouter = 5; - EnableThinkingReasoningFormat enable_thinking = 6; - ThinkingTypeReasoningFormat thinking_type = 7; - DashscopeReasoningFormat dashscope = 8; - SelfHostedReasoningFormat self_hosted = 9; - } -} - -// ═══════════════════════════════════════════════════════════════════════════════ -// Provider Website -// ═══════════════════════════════════════════════════════════════════════════════ - -message ProviderWebsite { - optional string official = 1; - optional string docs = 2; - optional string api_key = 3; - optional string models = 4; -} - -// ═══════════════════════════════════════════════════════════════════════════════ -// Models API URLs -// ═══════════════════════════════════════════════════════════════════════════════ - -message ModelsApiUrls { - optional string default = 1; - optional string embedding = 2; - optional string reranker = 3; -} - -// ═══════════════════════════════════════════════════════════════════════════════ -// Provider Metadata (wraps generic Metadata + provider-specific fields) -// ═══════════════════════════════════════════════════════════════════════════════ - -message ProviderMetadata { - optional ProviderWebsite website = 1; -} - -// ═══════════════════════════════════════════════════════════════════════════════ -// Per-endpoint-type configuration -// ═══════════════════════════════════════════════════════════════════════════════ - -message EndpointConfig { - // Base URL for this endpoint type's API - optional string base_url = 1; - // URLs for fetching available models via this endpoint type - optional ModelsApiUrls models_api_urls = 2; - // How this endpoint type expects reasoning parameters to be formatted - optional ProviderReasoningFormat reasoning_format = 3; -} - -// ═══════════════════════════════════════════════════════════════════════════════ -// Provider Config -// ═══════════════════════════════════════════════════════════════════════════════ - -message ProviderConfig { - string id = 1; - string name = 2; - optional string description = 3; - - // Per-endpoint-type configuration (key = EndpointType enum value) - map endpoint_configs = 10; - - optional EndpointType default_chat_endpoint = 5; - optional ApiFeatures api_features = 6; - - optional ProviderMetadata metadata = 8; -} - -// Top-level container -message ProviderRegistry { - string version = 1; - repeated ProviderConfig providers = 2; -} diff --git a/packages/provider-registry/proto/v1/provider_models.proto b/packages/provider-registry/proto/v1/provider_models.proto deleted file mode 100644 index 603f5551b04..00000000000 --- a/packages/provider-registry/proto/v1/provider_models.proto +++ /dev/null @@ -1,62 +0,0 @@ -syntax = "proto3"; -package registry.v1; - -import "v1/common.proto"; -import "v1/model.proto"; - -// ═══════════════════════════════════════════════════════════════════════════════ -// Capability Override -// ═══════════════════════════════════════════════════════════════════════════════ - -message CapabilityOverride { - repeated ModelCapability add = 1; - repeated ModelCapability remove = 2; - repeated ModelCapability force = 3; -} - -// ═══════════════════════════════════════════════════════════════════════════════ -// Model Limits Override -// ═══════════════════════════════════════════════════════════════════════════════ - -message ModelLimits { - optional uint32 context_window = 1; - optional uint32 max_output_tokens = 2; - optional uint32 max_input_tokens = 3; -} - -// ═══════════════════════════════════════════════════════════════════════════════ -// Provider Model Override -// ═══════════════════════════════════════════════════════════════════════════════ - -message ProviderModelOverride { - // Identification - string provider_id = 1; - string model_id = 2; - optional string api_model_id = 3; - optional string model_variant = 4; - - // Overrides - optional CapabilityOverride capabilities = 5; - optional ModelLimits limits = 6; - optional ModelPricing pricing = 7; - optional ReasoningSupport reasoning = 8; - optional ParameterSupport parameter_support = 9; - - repeated EndpointType endpoint_types = 10; - repeated Modality input_modalities = 11; - repeated Modality output_modalities = 12; - - // Status - optional bool disabled = 13; - optional string replace_with = 14; - - // Metadata - optional string reason = 15; - uint32 priority = 16; // 0 = auto, 100+ = manual -} - -// Top-level container -message ProviderModelRegistry { - string version = 1; - repeated ProviderModelOverride overrides = 2; -} diff --git a/packages/provider-registry/src/gen/v1/common_pb.ts b/packages/provider-registry/src/gen/v1/common_pb.ts deleted file mode 100644 index f6e1da13432..00000000000 --- a/packages/provider-registry/src/gen/v1/common_pb.ts +++ /dev/null @@ -1,456 +0,0 @@ -// @generated by protoc-gen-es v2.11.0 with parameter "target=ts" -// @generated from file v1/common.proto (package registry.v1, syntax proto3) -/* eslint-disable */ - -import type { GenEnum, GenFile, GenMessage } from '@bufbuild/protobuf/codegenv2' -import { enumDesc, fileDesc, messageDesc } from '@bufbuild/protobuf/codegenv2' -import type { Message } from '@bufbuild/protobuf' - -/** - * Describes the file v1/common.proto. - */ -export const file_v1_common: GenFile = - /*@__PURE__*/ - fileDesc( - 'Cg92MS9jb21tb24ucHJvdG8SC3JlZ2lzdHJ5LnYxIigKDE51bWVyaWNSYW5nZRILCgNtaW4YASABKAESCwoDbWF4GAIgASgBInAKDVByaWNlUGVyVG9rZW4SHwoScGVyX21pbGxpb25fdG9rZW5zGAEgASgBSACIAQESJwoIY3VycmVuY3kYAiABKA4yFS5yZWdpc3RyeS52MS5DdXJyZW5jeUIVChNfcGVyX21pbGxpb25fdG9rZW5zIm8KCE1ldGFkYXRhEjMKB2VudHJpZXMYASADKAsyIi5yZWdpc3RyeS52MS5NZXRhZGF0YS5FbnRyaWVzRW50cnkaLgoMRW50cmllc0VudHJ5EgsKA2tleRgBIAEoCRINCgV2YWx1ZRgCIAEoCToCOAEq/AQKDEVuZHBvaW50VHlwZRIdChlFTkRQT0lOVF9UWVBFX1VOU1BFQ0lGSUVEEAASKQolRU5EUE9JTlRfVFlQRV9PUEVOQUlfQ0hBVF9DT01QTEVUSU9OUxABEikKJUVORFBPSU5UX1RZUEVfT1BFTkFJX1RFWFRfQ09NUExFVElPTlMQAhIkCiBFTkRQT0lOVF9UWVBFX0FOVEhST1BJQ19NRVNTQUdFUxADEiIKHkVORFBPSU5UX1RZUEVfT1BFTkFJX1JFU1BPTlNFUxAEEikKJUVORFBPSU5UX1RZUEVfR09PR0xFX0dFTkVSQVRFX0NPTlRFTlQQBRIdChlFTkRQT0lOVF9UWVBFX09MTEFNQV9DSEFUEAYSIQodRU5EUE9JTlRfVFlQRV9PTExBTUFfR0VORVJBVEUQBxIjCh9FTkRQT0lOVF9UWVBFX09QRU5BSV9FTUJFRERJTkdTEAgSHQoZRU5EUE9JTlRfVFlQRV9KSU5BX1JFUkFOSxAJEikKJUVORFBPSU5UX1RZUEVfT1BFTkFJX0lNQUdFX0dFTkVSQVRJT04QChIjCh9FTkRQT0lOVF9UWVBFX09QRU5BSV9JTUFHRV9FRElUEAsSLAooRU5EUE9JTlRfVFlQRV9PUEVOQUlfQVVESU9fVFJBTlNDUklQVElPThAMEioKJkVORFBPSU5UX1RZUEVfT1BFTkFJX0FVRElPX1RSQU5TTEFUSU9OEA0SJwojRU5EUE9JTlRfVFlQRV9PUEVOQUlfVEVYVF9UT19TUEVFQ0gQDhIpCiVFTkRQT0lOVF9UWVBFX09QRU5BSV9WSURFT19HRU5FUkFUSU9OEA8qnAUKD01vZGVsQ2FwYWJpbGl0eRIgChxNT0RFTF9DQVBBQklMSVRZX1VOU1BFQ0lGSUVEEAASIgoeTU9ERUxfQ0FQQUJJTElUWV9GVU5DVElPTl9DQUxMEAESHgoaTU9ERUxfQ0FQQUJJTElUWV9SRUFTT05JTkcQAhImCiJNT0RFTF9DQVBBQklMSVRZX0lNQUdFX1JFQ09HTklUSU9OEAMSJQohTU9ERUxfQ0FQQUJJTElUWV9JTUFHRV9HRU5FUkFUSU9OEAQSJgoiTU9ERUxfQ0FQQUJJTElUWV9BVURJT19SRUNPR05JVElPThAFEiUKIU1PREVMX0NBUEFCSUxJVFlfQVVESU9fR0VORVJBVElPThAGEh4KGk1PREVMX0NBUEFCSUxJVFlfRU1CRURESU5HEAcSGwoXTU9ERUxfQ0FQQUJJTElUWV9SRVJBTksQCBIlCiFNT0RFTF9DQVBBQklMSVRZX0FVRElPX1RSQU5TQ1JJUFQQCRImCiJNT0RFTF9DQVBBQklMSVRZX1ZJREVPX1JFQ09HTklUSU9OEAoSJQohTU9ERUxfQ0FQQUJJTElUWV9WSURFT19HRU5FUkFUSU9OEAsSJgoiTU9ERUxfQ0FQQUJJTElUWV9TVFJVQ1RVUkVEX09VVFBVVBAMEh8KG01PREVMX0NBUEFCSUxJVFlfRklMRV9JTlBVVBANEh8KG01PREVMX0NBUEFCSUxJVFlfV0VCX1NFQVJDSBAOEiMKH01PREVMX0NBUEFCSUxJVFlfQ09ERV9FWEVDVVRJT04QDxIgChxNT0RFTF9DQVBBQklMSVRZX0ZJTEVfU0VBUkNIEBASIQodTU9ERUxfQ0FQQUJJTElUWV9DT01QVVRFUl9VU0UQESqIAQoITW9kYWxpdHkSGAoUTU9EQUxJVFlfVU5TUEVDSUZJRUQQABIRCg1NT0RBTElUWV9URVhUEAESEgoOTU9EQUxJVFlfSU1BR0UQAhISCg5NT0RBTElUWV9BVURJTxADEhIKDk1PREFMSVRZX1ZJREVPEAQSEwoPTU9EQUxJVFlfVkVDVE9SEAUqSAoIQ3VycmVuY3kSGAoUQ1VSUkVOQ1lfVU5TUEVDSUZJRUQQABIQCgxDVVJSRU5DWV9VU0QQARIQCgxDVVJSRU5DWV9DTlkQAirzAQoPUmVhc29uaW5nRWZmb3J0EiAKHFJFQVNPTklOR19FRkZPUlRfVU5TUEVDSUZJRUQQABIZChVSRUFTT05JTkdfRUZGT1JUX05PTkUQARIcChhSRUFTT05JTkdfRUZGT1JUX01JTklNQUwQAhIYChRSRUFTT05JTkdfRUZGT1JUX0xPVxADEhsKF1JFQVNPTklOR19FRkZPUlRfTUVESVVNEAQSGQoVUkVBU09OSU5HX0VGRk9SVF9ISUdIEAUSGAoUUkVBU09OSU5HX0VGRk9SVF9NQVgQBhIZChVSRUFTT05JTkdfRUZGT1JUX0FVVE8QByqnAQoVT3BlbkFJUmVhc29uaW5nRWZmb3J0EicKI09QRU5BSV9SRUFTT05JTkdfRUZGT1JUX1VOU1BFQ0lGSUVEEAASHwobT1BFTkFJX1JFQVNPTklOR19FRkZPUlRfTE9XEAESIgoeT1BFTkFJX1JFQVNPTklOR19FRkZPUlRfTUVESVVNEAISIAocT1BFTkFJX1JFQVNPTklOR19FRkZPUlRfSElHSBADKtoBChhBbnRocm9waWNSZWFzb25pbmdFZmZvcnQSKgomQU5USFJPUElDX1JFQVNPTklOR19FRkZPUlRfVU5TUEVDSUZJRUQQABIiCh5BTlRIUk9QSUNfUkVBU09OSU5HX0VGRk9SVF9MT1cQARIlCiFBTlRIUk9QSUNfUkVBU09OSU5HX0VGRk9SVF9NRURJVU0QAhIjCh9BTlRIUk9QSUNfUkVBU09OSU5HX0VGRk9SVF9ISUdIEAMSIgoeQU5USFJPUElDX1JFQVNPTklOR19FRkZPUlRfTUFYEARiBnByb3RvMw' - ) - -/** - * @generated from message registry.v1.NumericRange - */ -export type NumericRange = Message<'registry.v1.NumericRange'> & { - /** - * @generated from field: double min = 1; - */ - min: number - - /** - * @generated from field: double max = 2; - */ - max: number -} - -/** - * Describes the message registry.v1.NumericRange. - * Use `create(NumericRangeSchema)` to create a new message. - */ -export const NumericRangeSchema: GenMessage = /*@__PURE__*/ messageDesc(file_v1_common, 0) - -/** - * @generated from message registry.v1.PricePerToken - */ -export type PricePerToken = Message<'registry.v1.PricePerToken'> & { - /** - * nullable — absent means unknown - * - * @generated from field: optional double per_million_tokens = 1; - */ - perMillionTokens?: number - - /** - * @generated from field: registry.v1.Currency currency = 2; - */ - currency: Currency -} - -/** - * Describes the message registry.v1.PricePerToken. - * Use `create(PricePerTokenSchema)` to create a new message. - */ -export const PricePerTokenSchema: GenMessage = /*@__PURE__*/ messageDesc(file_v1_common, 1) - -/** - * Generic key-value metadata (replaces Record) - * - * @generated from message registry.v1.Metadata - */ -export type Metadata = Message<'registry.v1.Metadata'> & { - /** - * @generated from field: map entries = 1; - */ - entries: { [key: string]: string } -} - -/** - * Describes the message registry.v1.Metadata. - * Use `create(MetadataSchema)` to create a new message. - */ -export const MetadataSchema: GenMessage = /*@__PURE__*/ messageDesc(file_v1_common, 2) - -/** - * @generated from enum registry.v1.EndpointType - */ -export enum EndpointType { - /** - * @generated from enum value: ENDPOINT_TYPE_UNSPECIFIED = 0; - */ - UNSPECIFIED = 0, - - /** - * @generated from enum value: ENDPOINT_TYPE_OPENAI_CHAT_COMPLETIONS = 1; - */ - OPENAI_CHAT_COMPLETIONS = 1, - - /** - * @generated from enum value: ENDPOINT_TYPE_OPENAI_TEXT_COMPLETIONS = 2; - */ - OPENAI_TEXT_COMPLETIONS = 2, - - /** - * @generated from enum value: ENDPOINT_TYPE_ANTHROPIC_MESSAGES = 3; - */ - ANTHROPIC_MESSAGES = 3, - - /** - * @generated from enum value: ENDPOINT_TYPE_OPENAI_RESPONSES = 4; - */ - OPENAI_RESPONSES = 4, - - /** - * @generated from enum value: ENDPOINT_TYPE_GOOGLE_GENERATE_CONTENT = 5; - */ - GOOGLE_GENERATE_CONTENT = 5, - - /** - * @generated from enum value: ENDPOINT_TYPE_OLLAMA_CHAT = 6; - */ - OLLAMA_CHAT = 6, - - /** - * @generated from enum value: ENDPOINT_TYPE_OLLAMA_GENERATE = 7; - */ - OLLAMA_GENERATE = 7, - - /** - * @generated from enum value: ENDPOINT_TYPE_OPENAI_EMBEDDINGS = 8; - */ - OPENAI_EMBEDDINGS = 8, - - /** - * @generated from enum value: ENDPOINT_TYPE_JINA_RERANK = 9; - */ - JINA_RERANK = 9, - - /** - * @generated from enum value: ENDPOINT_TYPE_OPENAI_IMAGE_GENERATION = 10; - */ - OPENAI_IMAGE_GENERATION = 10, - - /** - * @generated from enum value: ENDPOINT_TYPE_OPENAI_IMAGE_EDIT = 11; - */ - OPENAI_IMAGE_EDIT = 11, - - /** - * @generated from enum value: ENDPOINT_TYPE_OPENAI_AUDIO_TRANSCRIPTION = 12; - */ - OPENAI_AUDIO_TRANSCRIPTION = 12, - - /** - * @generated from enum value: ENDPOINT_TYPE_OPENAI_AUDIO_TRANSLATION = 13; - */ - OPENAI_AUDIO_TRANSLATION = 13, - - /** - * @generated from enum value: ENDPOINT_TYPE_OPENAI_TEXT_TO_SPEECH = 14; - */ - OPENAI_TEXT_TO_SPEECH = 14, - - /** - * @generated from enum value: ENDPOINT_TYPE_OPENAI_VIDEO_GENERATION = 15; - */ - OPENAI_VIDEO_GENERATION = 15 -} - -/** - * Describes the enum registry.v1.EndpointType. - */ -export const EndpointTypeSchema: GenEnum = /*@__PURE__*/ enumDesc(file_v1_common, 0) - -/** - * @generated from enum registry.v1.ModelCapability - */ -export enum ModelCapability { - /** - * @generated from enum value: MODEL_CAPABILITY_UNSPECIFIED = 0; - */ - UNSPECIFIED = 0, - - /** - * @generated from enum value: MODEL_CAPABILITY_FUNCTION_CALL = 1; - */ - FUNCTION_CALL = 1, - - /** - * @generated from enum value: MODEL_CAPABILITY_REASONING = 2; - */ - REASONING = 2, - - /** - * @generated from enum value: MODEL_CAPABILITY_IMAGE_RECOGNITION = 3; - */ - IMAGE_RECOGNITION = 3, - - /** - * @generated from enum value: MODEL_CAPABILITY_IMAGE_GENERATION = 4; - */ - IMAGE_GENERATION = 4, - - /** - * @generated from enum value: MODEL_CAPABILITY_AUDIO_RECOGNITION = 5; - */ - AUDIO_RECOGNITION = 5, - - /** - * @generated from enum value: MODEL_CAPABILITY_AUDIO_GENERATION = 6; - */ - AUDIO_GENERATION = 6, - - /** - * @generated from enum value: MODEL_CAPABILITY_EMBEDDING = 7; - */ - EMBEDDING = 7, - - /** - * @generated from enum value: MODEL_CAPABILITY_RERANK = 8; - */ - RERANK = 8, - - /** - * @generated from enum value: MODEL_CAPABILITY_AUDIO_TRANSCRIPT = 9; - */ - AUDIO_TRANSCRIPT = 9, - - /** - * @generated from enum value: MODEL_CAPABILITY_VIDEO_RECOGNITION = 10; - */ - VIDEO_RECOGNITION = 10, - - /** - * @generated from enum value: MODEL_CAPABILITY_VIDEO_GENERATION = 11; - */ - VIDEO_GENERATION = 11, - - /** - * @generated from enum value: MODEL_CAPABILITY_STRUCTURED_OUTPUT = 12; - */ - STRUCTURED_OUTPUT = 12, - - /** - * @generated from enum value: MODEL_CAPABILITY_FILE_INPUT = 13; - */ - FILE_INPUT = 13, - - /** - * @generated from enum value: MODEL_CAPABILITY_WEB_SEARCH = 14; - */ - WEB_SEARCH = 14, - - /** - * @generated from enum value: MODEL_CAPABILITY_CODE_EXECUTION = 15; - */ - CODE_EXECUTION = 15, - - /** - * @generated from enum value: MODEL_CAPABILITY_FILE_SEARCH = 16; - */ - FILE_SEARCH = 16, - - /** - * @generated from enum value: MODEL_CAPABILITY_COMPUTER_USE = 17; - */ - COMPUTER_USE = 17 -} - -/** - * Describes the enum registry.v1.ModelCapability. - */ -export const ModelCapabilitySchema: GenEnum = /*@__PURE__*/ enumDesc(file_v1_common, 1) - -/** - * @generated from enum registry.v1.Modality - */ -export enum Modality { - /** - * @generated from enum value: MODALITY_UNSPECIFIED = 0; - */ - UNSPECIFIED = 0, - - /** - * @generated from enum value: MODALITY_TEXT = 1; - */ - TEXT = 1, - - /** - * @generated from enum value: MODALITY_IMAGE = 2; - */ - IMAGE = 2, - - /** - * @generated from enum value: MODALITY_AUDIO = 3; - */ - AUDIO = 3, - - /** - * @generated from enum value: MODALITY_VIDEO = 4; - */ - VIDEO = 4, - - /** - * @generated from enum value: MODALITY_VECTOR = 5; - */ - VECTOR = 5 -} - -/** - * Describes the enum registry.v1.Modality. - */ -export const ModalitySchema: GenEnum = /*@__PURE__*/ enumDesc(file_v1_common, 2) - -/** - * @generated from enum registry.v1.Currency - */ -export enum Currency { - /** - * defaults to USD - * - * @generated from enum value: CURRENCY_UNSPECIFIED = 0; - */ - UNSPECIFIED = 0, - - /** - * @generated from enum value: CURRENCY_USD = 1; - */ - USD = 1, - - /** - * @generated from enum value: CURRENCY_CNY = 2; - */ - CNY = 2 -} - -/** - * Describes the enum registry.v1.Currency. - */ -export const CurrencySchema: GenEnum = /*@__PURE__*/ enumDesc(file_v1_common, 3) - -/** - * Shared reasoning effort levels — superset of all providers' actual API values. - * Used in ReasoningCommon.supported_efforts to describe catalog-level capabilities. - * Per-provider params use their own specific effort enums below. - * - * @generated from enum registry.v1.ReasoningEffort - */ -export enum ReasoningEffort { - /** - * @generated from enum value: REASONING_EFFORT_UNSPECIFIED = 0; - */ - UNSPECIFIED = 0, - - /** - * @generated from enum value: REASONING_EFFORT_NONE = 1; - */ - NONE = 1, - - /** - * @generated from enum value: REASONING_EFFORT_MINIMAL = 2; - */ - MINIMAL = 2, - - /** - * @generated from enum value: REASONING_EFFORT_LOW = 3; - */ - LOW = 3, - - /** - * @generated from enum value: REASONING_EFFORT_MEDIUM = 4; - */ - MEDIUM = 4, - - /** - * @generated from enum value: REASONING_EFFORT_HIGH = 5; - */ - HIGH = 5, - - /** - * @generated from enum value: REASONING_EFFORT_MAX = 6; - */ - MAX = 6, - - /** - * @generated from enum value: REASONING_EFFORT_AUTO = 7; - */ - AUTO = 7 -} - -/** - * Describes the enum registry.v1.ReasoningEffort. - */ -export const ReasoningEffortSchema: GenEnum = /*@__PURE__*/ enumDesc(file_v1_common, 4) - -/** - * @generated from enum registry.v1.OpenAIReasoningEffort - */ -export enum OpenAIReasoningEffort { - /** - * @generated from enum value: OPENAI_REASONING_EFFORT_UNSPECIFIED = 0; - */ - OPENAI_REASONING_EFFORT_UNSPECIFIED = 0, - - /** - * @generated from enum value: OPENAI_REASONING_EFFORT_LOW = 1; - */ - OPENAI_REASONING_EFFORT_LOW = 1, - - /** - * @generated from enum value: OPENAI_REASONING_EFFORT_MEDIUM = 2; - */ - OPENAI_REASONING_EFFORT_MEDIUM = 2, - - /** - * @generated from enum value: OPENAI_REASONING_EFFORT_HIGH = 3; - */ - OPENAI_REASONING_EFFORT_HIGH = 3 -} - -/** - * Describes the enum registry.v1.OpenAIReasoningEffort. - */ -export const OpenAIReasoningEffortSchema: GenEnum = /*@__PURE__*/ enumDesc(file_v1_common, 5) - -/** - * @generated from enum registry.v1.AnthropicReasoningEffort - */ -export enum AnthropicReasoningEffort { - /** - * @generated from enum value: ANTHROPIC_REASONING_EFFORT_UNSPECIFIED = 0; - */ - UNSPECIFIED = 0, - - /** - * @generated from enum value: ANTHROPIC_REASONING_EFFORT_LOW = 1; - */ - LOW = 1, - - /** - * @generated from enum value: ANTHROPIC_REASONING_EFFORT_MEDIUM = 2; - */ - MEDIUM = 2, - - /** - * @generated from enum value: ANTHROPIC_REASONING_EFFORT_HIGH = 3; - */ - HIGH = 3, - - /** - * @generated from enum value: ANTHROPIC_REASONING_EFFORT_MAX = 4; - */ - MAX = 4 -} - -/** - * Describes the enum registry.v1.AnthropicReasoningEffort. - */ -export const AnthropicReasoningEffortSchema: GenEnum = - /*@__PURE__*/ - enumDesc(file_v1_common, 6) diff --git a/packages/provider-registry/src/gen/v1/model_pb.ts b/packages/provider-registry/src/gen/v1/model_pb.ts deleted file mode 100644 index 087008be8fe..00000000000 --- a/packages/provider-registry/src/gen/v1/model_pb.ts +++ /dev/null @@ -1,387 +0,0 @@ -// @generated by protoc-gen-es v2.11.0 with parameter "target=ts" -// @generated from file v1/model.proto (package registry.v1, syntax proto3) -/* eslint-disable */ - -import type { GenEnum, GenFile, GenMessage } from '@bufbuild/protobuf/codegenv2' -import { enumDesc, fileDesc, messageDesc } from '@bufbuild/protobuf/codegenv2' -import type { - Currency, - Metadata, - Modality, - ModelCapability, - NumericRange, - PricePerToken, - ReasoningEffort -} from './common_pb' -import { file_v1_common } from './common_pb' -import type { Message } from '@bufbuild/protobuf' - -/** - * Describes the file v1/model.proto. - */ -export const file_v1_model: GenFile = - /*@__PURE__*/ - fileDesc( - 'Cg52MS9tb2RlbC5wcm90bxILcmVnaXN0cnkudjEiawoTVGhpbmtpbmdUb2tlbkxpbWl0cxIQCgNtaW4YASABKA1IAIgBARIQCgNtYXgYAiABKA1IAYgBARIUCgdkZWZhdWx0GAMgASgNSAKIAQFCBgoEX21pbkIGCgRfbWF4QgoKCF9kZWZhdWx0ItUBChBSZWFzb25pbmdTdXBwb3J0EkQKFXRoaW5raW5nX3Rva2VuX2xpbWl0cxgBIAEoCzIgLnJlZ2lzdHJ5LnYxLlRoaW5raW5nVG9rZW5MaW1pdHNIAIgBARI3ChFzdXBwb3J0ZWRfZWZmb3J0cxgCIAMoDjIcLnJlZ2lzdHJ5LnYxLlJlYXNvbmluZ0VmZm9ydBIYCgtpbnRlcmxlYXZlZBgDIAEoCEgBiAEBQhgKFl90aGlua2luZ190b2tlbl9saW1pdHNCDgoMX2ludGVybGVhdmVkImQKFlJhbmdlZFBhcmFtZXRlclN1cHBvcnQSEQoJc3VwcG9ydGVkGAEgASgIEi0KBXJhbmdlGAIgASgLMhkucmVnaXN0cnkudjEuTnVtZXJpY1JhbmdlSACIAQFCCAoGX3JhbmdlItkDChBQYXJhbWV0ZXJTdXBwb3J0Ej0KC3RlbXBlcmF0dXJlGAEgASgLMiMucmVnaXN0cnkudjEuUmFuZ2VkUGFyYW1ldGVyU3VwcG9ydEgAiAEBEjcKBXRvcF9wGAIgASgLMiMucmVnaXN0cnkudjEuUmFuZ2VkUGFyYW1ldGVyU3VwcG9ydEgBiAEBEjcKBXRvcF9rGAMgASgLMiMucmVnaXN0cnkudjEuUmFuZ2VkUGFyYW1ldGVyU3VwcG9ydEgCiAEBEh4KEWZyZXF1ZW5jeV9wZW5hbHR5GAQgASgISAOIAQESHQoQcHJlc2VuY2VfcGVuYWx0eRgFIAEoCEgEiAEBEhcKCm1heF90b2tlbnMYBiABKAhIBYgBARIbCg5zdG9wX3NlcXVlbmNlcxgHIAEoCEgGiAEBEhsKDnN5c3RlbV9tZXNzYWdlGAggASgISAeIAQFCDgoMX3RlbXBlcmF0dXJlQggKBl90b3BfcEIICgZfdG9wX2tCFAoSX2ZyZXF1ZW5jeV9wZW5hbHR5QhMKEV9wcmVzZW5jZV9wZW5hbHR5Qg0KC19tYXhfdG9rZW5zQhEKD19zdG9wX3NlcXVlbmNlc0IRCg9fc3lzdGVtX21lc3NhZ2UibwoKSW1hZ2VQcmljZRINCgVwcmljZRgBIAEoARInCghjdXJyZW5jeRgCIAEoDjIVLnJlZ2lzdHJ5LnYxLkN1cnJlbmN5EikKBHVuaXQYAyABKA4yGy5yZWdpc3RyeS52MS5JbWFnZVByaWNlVW5pdCJFCgtNaW51dGVQcmljZRINCgVwcmljZRgBIAEoARInCghjdXJyZW5jeRgCIAEoDjIVLnJlZ2lzdHJ5LnYxLkN1cnJlbmN5IvACCgxNb2RlbFByaWNpbmcSKQoFaW5wdXQYASABKAsyGi5yZWdpc3RyeS52MS5QcmljZVBlclRva2VuEioKBm91dHB1dBgCIAEoCzIaLnJlZ2lzdHJ5LnYxLlByaWNlUGVyVG9rZW4SMwoKY2FjaGVfcmVhZBgDIAEoCzIaLnJlZ2lzdHJ5LnYxLlByaWNlUGVyVG9rZW5IAIgBARI0CgtjYWNoZV93cml0ZRgEIAEoCzIaLnJlZ2lzdHJ5LnYxLlByaWNlUGVyVG9rZW5IAYgBARIvCglwZXJfaW1hZ2UYBSABKAsyFy5yZWdpc3RyeS52MS5JbWFnZVByaWNlSAKIAQESMQoKcGVyX21pbnV0ZRgGIAEoCzIYLnJlZ2lzdHJ5LnYxLk1pbnV0ZVByaWNlSAOIAQFCDQoLX2NhY2hlX3JlYWRCDgoMX2NhY2hlX3dyaXRlQgwKCl9wZXJfaW1hZ2VCDQoLX3Blcl9taW51dGUioQYKC01vZGVsQ29uZmlnEgoKAmlkGAEgASgJEhEKBG5hbWUYAiABKAlIAIgBARIYCgtkZXNjcmlwdGlvbhgDIAEoCUgBiAEBEjIKDGNhcGFiaWxpdGllcxgEIAMoDjIcLnJlZ2lzdHJ5LnYxLk1vZGVsQ2FwYWJpbGl0eRIvChBpbnB1dF9tb2RhbGl0aWVzGAUgAygOMhUucmVnaXN0cnkudjEuTW9kYWxpdHkSMAoRb3V0cHV0X21vZGFsaXRpZXMYBiADKA4yFS5yZWdpc3RyeS52MS5Nb2RhbGl0eRIbCg5jb250ZXh0X3dpbmRvdxgHIAEoDUgCiAEBEh4KEW1heF9vdXRwdXRfdG9rZW5zGAggASgNSAOIAQESHQoQbWF4X2lucHV0X3Rva2VucxgJIAEoDUgEiAEBEi8KB3ByaWNpbmcYCiABKAsyGS5yZWdpc3RyeS52MS5Nb2RlbFByaWNpbmdIBYgBARI1CglyZWFzb25pbmcYCyABKAsyHS5yZWdpc3RyeS52MS5SZWFzb25pbmdTdXBwb3J0SAaIAQESPQoRcGFyYW1ldGVyX3N1cHBvcnQYDCABKAsyHS5yZWdpc3RyeS52MS5QYXJhbWV0ZXJTdXBwb3J0SAeIAQESEwoGZmFtaWx5GA0gASgJSAiIAQESFQoIb3duZWRfYnkYDiABKAlICYgBARIZCgxvcGVuX3dlaWdodHMYDyABKAhICogBARINCgVhbGlhcxgQIAMoCRIsCghtZXRhZGF0YRgRIAEoCzIVLnJlZ2lzdHJ5LnYxLk1ldGFkYXRhSAuIAQFCBwoFX25hbWVCDgoMX2Rlc2NyaXB0aW9uQhEKD19jb250ZXh0X3dpbmRvd0IUChJfbWF4X291dHB1dF90b2tlbnNCEwoRX21heF9pbnB1dF90b2tlbnNCCgoIX3ByaWNpbmdCDAoKX3JlYXNvbmluZ0IUChJfcGFyYW1ldGVyX3N1cHBvcnRCCQoHX2ZhbWlseUILCglfb3duZWRfYnlCDwoNX29wZW5fd2VpZ2h0c0ILCglfbWV0YWRhdGEiSgoNTW9kZWxSZWdpc3RyeRIPCgd2ZXJzaW9uGAEgASgJEigKBm1vZGVscxgCIAMoCzIYLnJlZ2lzdHJ5LnYxLk1vZGVsQ29uZmlnKmoKDkltYWdlUHJpY2VVbml0EiAKHElNQUdFX1BSSUNFX1VOSVRfVU5TUEVDSUZJRUQQABIaChZJTUFHRV9QUklDRV9VTklUX0lNQUdFEAESGgoWSU1BR0VfUFJJQ0VfVU5JVF9QSVhFTBACYgZwcm90bzM', - [file_v1_common] - ) - -/** - * @generated from message registry.v1.ThinkingTokenLimits - */ -export type ThinkingTokenLimits = Message<'registry.v1.ThinkingTokenLimits'> & { - /** - * @generated from field: optional uint32 min = 1; - */ - min?: number - - /** - * @generated from field: optional uint32 max = 2; - */ - max?: number - - /** - * @generated from field: optional uint32 default = 3; - */ - default?: number -} - -/** - * Describes the message registry.v1.ThinkingTokenLimits. - * Use `create(ThinkingTokenLimitsSchema)` to create a new message. - */ -export const ThinkingTokenLimitsSchema: GenMessage = /*@__PURE__*/ messageDesc(file_v1_model, 0) - -/** - * Model-level reasoning capabilities - * - * @generated from message registry.v1.ReasoningSupport - */ -export type ReasoningSupport = Message<'registry.v1.ReasoningSupport'> & { - /** - * @generated from field: optional registry.v1.ThinkingTokenLimits thinking_token_limits = 1; - */ - thinkingTokenLimits?: ThinkingTokenLimits - - /** - * @generated from field: repeated registry.v1.ReasoningEffort supported_efforts = 2; - */ - supportedEfforts: ReasoningEffort[] - - /** - * @generated from field: optional bool interleaved = 3; - */ - interleaved?: boolean -} - -/** - * Describes the message registry.v1.ReasoningSupport. - * Use `create(ReasoningSupportSchema)` to create a new message. - */ -export const ReasoningSupportSchema: GenMessage = /*@__PURE__*/ messageDesc(file_v1_model, 1) - -/** - * @generated from message registry.v1.RangedParameterSupport - */ -export type RangedParameterSupport = Message<'registry.v1.RangedParameterSupport'> & { - /** - * @generated from field: bool supported = 1; - */ - supported: boolean - - /** - * @generated from field: optional registry.v1.NumericRange range = 2; - */ - range?: NumericRange -} - -/** - * Describes the message registry.v1.RangedParameterSupport. - * Use `create(RangedParameterSupportSchema)` to create a new message. - */ -export const RangedParameterSupportSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_v1_model, 2) - -/** - * @generated from message registry.v1.ParameterSupport - */ -export type ParameterSupport = Message<'registry.v1.ParameterSupport'> & { - /** - * @generated from field: optional registry.v1.RangedParameterSupport temperature = 1; - */ - temperature?: RangedParameterSupport - - /** - * @generated from field: optional registry.v1.RangedParameterSupport top_p = 2; - */ - topP?: RangedParameterSupport - - /** - * @generated from field: optional registry.v1.RangedParameterSupport top_k = 3; - */ - topK?: RangedParameterSupport - - /** - * @generated from field: optional bool frequency_penalty = 4; - */ - frequencyPenalty?: boolean - - /** - * @generated from field: optional bool presence_penalty = 5; - */ - presencePenalty?: boolean - - /** - * @generated from field: optional bool max_tokens = 6; - */ - maxTokens?: boolean - - /** - * @generated from field: optional bool stop_sequences = 7; - */ - stopSequences?: boolean - - /** - * @generated from field: optional bool system_message = 8; - */ - systemMessage?: boolean -} - -/** - * Describes the message registry.v1.ParameterSupport. - * Use `create(ParameterSupportSchema)` to create a new message. - */ -export const ParameterSupportSchema: GenMessage = /*@__PURE__*/ messageDesc(file_v1_model, 3) - -/** - * @generated from message registry.v1.ImagePrice - */ -export type ImagePrice = Message<'registry.v1.ImagePrice'> & { - /** - * @generated from field: double price = 1; - */ - price: number - - /** - * @generated from field: registry.v1.Currency currency = 2; - */ - currency: Currency - - /** - * @generated from field: registry.v1.ImagePriceUnit unit = 3; - */ - unit: ImagePriceUnit -} - -/** - * Describes the message registry.v1.ImagePrice. - * Use `create(ImagePriceSchema)` to create a new message. - */ -export const ImagePriceSchema: GenMessage = /*@__PURE__*/ messageDesc(file_v1_model, 4) - -/** - * @generated from message registry.v1.MinutePrice - */ -export type MinutePrice = Message<'registry.v1.MinutePrice'> & { - /** - * @generated from field: double price = 1; - */ - price: number - - /** - * @generated from field: registry.v1.Currency currency = 2; - */ - currency: Currency -} - -/** - * Describes the message registry.v1.MinutePrice. - * Use `create(MinutePriceSchema)` to create a new message. - */ -export const MinutePriceSchema: GenMessage = /*@__PURE__*/ messageDesc(file_v1_model, 5) - -/** - * @generated from message registry.v1.ModelPricing - */ -export type ModelPricing = Message<'registry.v1.ModelPricing'> & { - /** - * @generated from field: registry.v1.PricePerToken input = 1; - */ - input?: PricePerToken - - /** - * @generated from field: registry.v1.PricePerToken output = 2; - */ - output?: PricePerToken - - /** - * @generated from field: optional registry.v1.PricePerToken cache_read = 3; - */ - cacheRead?: PricePerToken - - /** - * @generated from field: optional registry.v1.PricePerToken cache_write = 4; - */ - cacheWrite?: PricePerToken - - /** - * @generated from field: optional registry.v1.ImagePrice per_image = 5; - */ - perImage?: ImagePrice - - /** - * @generated from field: optional registry.v1.MinutePrice per_minute = 6; - */ - perMinute?: MinutePrice -} - -/** - * Describes the message registry.v1.ModelPricing. - * Use `create(ModelPricingSchema)` to create a new message. - */ -export const ModelPricingSchema: GenMessage = /*@__PURE__*/ messageDesc(file_v1_model, 6) - -/** - * @generated from message registry.v1.ModelConfig - */ -export type ModelConfig = Message<'registry.v1.ModelConfig'> & { - /** - * @generated from field: string id = 1; - */ - id: string - - /** - * @generated from field: optional string name = 2; - */ - name?: string - - /** - * @generated from field: optional string description = 3; - */ - description?: string - - /** - * @generated from field: repeated registry.v1.ModelCapability capabilities = 4; - */ - capabilities: ModelCapability[] - - /** - * @generated from field: repeated registry.v1.Modality input_modalities = 5; - */ - inputModalities: Modality[] - - /** - * @generated from field: repeated registry.v1.Modality output_modalities = 6; - */ - outputModalities: Modality[] - - /** - * @generated from field: optional uint32 context_window = 7; - */ - contextWindow?: number - - /** - * @generated from field: optional uint32 max_output_tokens = 8; - */ - maxOutputTokens?: number - - /** - * @generated from field: optional uint32 max_input_tokens = 9; - */ - maxInputTokens?: number - - /** - * @generated from field: optional registry.v1.ModelPricing pricing = 10; - */ - pricing?: ModelPricing - - /** - * @generated from field: optional registry.v1.ReasoningSupport reasoning = 11; - */ - reasoning?: ReasoningSupport - - /** - * @generated from field: optional registry.v1.ParameterSupport parameter_support = 12; - */ - parameterSupport?: ParameterSupport - - /** - * @generated from field: optional string family = 13; - */ - family?: string - - /** - * @generated from field: optional string owned_by = 14; - */ - ownedBy?: string - - /** - * @generated from field: optional bool open_weights = 15; - */ - openWeights?: boolean - - /** - * @generated from field: repeated string alias = 16; - */ - alias: string[] - - /** - * @generated from field: optional registry.v1.Metadata metadata = 17; - */ - metadata?: Metadata -} - -/** - * Describes the message registry.v1.ModelConfig. - * Use `create(ModelConfigSchema)` to create a new message. - */ -export const ModelConfigSchema: GenMessage = /*@__PURE__*/ messageDesc(file_v1_model, 7) - -/** - * Top-level container - * - * @generated from message registry.v1.ModelRegistry - */ -export type ModelRegistry = Message<'registry.v1.ModelRegistry'> & { - /** - * @generated from field: string version = 1; - */ - version: string - - /** - * @generated from field: repeated registry.v1.ModelConfig models = 2; - */ - models: ModelConfig[] -} - -/** - * Describes the message registry.v1.ModelRegistry. - * Use `create(ModelRegistrySchema)` to create a new message. - */ -export const ModelRegistrySchema: GenMessage = /*@__PURE__*/ messageDesc(file_v1_model, 8) - -/** - * @generated from enum registry.v1.ImagePriceUnit - */ -export enum ImagePriceUnit { - /** - * @generated from enum value: IMAGE_PRICE_UNIT_UNSPECIFIED = 0; - */ - UNSPECIFIED = 0, - - /** - * @generated from enum value: IMAGE_PRICE_UNIT_IMAGE = 1; - */ - IMAGE = 1, - - /** - * @generated from enum value: IMAGE_PRICE_UNIT_PIXEL = 2; - */ - PIXEL = 2 -} - -/** - * Describes the enum registry.v1.ImagePriceUnit. - */ -export const ImagePriceUnitSchema: GenEnum = /*@__PURE__*/ enumDesc(file_v1_model, 0) diff --git a/packages/provider-registry/src/gen/v1/provider_models_pb.ts b/packages/provider-registry/src/gen/v1/provider_models_pb.ts deleted file mode 100644 index 863501ec0b0..00000000000 --- a/packages/provider-registry/src/gen/v1/provider_models_pb.ts +++ /dev/null @@ -1,203 +0,0 @@ -// @generated by protoc-gen-es v2.11.0 with parameter "target=ts" -// @generated from file v1/provider_models.proto (package registry.v1, syntax proto3) -/* eslint-disable */ - -import type { GenFile, GenMessage } from '@bufbuild/protobuf/codegenv2' -import { fileDesc, messageDesc } from '@bufbuild/protobuf/codegenv2' -import type { EndpointType, Modality, ModelCapability } from './common_pb' -import { file_v1_common } from './common_pb' -import type { ModelPricing, ParameterSupport, ReasoningSupport } from './model_pb' -import { file_v1_model } from './model_pb' -import type { Message } from '@bufbuild/protobuf' - -/** - * Describes the file v1/provider_models.proto. - */ -export const file_v1_provider_models: GenFile = - /*@__PURE__*/ - fileDesc( - 'Chh2MS9wcm92aWRlcl9tb2RlbHMucHJvdG8SC3JlZ2lzdHJ5LnYxIpoBChJDYXBhYmlsaXR5T3ZlcnJpZGUSKQoDYWRkGAEgAygOMhwucmVnaXN0cnkudjEuTW9kZWxDYXBhYmlsaXR5EiwKBnJlbW92ZRgCIAMoDjIcLnJlZ2lzdHJ5LnYxLk1vZGVsQ2FwYWJpbGl0eRIrCgVmb3JjZRgDIAMoDjIcLnJlZ2lzdHJ5LnYxLk1vZGVsQ2FwYWJpbGl0eSKnAQoLTW9kZWxMaW1pdHMSGwoOY29udGV4dF93aW5kb3cYASABKA1IAIgBARIeChFtYXhfb3V0cHV0X3Rva2VucxgCIAEoDUgBiAEBEh0KEG1heF9pbnB1dF90b2tlbnMYAyABKA1IAogBAUIRCg9fY29udGV4dF93aW5kb3dCFAoSX21heF9vdXRwdXRfdG9rZW5zQhMKEV9tYXhfaW5wdXRfdG9rZW5zIo4GChVQcm92aWRlck1vZGVsT3ZlcnJpZGUSEwoLcHJvdmlkZXJfaWQYASABKAkSEAoIbW9kZWxfaWQYAiABKAkSGQoMYXBpX21vZGVsX2lkGAMgASgJSACIAQESGgoNbW9kZWxfdmFyaWFudBgEIAEoCUgBiAEBEjoKDGNhcGFiaWxpdGllcxgFIAEoCzIfLnJlZ2lzdHJ5LnYxLkNhcGFiaWxpdHlPdmVycmlkZUgCiAEBEi0KBmxpbWl0cxgGIAEoCzIYLnJlZ2lzdHJ5LnYxLk1vZGVsTGltaXRzSAOIAQESLwoHcHJpY2luZxgHIAEoCzIZLnJlZ2lzdHJ5LnYxLk1vZGVsUHJpY2luZ0gEiAEBEjUKCXJlYXNvbmluZxgIIAEoCzIdLnJlZ2lzdHJ5LnYxLlJlYXNvbmluZ1N1cHBvcnRIBYgBARI9ChFwYXJhbWV0ZXJfc3VwcG9ydBgJIAEoCzIdLnJlZ2lzdHJ5LnYxLlBhcmFtZXRlclN1cHBvcnRIBogBARIxCg5lbmRwb2ludF90eXBlcxgKIAMoDjIZLnJlZ2lzdHJ5LnYxLkVuZHBvaW50VHlwZRIvChBpbnB1dF9tb2RhbGl0aWVzGAsgAygOMhUucmVnaXN0cnkudjEuTW9kYWxpdHkSMAoRb3V0cHV0X21vZGFsaXRpZXMYDCADKA4yFS5yZWdpc3RyeS52MS5Nb2RhbGl0eRIVCghkaXNhYmxlZBgNIAEoCEgHiAEBEhkKDHJlcGxhY2Vfd2l0aBgOIAEoCUgIiAEBEhMKBnJlYXNvbhgPIAEoCUgJiAEBEhAKCHByaW9yaXR5GBAgASgNQg8KDV9hcGlfbW9kZWxfaWRCEAoOX21vZGVsX3ZhcmlhbnRCDwoNX2NhcGFiaWxpdGllc0IJCgdfbGltaXRzQgoKCF9wcmljaW5nQgwKCl9yZWFzb25pbmdCFAoSX3BhcmFtZXRlcl9zdXBwb3J0QgsKCV9kaXNhYmxlZEIPCg1fcmVwbGFjZV93aXRoQgkKB19yZWFzb24iXwoVUHJvdmlkZXJNb2RlbFJlZ2lzdHJ5Eg8KB3ZlcnNpb24YASABKAkSNQoJb3ZlcnJpZGVzGAIgAygLMiIucmVnaXN0cnkudjEuUHJvdmlkZXJNb2RlbE92ZXJyaWRlYgZwcm90bzM', - [file_v1_common, file_v1_model] - ) - -/** - * @generated from message registry.v1.CapabilityOverride - */ -export type CapabilityOverride = Message<'registry.v1.CapabilityOverride'> & { - /** - * @generated from field: repeated registry.v1.ModelCapability add = 1; - */ - add: ModelCapability[] - - /** - * @generated from field: repeated registry.v1.ModelCapability remove = 2; - */ - remove: ModelCapability[] - - /** - * @generated from field: repeated registry.v1.ModelCapability force = 3; - */ - force: ModelCapability[] -} - -/** - * Describes the message registry.v1.CapabilityOverride. - * Use `create(CapabilityOverrideSchema)` to create a new message. - */ -export const CapabilityOverrideSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_v1_provider_models, 0) - -/** - * @generated from message registry.v1.ModelLimits - */ -export type ModelLimits = Message<'registry.v1.ModelLimits'> & { - /** - * @generated from field: optional uint32 context_window = 1; - */ - contextWindow?: number - - /** - * @generated from field: optional uint32 max_output_tokens = 2; - */ - maxOutputTokens?: number - - /** - * @generated from field: optional uint32 max_input_tokens = 3; - */ - maxInputTokens?: number -} - -/** - * Describes the message registry.v1.ModelLimits. - * Use `create(ModelLimitsSchema)` to create a new message. - */ -export const ModelLimitsSchema: GenMessage = /*@__PURE__*/ messageDesc(file_v1_provider_models, 1) - -/** - * @generated from message registry.v1.ProviderModelOverride - */ -export type ProviderModelOverride = Message<'registry.v1.ProviderModelOverride'> & { - /** - * Identification - * - * @generated from field: string provider_id = 1; - */ - providerId: string - - /** - * @generated from field: string model_id = 2; - */ - modelId: string - - /** - * @generated from field: optional string api_model_id = 3; - */ - apiModelId?: string - - /** - * @generated from field: optional string model_variant = 4; - */ - modelVariant?: string - - /** - * Overrides - * - * @generated from field: optional registry.v1.CapabilityOverride capabilities = 5; - */ - capabilities?: CapabilityOverride - - /** - * @generated from field: optional registry.v1.ModelLimits limits = 6; - */ - limits?: ModelLimits - - /** - * @generated from field: optional registry.v1.ModelPricing pricing = 7; - */ - pricing?: ModelPricing - - /** - * @generated from field: optional registry.v1.ReasoningSupport reasoning = 8; - */ - reasoning?: ReasoningSupport - - /** - * @generated from field: optional registry.v1.ParameterSupport parameter_support = 9; - */ - parameterSupport?: ParameterSupport - - /** - * @generated from field: repeated registry.v1.EndpointType endpoint_types = 10; - */ - endpointTypes: EndpointType[] - - /** - * @generated from field: repeated registry.v1.Modality input_modalities = 11; - */ - inputModalities: Modality[] - - /** - * @generated from field: repeated registry.v1.Modality output_modalities = 12; - */ - outputModalities: Modality[] - - /** - * Status - * - * @generated from field: optional bool disabled = 13; - */ - disabled?: boolean - - /** - * @generated from field: optional string replace_with = 14; - */ - replaceWith?: string - - /** - * Metadata - * - * @generated from field: optional string reason = 15; - */ - reason?: string - - /** - * 0 = auto, 100+ = manual - * - * @generated from field: uint32 priority = 16; - */ - priority: number -} - -/** - * Describes the message registry.v1.ProviderModelOverride. - * Use `create(ProviderModelOverrideSchema)` to create a new message. - */ -export const ProviderModelOverrideSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_v1_provider_models, 2) - -/** - * Top-level container - * - * @generated from message registry.v1.ProviderModelRegistry - */ -export type ProviderModelRegistry = Message<'registry.v1.ProviderModelRegistry'> & { - /** - * @generated from field: string version = 1; - */ - version: string - - /** - * @generated from field: repeated registry.v1.ProviderModelOverride overrides = 2; - */ - overrides: ProviderModelOverride[] -} - -/** - * Describes the message registry.v1.ProviderModelRegistry. - * Use `create(ProviderModelRegistrySchema)` to create a new message. - */ -export const ProviderModelRegistrySchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_v1_provider_models, 3) diff --git a/packages/provider-registry/src/gen/v1/provider_pb.ts b/packages/provider-registry/src/gen/v1/provider_pb.ts deleted file mode 100644 index a2e3c514d97..00000000000 --- a/packages/provider-registry/src/gen/v1/provider_pb.ts +++ /dev/null @@ -1,707 +0,0 @@ -// @generated by protoc-gen-es v2.11.0 with parameter "target=ts" -// @generated from file v1/provider.proto (package registry.v1, syntax proto3) -/* eslint-disable */ - -import type { GenEnum, GenFile, GenMessage } from '@bufbuild/protobuf/codegenv2' -import { enumDesc, fileDesc, messageDesc } from '@bufbuild/protobuf/codegenv2' -import type { AnthropicReasoningEffort, EndpointType, OpenAIReasoningEffort } from './common_pb' -import { file_v1_common } from './common_pb' -import type { Message } from '@bufbuild/protobuf' - -/** - * Describes the file v1/provider.proto. - */ -export const file_v1_provider: GenFile = - /*@__PURE__*/ - fileDesc( - 'ChF2MS9wcm92aWRlci5wcm90bxILcmVnaXN0cnkudjEinwIKC0FwaUZlYXR1cmVzEhoKDWFycmF5X2NvbnRlbnQYASABKAhIAIgBARIbCg5zdHJlYW1fb3B0aW9ucxgCIAEoCEgBiAEBEhsKDmRldmVsb3Blcl9yb2xlGAMgASgISAKIAQESGQoMc2VydmljZV90aWVyGAQgASgISAOIAQESFgoJdmVyYm9zaXR5GAUgASgISASIAQESHAoPZW5hYmxlX3RoaW5raW5nGAYgASgISAWIAQFCEAoOX2FycmF5X2NvbnRlbnRCEQoPX3N0cmVhbV9vcHRpb25zQhEKD19kZXZlbG9wZXJfcm9sZUIPCg1fc2VydmljZV90aWVyQgwKCl92ZXJib3NpdHlCEgoQX2VuYWJsZV90aGlua2luZyJzChlPcGVuQUlDaGF0UmVhc29uaW5nRm9ybWF0EkEKEHJlYXNvbmluZ19lZmZvcnQYASABKA4yIi5yZWdpc3RyeS52MS5PcGVuQUlSZWFzb25pbmdFZmZvcnRIAIgBAUITChFfcmVhc29uaW5nX2VmZm9ydCKpAQoeT3BlbkFJUmVzcG9uc2VzUmVhc29uaW5nRm9ybWF0EjcKBmVmZm9ydBgBIAEoDjIiLnJlZ2lzdHJ5LnYxLk9wZW5BSVJlYXNvbmluZ0VmZm9ydEgAiAEBEjcKB3N1bW1hcnkYAiABKA4yIS5yZWdpc3RyeS52MS5SZXNwb25zZXNTdW1tYXJ5TW9kZUgBiAEBQgkKB19lZmZvcnRCCgoIX3N1bW1hcnkizwEKGEFudGhyb3BpY1JlYXNvbmluZ0Zvcm1hdBI1CgR0eXBlGAEgASgOMiIucmVnaXN0cnkudjEuQW50aHJvcGljVGhpbmtpbmdUeXBlSACIAQESGgoNYnVkZ2V0X3Rva2VucxgCIAEoDUgBiAEBEjoKBmVmZm9ydBgDIAEoDjIlLnJlZ2lzdHJ5LnYxLkFudGhyb3BpY1JlYXNvbmluZ0VmZm9ydEgCiAEBQgcKBV90eXBlQhAKDl9idWRnZXRfdG9rZW5zQgkKB19lZmZvcnQifAoUR2VtaW5pVGhpbmtpbmdDb25maWcSHQoQaW5jbHVkZV90aG91Z2h0cxgBIAEoCEgAiAEBEhwKD3RoaW5raW5nX2J1ZGdldBgCIAEoDUgBiAEBQhMKEV9pbmNsdWRlX3Rob3VnaHRzQhIKEF90aGlua2luZ19idWRnZXQimwEKFUdlbWluaVJlYXNvbmluZ0Zvcm1hdBI8Cg90aGlua2luZ19jb25maWcYASABKAsyIS5yZWdpc3RyeS52MS5HZW1pbmlUaGlua2luZ0NvbmZpZ0gAEjoKDnRoaW5raW5nX2xldmVsGAIgASgOMiAucmVnaXN0cnkudjEuR2VtaW5pVGhpbmtpbmdMZXZlbEgAQggKBmNvbmZpZyKpAQoZT3BlblJvdXRlclJlYXNvbmluZ0Zvcm1hdBI3CgZlZmZvcnQYASABKA4yIi5yZWdpc3RyeS52MS5PcGVuQUlSZWFzb25pbmdFZmZvcnRIAIgBARIXCgptYXhfdG9rZW5zGAIgASgNSAGIAQESFAoHZXhjbHVkZRgDIAEoCEgCiAEBQgkKB19lZmZvcnRCDQoLX21heF90b2tlbnNCCgoIX2V4Y2x1ZGUigwEKHUVuYWJsZVRoaW5raW5nUmVhc29uaW5nRm9ybWF0EhwKD2VuYWJsZV90aGlua2luZxgBIAEoCEgAiAEBEhwKD3RoaW5raW5nX2J1ZGdldBgCIAEoDUgBiAEBQhIKEF9lbmFibGVfdGhpbmtpbmdCEgoQX3RoaW5raW5nX2J1ZGdldCJmChtUaGlua2luZ1R5cGVSZWFzb25pbmdGb3JtYXQSNQoNdGhpbmtpbmdfdHlwZRgBIAEoDjIZLnJlZ2lzdHJ5LnYxLlRoaW5raW5nVHlwZUgAiAEBQhAKDl90aGlua2luZ190eXBlIoQBChhEYXNoc2NvcGVSZWFzb25pbmdGb3JtYXQSHAoPZW5hYmxlX3RoaW5raW5nGAEgASgISACIAQESHwoSaW5jcmVtZW50YWxfb3V0cHV0GAIgASgISAGIAQFCEgoQX2VuYWJsZV90aGlua2luZ0IVChNfaW5jcmVtZW50YWxfb3V0cHV0InEKGVNlbGZIb3N0ZWRSZWFzb25pbmdGb3JtYXQSHAoPZW5hYmxlX3RoaW5raW5nGAEgASgISACIAQESFQoIdGhpbmtpbmcYAiABKAhIAYgBAUISChBfZW5hYmxlX3RoaW5raW5nQgsKCV90aGlua2luZyLgBAoXUHJvdmlkZXJSZWFzb25pbmdGb3JtYXQSPQoLb3BlbmFpX2NoYXQYASABKAsyJi5yZWdpc3RyeS52MS5PcGVuQUlDaGF0UmVhc29uaW5nRm9ybWF0SAASRwoQb3BlbmFpX3Jlc3BvbnNlcxgCIAEoCzIrLnJlZ2lzdHJ5LnYxLk9wZW5BSVJlc3BvbnNlc1JlYXNvbmluZ0Zvcm1hdEgAEjoKCWFudGhyb3BpYxgDIAEoCzIlLnJlZ2lzdHJ5LnYxLkFudGhyb3BpY1JlYXNvbmluZ0Zvcm1hdEgAEjQKBmdlbWluaRgEIAEoCzIiLnJlZ2lzdHJ5LnYxLkdlbWluaVJlYXNvbmluZ0Zvcm1hdEgAEjwKCm9wZW5yb3V0ZXIYBSABKAsyJi5yZWdpc3RyeS52MS5PcGVuUm91dGVyUmVhc29uaW5nRm9ybWF0SAASRQoPZW5hYmxlX3RoaW5raW5nGAYgASgLMioucmVnaXN0cnkudjEuRW5hYmxlVGhpbmtpbmdSZWFzb25pbmdGb3JtYXRIABJBCg10aGlua2luZ190eXBlGAcgASgLMigucmVnaXN0cnkudjEuVGhpbmtpbmdUeXBlUmVhc29uaW5nRm9ybWF0SAASOgoJZGFzaHNjb3BlGAggASgLMiUucmVnaXN0cnkudjEuRGFzaHNjb3BlUmVhc29uaW5nRm9ybWF0SAASPQoLc2VsZl9ob3N0ZWQYCSABKAsyJi5yZWdpc3RyeS52MS5TZWxmSG9zdGVkUmVhc29uaW5nRm9ybWF0SABCCAoGZm9ybWF0IpMBCg9Qcm92aWRlcldlYnNpdGUSFQoIb2ZmaWNpYWwYASABKAlIAIgBARIRCgRkb2NzGAIgASgJSAGIAQESFAoHYXBpX2tleRgDIAEoCUgCiAEBEhMKBm1vZGVscxgEIAEoCUgDiAEBQgsKCV9vZmZpY2lhbEIHCgVfZG9jc0IKCghfYXBpX2tleUIJCgdfbW9kZWxzInsKDU1vZGVsc0FwaVVybHMSFAoHZGVmYXVsdBgBIAEoCUgAiAEBEhYKCWVtYmVkZGluZxgCIAEoCUgBiAEBEhUKCHJlcmFua2VyGAMgASgJSAKIAQFCCgoIX2RlZmF1bHRCDAoKX2VtYmVkZGluZ0ILCglfcmVyYW5rZXIiUgoQUHJvdmlkZXJNZXRhZGF0YRIyCgd3ZWJzaXRlGAEgASgLMhwucmVnaXN0cnkudjEuUHJvdmlkZXJXZWJzaXRlSACIAQFCCgoIX3dlYnNpdGUi3AEKDkVuZHBvaW50Q29uZmlnEhUKCGJhc2VfdXJsGAEgASgJSACIAQESOAoPbW9kZWxzX2FwaV91cmxzGAIgASgLMhoucmVnaXN0cnkudjEuTW9kZWxzQXBpVXJsc0gBiAEBEkMKEHJlYXNvbmluZ19mb3JtYXQYAyABKAsyJC5yZWdpc3RyeS52MS5Qcm92aWRlclJlYXNvbmluZ0Zvcm1hdEgCiAEBQgsKCV9iYXNlX3VybEISChBfbW9kZWxzX2FwaV91cmxzQhMKEV9yZWFzb25pbmdfZm9ybWF0ItcDCg5Qcm92aWRlckNvbmZpZxIKCgJpZBgBIAEoCRIMCgRuYW1lGAIgASgJEhgKC2Rlc2NyaXB0aW9uGAMgASgJSACIAQESSgoQZW5kcG9pbnRfY29uZmlncxgKIAMoCzIwLnJlZ2lzdHJ5LnYxLlByb3ZpZGVyQ29uZmlnLkVuZHBvaW50Q29uZmlnc0VudHJ5Ej0KFWRlZmF1bHRfY2hhdF9lbmRwb2ludBgFIAEoDjIZLnJlZ2lzdHJ5LnYxLkVuZHBvaW50VHlwZUgBiAEBEjMKDGFwaV9mZWF0dXJlcxgGIAEoCzIYLnJlZ2lzdHJ5LnYxLkFwaUZlYXR1cmVzSAKIAQESNAoIbWV0YWRhdGEYCCABKAsyHS5yZWdpc3RyeS52MS5Qcm92aWRlck1ldGFkYXRhSAOIAQEaUwoURW5kcG9pbnRDb25maWdzRW50cnkSCwoDa2V5GAEgASgFEioKBXZhbHVlGAIgASgLMhsucmVnaXN0cnkudjEuRW5kcG9pbnRDb25maWc6AjgBQg4KDF9kZXNjcmlwdGlvbkIYChZfZGVmYXVsdF9jaGF0X2VuZHBvaW50Qg8KDV9hcGlfZmVhdHVyZXNCCwoJX21ldGFkYXRhIlMKEFByb3ZpZGVyUmVnaXN0cnkSDwoHdmVyc2lvbhgBIAEoCRIuCglwcm92aWRlcnMYAiADKAsyGy5yZWdpc3RyeS52MS5Qcm92aWRlckNvbmZpZyqoAQoUUmVzcG9uc2VzU3VtbWFyeU1vZGUSJgoiUkVTUE9OU0VTX1NVTU1BUllfTU9ERV9VTlNQRUNJRklFRBAAEh8KG1JFU1BPTlNFU19TVU1NQVJZX01PREVfQVVUTxABEiIKHlJFU1BPTlNFU19TVU1NQVJZX01PREVfQ09OQ0lTRRACEiMKH1JFU1BPTlNFU19TVU1NQVJZX01PREVfREVUQUlMRUQQAyqxAQoVQW50aHJvcGljVGhpbmtpbmdUeXBlEicKI0FOVEhST1BJQ19USElOS0lOR19UWVBFX1VOU1BFQ0lGSUVEEAASIwofQU5USFJPUElDX1RISU5LSU5HX1RZUEVfRU5BQkxFRBABEiQKIEFOVEhST1BJQ19USElOS0lOR19UWVBFX0RJU0FCTEVEEAISJAogQU5USFJPUElDX1RISU5LSU5HX1RZUEVfQURBUFRJVkUQAyrAAQoTR2VtaW5pVGhpbmtpbmdMZXZlbBIlCiFHRU1JTklfVEhJTktJTkdfTEVWRUxfVU5TUEVDSUZJRUQQABIhCh1HRU1JTklfVEhJTktJTkdfTEVWRUxfTUlOSU1BTBABEh0KGUdFTUlOSV9USElOS0lOR19MRVZFTF9MT1cQAhIgChxHRU1JTklfVEhJTktJTkdfTEVWRUxfTUVESVVNEAMSHgoaR0VNSU5JX1RISU5LSU5HX0xFVkVMX0hJR0gQBCp8CgxUaGlua2luZ1R5cGUSHQoZVEhJTktJTkdfVFlQRV9VTlNQRUNJRklFRBAAEhkKFVRISU5LSU5HX1RZUEVfRU5BQkxFRBABEhoKFlRISU5LSU5HX1RZUEVfRElTQUJMRUQQAhIWChJUSElOS0lOR19UWVBFX0FVVE8QA2IGcHJvdG8z', - [file_v1_common] - ) - -/** - * --- Request format flags --- - * - * @generated from message registry.v1.ApiFeatures - */ -export type ApiFeatures = Message<'registry.v1.ApiFeatures'> & { - /** - * Whether the provider supports array-formatted content in messages - * - * @generated from field: optional bool array_content = 1; - */ - arrayContent?: boolean - - /** - * Whether the provider supports stream_options for usage data - * - * @generated from field: optional bool stream_options = 2; - */ - streamOptions?: boolean - - /** - * Whether the provider supports the 'developer' role (OpenAI-specific) - * - * @generated from field: optional bool developer_role = 3; - */ - developerRole?: boolean - - /** - * Whether the provider supports service tier selection (OpenAI/Groq-specific) - * - * @generated from field: optional bool service_tier = 4; - */ - serviceTier?: boolean - - /** - * Whether the provider supports verbosity settings (Gemini-specific) - * - * @generated from field: optional bool verbosity = 5; - */ - verbosity?: boolean - - /** - * Whether the provider supports enable_thinking parameter format - * - * @generated from field: optional bool enable_thinking = 6; - */ - enableThinking?: boolean -} - -/** - * Describes the message registry.v1.ApiFeatures. - * Use `create(ApiFeaturesSchema)` to create a new message. - */ -export const ApiFeaturesSchema: GenMessage = /*@__PURE__*/ messageDesc(file_v1_provider, 0) - -/** - * @generated from message registry.v1.OpenAIChatReasoningFormat - */ -export type OpenAIChatReasoningFormat = Message<'registry.v1.OpenAIChatReasoningFormat'> & { - /** - * @generated from field: optional registry.v1.OpenAIReasoningEffort reasoning_effort = 1; - */ - reasoningEffort?: OpenAIReasoningEffort -} - -/** - * Describes the message registry.v1.OpenAIChatReasoningFormat. - * Use `create(OpenAIChatReasoningFormatSchema)` to create a new message. - */ -export const OpenAIChatReasoningFormatSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_v1_provider, 1) - -/** - * @generated from message registry.v1.OpenAIResponsesReasoningFormat - */ -export type OpenAIResponsesReasoningFormat = Message<'registry.v1.OpenAIResponsesReasoningFormat'> & { - /** - * @generated from field: optional registry.v1.OpenAIReasoningEffort effort = 1; - */ - effort?: OpenAIReasoningEffort - - /** - * @generated from field: optional registry.v1.ResponsesSummaryMode summary = 2; - */ - summary?: ResponsesSummaryMode -} - -/** - * Describes the message registry.v1.OpenAIResponsesReasoningFormat. - * Use `create(OpenAIResponsesReasoningFormatSchema)` to create a new message. - */ -export const OpenAIResponsesReasoningFormatSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_v1_provider, 2) - -/** - * @generated from message registry.v1.AnthropicReasoningFormat - */ -export type AnthropicReasoningFormat = Message<'registry.v1.AnthropicReasoningFormat'> & { - /** - * @generated from field: optional registry.v1.AnthropicThinkingType type = 1; - */ - type?: AnthropicThinkingType - - /** - * @generated from field: optional uint32 budget_tokens = 2; - */ - budgetTokens?: number - - /** - * @generated from field: optional registry.v1.AnthropicReasoningEffort effort = 3; - */ - effort?: AnthropicReasoningEffort -} - -/** - * Describes the message registry.v1.AnthropicReasoningFormat. - * Use `create(AnthropicReasoningFormatSchema)` to create a new message. - */ -export const AnthropicReasoningFormatSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_v1_provider, 3) - -/** - * @generated from message registry.v1.GeminiThinkingConfig - */ -export type GeminiThinkingConfig = Message<'registry.v1.GeminiThinkingConfig'> & { - /** - * @generated from field: optional bool include_thoughts = 1; - */ - includeThoughts?: boolean - - /** - * @generated from field: optional uint32 thinking_budget = 2; - */ - thinkingBudget?: number -} - -/** - * Describes the message registry.v1.GeminiThinkingConfig. - * Use `create(GeminiThinkingConfigSchema)` to create a new message. - */ -export const GeminiThinkingConfigSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_v1_provider, 4) - -/** - * @generated from message registry.v1.GeminiReasoningFormat - */ -export type GeminiReasoningFormat = Message<'registry.v1.GeminiReasoningFormat'> & { - /** - * @generated from oneof registry.v1.GeminiReasoningFormat.config - */ - config: - | { - /** - * @generated from field: registry.v1.GeminiThinkingConfig thinking_config = 1; - */ - value: GeminiThinkingConfig - case: 'thinkingConfig' - } - | { - /** - * @generated from field: registry.v1.GeminiThinkingLevel thinking_level = 2; - */ - value: GeminiThinkingLevel - case: 'thinkingLevel' - } - | { case: undefined; value?: undefined } -} - -/** - * Describes the message registry.v1.GeminiReasoningFormat. - * Use `create(GeminiReasoningFormatSchema)` to create a new message. - */ -export const GeminiReasoningFormatSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_v1_provider, 5) - -/** - * @generated from message registry.v1.OpenRouterReasoningFormat - */ -export type OpenRouterReasoningFormat = Message<'registry.v1.OpenRouterReasoningFormat'> & { - /** - * @generated from field: optional registry.v1.OpenAIReasoningEffort effort = 1; - */ - effort?: OpenAIReasoningEffort - - /** - * @generated from field: optional uint32 max_tokens = 2; - */ - maxTokens?: number - - /** - * @generated from field: optional bool exclude = 3; - */ - exclude?: boolean -} - -/** - * Describes the message registry.v1.OpenRouterReasoningFormat. - * Use `create(OpenRouterReasoningFormatSchema)` to create a new message. - */ -export const OpenRouterReasoningFormatSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_v1_provider, 6) - -/** - * enable_thinking + thinking_budget format (Qwen, Hunyuan, Silicon, etc.) - * - * @generated from message registry.v1.EnableThinkingReasoningFormat - */ -export type EnableThinkingReasoningFormat = Message<'registry.v1.EnableThinkingReasoningFormat'> & { - /** - * @generated from field: optional bool enable_thinking = 1; - */ - enableThinking?: boolean - - /** - * @generated from field: optional uint32 thinking_budget = 2; - */ - thinkingBudget?: number -} - -/** - * Describes the message registry.v1.EnableThinkingReasoningFormat. - * Use `create(EnableThinkingReasoningFormatSchema)` to create a new message. - */ -export const EnableThinkingReasoningFormatSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_v1_provider, 7) - -/** - * thinking: { type } format (Doubao, Zhipu, DeepSeek, many aggregators) - * - * @generated from message registry.v1.ThinkingTypeReasoningFormat - */ -export type ThinkingTypeReasoningFormat = Message<'registry.v1.ThinkingTypeReasoningFormat'> & { - /** - * @generated from field: optional registry.v1.ThinkingType thinking_type = 1; - */ - thinkingType?: ThinkingType -} - -/** - * Describes the message registry.v1.ThinkingTypeReasoningFormat. - * Use `create(ThinkingTypeReasoningFormatSchema)` to create a new message. - */ -export const ThinkingTypeReasoningFormatSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_v1_provider, 8) - -/** - * DashScope-specific: enable_thinking + incremental_output - * - * @generated from message registry.v1.DashscopeReasoningFormat - */ -export type DashscopeReasoningFormat = Message<'registry.v1.DashscopeReasoningFormat'> & { - /** - * @generated from field: optional bool enable_thinking = 1; - */ - enableThinking?: boolean - - /** - * @generated from field: optional bool incremental_output = 2; - */ - incrementalOutput?: boolean -} - -/** - * Describes the message registry.v1.DashscopeReasoningFormat. - * Use `create(DashscopeReasoningFormatSchema)` to create a new message. - */ -export const DashscopeReasoningFormatSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_v1_provider, 9) - -/** - * Self-hosted/Nvidia: chat_template_kwargs - * - * @generated from message registry.v1.SelfHostedReasoningFormat - */ -export type SelfHostedReasoningFormat = Message<'registry.v1.SelfHostedReasoningFormat'> & { - /** - * @generated from field: optional bool enable_thinking = 1; - */ - enableThinking?: boolean - - /** - * @generated from field: optional bool thinking = 2; - */ - thinking?: boolean -} - -/** - * Describes the message registry.v1.SelfHostedReasoningFormat. - * Use `create(SelfHostedReasoningFormatSchema)` to create a new message. - */ -export const SelfHostedReasoningFormatSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_v1_provider, 10) - -/** - * Discriminated union: which API parameter shape this provider uses for reasoning - * - * @generated from message registry.v1.ProviderReasoningFormat - */ -export type ProviderReasoningFormat = Message<'registry.v1.ProviderReasoningFormat'> & { - /** - * @generated from oneof registry.v1.ProviderReasoningFormat.format - */ - format: - | { - /** - * @generated from field: registry.v1.OpenAIChatReasoningFormat openai_chat = 1; - */ - value: OpenAIChatReasoningFormat - case: 'openaiChat' - } - | { - /** - * @generated from field: registry.v1.OpenAIResponsesReasoningFormat openai_responses = 2; - */ - value: OpenAIResponsesReasoningFormat - case: 'openaiResponses' - } - | { - /** - * @generated from field: registry.v1.AnthropicReasoningFormat anthropic = 3; - */ - value: AnthropicReasoningFormat - case: 'anthropic' - } - | { - /** - * @generated from field: registry.v1.GeminiReasoningFormat gemini = 4; - */ - value: GeminiReasoningFormat - case: 'gemini' - } - | { - /** - * @generated from field: registry.v1.OpenRouterReasoningFormat openrouter = 5; - */ - value: OpenRouterReasoningFormat - case: 'openrouter' - } - | { - /** - * @generated from field: registry.v1.EnableThinkingReasoningFormat enable_thinking = 6; - */ - value: EnableThinkingReasoningFormat - case: 'enableThinking' - } - | { - /** - * @generated from field: registry.v1.ThinkingTypeReasoningFormat thinking_type = 7; - */ - value: ThinkingTypeReasoningFormat - case: 'thinkingType' - } - | { - /** - * @generated from field: registry.v1.DashscopeReasoningFormat dashscope = 8; - */ - value: DashscopeReasoningFormat - case: 'dashscope' - } - | { - /** - * @generated from field: registry.v1.SelfHostedReasoningFormat self_hosted = 9; - */ - value: SelfHostedReasoningFormat - case: 'selfHosted' - } - | { case: undefined; value?: undefined } -} - -/** - * Describes the message registry.v1.ProviderReasoningFormat. - * Use `create(ProviderReasoningFormatSchema)` to create a new message. - */ -export const ProviderReasoningFormatSchema: GenMessage = - /*@__PURE__*/ - messageDesc(file_v1_provider, 11) - -/** - * @generated from message registry.v1.ProviderWebsite - */ -export type ProviderWebsite = Message<'registry.v1.ProviderWebsite'> & { - /** - * @generated from field: optional string official = 1; - */ - official?: string - - /** - * @generated from field: optional string docs = 2; - */ - docs?: string - - /** - * @generated from field: optional string api_key = 3; - */ - apiKey?: string - - /** - * @generated from field: optional string models = 4; - */ - models?: string -} - -/** - * Describes the message registry.v1.ProviderWebsite. - * Use `create(ProviderWebsiteSchema)` to create a new message. - */ -export const ProviderWebsiteSchema: GenMessage = /*@__PURE__*/ messageDesc(file_v1_provider, 12) - -/** - * @generated from message registry.v1.ModelsApiUrls - */ -export type ModelsApiUrls = Message<'registry.v1.ModelsApiUrls'> & { - /** - * @generated from field: optional string default = 1; - */ - default?: string - - /** - * @generated from field: optional string embedding = 2; - */ - embedding?: string - - /** - * @generated from field: optional string reranker = 3; - */ - reranker?: string -} - -/** - * Describes the message registry.v1.ModelsApiUrls. - * Use `create(ModelsApiUrlsSchema)` to create a new message. - */ -export const ModelsApiUrlsSchema: GenMessage = /*@__PURE__*/ messageDesc(file_v1_provider, 13) - -/** - * @generated from message registry.v1.ProviderMetadata - */ -export type ProviderMetadata = Message<'registry.v1.ProviderMetadata'> & { - /** - * @generated from field: optional registry.v1.ProviderWebsite website = 1; - */ - website?: ProviderWebsite -} - -/** - * Describes the message registry.v1.ProviderMetadata. - * Use `create(ProviderMetadataSchema)` to create a new message. - */ -export const ProviderMetadataSchema: GenMessage = /*@__PURE__*/ messageDesc(file_v1_provider, 14) - -/** - * @generated from message registry.v1.EndpointConfig - */ -export type EndpointConfig = Message<'registry.v1.EndpointConfig'> & { - /** - * Base URL for this endpoint type's API - * - * @generated from field: optional string base_url = 1; - */ - baseUrl?: string - - /** - * URLs for fetching available models via this endpoint type - * - * @generated from field: optional registry.v1.ModelsApiUrls models_api_urls = 2; - */ - modelsApiUrls?: ModelsApiUrls - - /** - * How this endpoint type expects reasoning parameters to be formatted - * - * @generated from field: optional registry.v1.ProviderReasoningFormat reasoning_format = 3; - */ - reasoningFormat?: ProviderReasoningFormat -} - -/** - * Describes the message registry.v1.EndpointConfig. - * Use `create(EndpointConfigSchema)` to create a new message. - */ -export const EndpointConfigSchema: GenMessage = /*@__PURE__*/ messageDesc(file_v1_provider, 15) - -/** - * @generated from message registry.v1.ProviderConfig - */ -export type ProviderConfig = Message<'registry.v1.ProviderConfig'> & { - /** - * @generated from field: string id = 1; - */ - id: string - - /** - * @generated from field: string name = 2; - */ - name: string - - /** - * @generated from field: optional string description = 3; - */ - description?: string - - /** - * Per-endpoint-type configuration (key = EndpointType enum value) - * - * @generated from field: map endpoint_configs = 10; - */ - endpointConfigs: { [key: number]: EndpointConfig } - - /** - * @generated from field: optional registry.v1.EndpointType default_chat_endpoint = 5; - */ - defaultChatEndpoint?: EndpointType - - /** - * @generated from field: optional registry.v1.ApiFeatures api_features = 6; - */ - apiFeatures?: ApiFeatures - - /** - * @generated from field: optional registry.v1.ProviderMetadata metadata = 8; - */ - metadata?: ProviderMetadata -} - -/** - * Describes the message registry.v1.ProviderConfig. - * Use `create(ProviderConfigSchema)` to create a new message. - */ -export const ProviderConfigSchema: GenMessage = /*@__PURE__*/ messageDesc(file_v1_provider, 16) - -/** - * Top-level container - * - * @generated from message registry.v1.ProviderRegistry - */ -export type ProviderRegistry = Message<'registry.v1.ProviderRegistry'> & { - /** - * @generated from field: string version = 1; - */ - version: string - - /** - * @generated from field: repeated registry.v1.ProviderConfig providers = 2; - */ - providers: ProviderConfig[] -} - -/** - * Describes the message registry.v1.ProviderRegistry. - * Use `create(ProviderRegistrySchema)` to create a new message. - */ -export const ProviderRegistrySchema: GenMessage = /*@__PURE__*/ messageDesc(file_v1_provider, 17) - -/** - * @generated from enum registry.v1.ResponsesSummaryMode - */ -export enum ResponsesSummaryMode { - /** - * @generated from enum value: RESPONSES_SUMMARY_MODE_UNSPECIFIED = 0; - */ - UNSPECIFIED = 0, - - /** - * @generated from enum value: RESPONSES_SUMMARY_MODE_AUTO = 1; - */ - AUTO = 1, - - /** - * @generated from enum value: RESPONSES_SUMMARY_MODE_CONCISE = 2; - */ - CONCISE = 2, - - /** - * @generated from enum value: RESPONSES_SUMMARY_MODE_DETAILED = 3; - */ - DETAILED = 3 -} - -/** - * Describes the enum registry.v1.ResponsesSummaryMode. - */ -export const ResponsesSummaryModeSchema: GenEnum = /*@__PURE__*/ enumDesc(file_v1_provider, 0) - -/** - * @generated from enum registry.v1.AnthropicThinkingType - */ -export enum AnthropicThinkingType { - /** - * @generated from enum value: ANTHROPIC_THINKING_TYPE_UNSPECIFIED = 0; - */ - UNSPECIFIED = 0, - - /** - * @generated from enum value: ANTHROPIC_THINKING_TYPE_ENABLED = 1; - */ - ENABLED = 1, - - /** - * @generated from enum value: ANTHROPIC_THINKING_TYPE_DISABLED = 2; - */ - DISABLED = 2, - - /** - * @generated from enum value: ANTHROPIC_THINKING_TYPE_ADAPTIVE = 3; - */ - ADAPTIVE = 3 -} - -/** - * Describes the enum registry.v1.AnthropicThinkingType. - */ -export const AnthropicThinkingTypeSchema: GenEnum = /*@__PURE__*/ enumDesc(file_v1_provider, 1) - -/** - * @generated from enum registry.v1.GeminiThinkingLevel - */ -export enum GeminiThinkingLevel { - /** - * @generated from enum value: GEMINI_THINKING_LEVEL_UNSPECIFIED = 0; - */ - UNSPECIFIED = 0, - - /** - * @generated from enum value: GEMINI_THINKING_LEVEL_MINIMAL = 1; - */ - MINIMAL = 1, - - /** - * @generated from enum value: GEMINI_THINKING_LEVEL_LOW = 2; - */ - LOW = 2, - - /** - * @generated from enum value: GEMINI_THINKING_LEVEL_MEDIUM = 3; - */ - MEDIUM = 3, - - /** - * @generated from enum value: GEMINI_THINKING_LEVEL_HIGH = 4; - */ - HIGH = 4 -} - -/** - * Describes the enum registry.v1.GeminiThinkingLevel. - */ -export const GeminiThinkingLevelSchema: GenEnum = /*@__PURE__*/ enumDesc(file_v1_provider, 2) - -/** - * @generated from enum registry.v1.ThinkingType - */ -export enum ThinkingType { - /** - * @generated from enum value: THINKING_TYPE_UNSPECIFIED = 0; - */ - UNSPECIFIED = 0, - - /** - * @generated from enum value: THINKING_TYPE_ENABLED = 1; - */ - ENABLED = 1, - - /** - * @generated from enum value: THINKING_TYPE_DISABLED = 2; - */ - DISABLED = 2, - - /** - * @generated from enum value: THINKING_TYPE_AUTO = 3; - */ - AUTO = 3 -} - -/** - * Describes the enum registry.v1.ThinkingType. - */ -export const ThinkingTypeSchema: GenEnum = /*@__PURE__*/ enumDesc(file_v1_provider, 3) diff --git a/packages/provider-registry/src/index.ts b/packages/provider-registry/src/index.ts index 204a38349e1..c31afe9d447 100644 --- a/packages/provider-registry/src/index.ts +++ b/packages/provider-registry/src/index.ts @@ -3,7 +3,7 @@ * Main entry point for the model and provider registry system */ -// Proto enums (re-exported from schemas/enums.ts which re-exports from gen/) +// Enums (canonical source of truth) export { AnthropicReasoningEffort, Currency, @@ -13,32 +13,33 @@ export { Modality, MODEL_CAPABILITY, ModelCapability, + objectValues, OpenAIReasoningEffort, ReasoningEffort } from './schemas/enums' -// Proto types (source of truth) -export type { ModelConfig, ModelRegistry, ModelConfig as ProtoModelConfig } from './gen/v1/model_pb' +// Schema-inferred types (replaces proto types) export type { + ModelConfig, ModelPricing, + ModelConfig as ProtoModelConfig, ModelPricing as ProtoModelPricing, ReasoningSupport as ProtoReasoningSupport, ReasoningSupport -} from './gen/v1/model_pb' -export type { - ProviderModelOverride as ProtoProviderModelOverride, - ProviderModelOverride, - ProviderModelRegistry -} from './gen/v1/provider_models_pb' +} from './schemas/model' export type { ProviderConfig as ProtoProviderConfig, ProviderReasoningFormat as ProtoProviderReasoningFormat, ProviderConfig, ProviderReasoningFormat, - ProviderRegistry -} from './gen/v1/provider_pb' + RegistryEndpointConfig +} from './schemas/provider' +export type { + ProviderModelOverride as ProtoProviderModelOverride, + ProviderModelOverride +} from './schemas/provider-models' -// Registry reader (read .pb files and return proto Message types) +// Registry reader (read .json files and return validated types) export { readModelRegistry, readProviderModelRegistry, readProviderRegistry } from './registry-reader' // Model ID normalization utilities diff --git a/packages/provider-registry/src/registry-reader.ts b/packages/provider-registry/src/registry-reader.ts index 225bf116b3c..c8f07b00539 100644 --- a/packages/provider-registry/src/registry-reader.ts +++ b/packages/provider-registry/src/registry-reader.ts @@ -1,35 +1,32 @@ /** - * Read-only registry reader for .pb files. + * Read-only registry reader for JSON files. * - * Reads protobuf registry data and returns proto Message types directly. - * No JSON conversion — proto types are the single source of truth. + * Reads JSON registry data and validates against Zod schemas. */ import { readFileSync } from 'node:fs' -import { fromBinary } from '@bufbuild/protobuf' +import type { ModelConfig } from './schemas/model' +import { ModelListSchema } from './schemas/model' +import type { ProviderConfig } from './schemas/provider' +import { ProviderListSchema } from './schemas/provider' +import type { ProviderModelOverride } from './schemas/provider-models' +import { ProviderModelListSchema } from './schemas/provider-models' -import type { ModelConfig } from './gen/v1/model_pb' -import { ModelRegistrySchema } from './gen/v1/model_pb' -import type { ProviderModelOverride } from './gen/v1/provider_models_pb' -import { ProviderModelRegistrySchema } from './gen/v1/provider_models_pb' -import type { ProviderConfig } from './gen/v1/provider_pb' -import { ProviderRegistrySchema } from './gen/v1/provider_pb' - -export function readModelRegistry(pbPath: string): { version: string; models: ModelConfig[] } { - const bytes = readFileSync(pbPath) - const registry = fromBinary(ModelRegistrySchema, new Uint8Array(bytes)) - return { version: registry.version, models: [...registry.models] } +export function readModelRegistry(jsonPath: string): { version: string; models: ModelConfig[] } { + const data = JSON.parse(readFileSync(jsonPath, 'utf-8')) + const registry = ModelListSchema.parse(data) + return { version: registry.version, models: registry.models } } -export function readProviderRegistry(pbPath: string): { version: string; providers: ProviderConfig[] } { - const bytes = readFileSync(pbPath) - const registry = fromBinary(ProviderRegistrySchema, new Uint8Array(bytes)) - return { version: registry.version, providers: [...registry.providers] } +export function readProviderRegistry(jsonPath: string): { version: string; providers: ProviderConfig[] } { + const data = JSON.parse(readFileSync(jsonPath, 'utf-8')) + const registry = ProviderListSchema.parse(data) + return { version: registry.version, providers: registry.providers } } -export function readProviderModelRegistry(pbPath: string): { version: string; overrides: ProviderModelOverride[] } { - const bytes = readFileSync(pbPath) - const registry = fromBinary(ProviderModelRegistrySchema, new Uint8Array(bytes)) - return { version: registry.version, overrides: [...registry.overrides] } +export function readProviderModelRegistry(jsonPath: string): { version: string; overrides: ProviderModelOverride[] } { + const data = JSON.parse(readFileSync(jsonPath, 'utf-8')) + const registry = ProviderModelListSchema.parse(data) + return { version: registry.version, overrides: registry.overrides } } diff --git a/packages/provider-registry/src/schemas/common.ts b/packages/provider-registry/src/schemas/common.ts index 9cd205b804c..3ad5c2c26b0 100644 --- a/packages/provider-registry/src/schemas/common.ts +++ b/packages/provider-registry/src/schemas/common.ts @@ -5,6 +5,8 @@ import * as z from 'zod' +import { Currency, objectValues } from './enums' + // Common string types for reuse export const ModelIdSchema = z.string() export const ProviderIdSchema = z.string() @@ -33,7 +35,7 @@ export const StringRangeSchema = z.object({ }) // Supported currencies for pricing -export const ZodCurrencySchema = z.enum(['USD', 'CNY']).default('USD').optional() +export const ZodCurrencySchema = z.enum(objectValues(Currency)).optional() // Price per token schema // Default currency is USD if not specified diff --git a/packages/provider-registry/src/schemas/enums.ts b/packages/provider-registry/src/schemas/enums.ts index 734f67aafc8..5a453f63c4e 100644 --- a/packages/provider-registry/src/schemas/enums.ts +++ b/packages/provider-registry/src/schemas/enums.ts @@ -1,67 +1,129 @@ /** * Canonical enum definitions for the registry system. * - * Re-exports proto-generated enums as the SINGLE SOURCE OF TRUTH. - * Proto numeric enums are used everywhere — no string conversion. + * These are the SINGLE SOURCE OF TRUTH for all enum types. + * Uses `as const` objects with kebab-case string values for debuggability. * - * - registry/schemas/ uses these via z.nativeEnum() + * - registry/schemas/ uses these via z.enum() * - shared/data/types/ re-exports these directly */ -import { - AnthropicReasoningEffort, - AnthropicReasoningEffortSchema, - Currency, - CurrencySchema, - EndpointType, - EndpointTypeSchema, - Modality, - ModalitySchema, - ModelCapability, - ModelCapabilitySchema, - OpenAIReasoningEffort, - OpenAIReasoningEffortSchema, - ReasoningEffort, - ReasoningEffortSchema -} from '../gen/v1/common_pb' - -// ───────────────────────────────────────────────────────────────────────────── -// Re-export proto enums as canonical source of truth -// ───────────────────────────────────────────────────────────────────────────── - -export { - AnthropicReasoningEffort, - Currency, - EndpointType, - Modality, - ModelCapability, - OpenAIReasoningEffort, - ReasoningEffort -} +// ───────────────────────────────────────────────────────────────────────────── +// EndpointType +// ───────────────────────────────────────────────────────────────────────────── -// Schema descriptors for enum-to-string conversion if needed -export { - AnthropicReasoningEffortSchema as AnthropicReasoningEffortEnumSchema, - CurrencySchema as CurrencyEnumSchema, - EndpointTypeSchema as EndpointTypeEnumSchema, - ModalitySchema as ModalityEnumSchema, - ModelCapabilitySchema as ModelCapabilityEnumSchema, - OpenAIReasoningEffortSchema as OpenAIReasoningEffortEnumSchema, - ReasoningEffortSchema as ReasoningEffortEnumSchema -} +export const EndpointType = { + OPENAI_CHAT_COMPLETIONS: 'openai-chat-completions', + OPENAI_TEXT_COMPLETIONS: 'openai-text-completions', + ANTHROPIC_MESSAGES: 'anthropic-messages', + OPENAI_RESPONSES: 'openai-responses', + GOOGLE_GENERATE_CONTENT: 'google-generate-content', + OLLAMA_CHAT: 'ollama-chat', + OLLAMA_GENERATE: 'ollama-generate', + OPENAI_EMBEDDINGS: 'openai-embeddings', + JINA_RERANK: 'jina-rerank', + OPENAI_IMAGE_GENERATION: 'openai-image-generation', + OPENAI_IMAGE_EDIT: 'openai-image-edit', + OPENAI_AUDIO_TRANSCRIPTION: 'openai-audio-transcription', + OPENAI_AUDIO_TRANSLATION: 'openai-audio-translation', + OPENAI_TEXT_TO_SPEECH: 'openai-text-to-speech', + OPENAI_VIDEO_GENERATION: 'openai-video-generation' +} as const +export type EndpointType = (typeof EndpointType)[keyof typeof EndpointType] + +// ───────────────────────────────────────────────────────────────────────────── +// ModelCapability +// ───────────────────────────────────────────────────────────────────────────── + +export const ModelCapability = { + FUNCTION_CALL: 'function-call', + REASONING: 'reasoning', + IMAGE_RECOGNITION: 'image-recognition', + IMAGE_GENERATION: 'image-generation', + AUDIO_RECOGNITION: 'audio-recognition', + AUDIO_GENERATION: 'audio-generation', + EMBEDDING: 'embedding', + RERANK: 'rerank', + AUDIO_TRANSCRIPT: 'audio-transcript', + VIDEO_RECOGNITION: 'video-recognition', + VIDEO_GENERATION: 'video-generation', + STRUCTURED_OUTPUT: 'structured-output', + FILE_INPUT: 'file-input', + WEB_SEARCH: 'web-search', + CODE_EXECUTION: 'code-execution', + FILE_SEARCH: 'file-search', + COMPUTER_USE: 'computer-use' +} as const +export type ModelCapability = (typeof ModelCapability)[keyof typeof ModelCapability] + +// ───────────────────────────────────────────────────────────────────────────── +// Modality +// ───────────────────────────────────────────────────────────────────────────── + +export const Modality = { + TEXT: 'text', + IMAGE: 'image', + AUDIO: 'audio', + VIDEO: 'video', + VECTOR: 'vector' +} as const +export type Modality = (typeof Modality)[keyof typeof Modality] + +// ───────────────────────────────────────────────────────────────────────────── +// Currency +// ───────────────────────────────────────────────────────────────────────────── + +// Uses uppercase ISO 4217 codes (not kebab-case) — intentional exception +export const Currency = { + USD: 'USD', + CNY: 'CNY' +} as const +export type Currency = (typeof Currency)[keyof typeof Currency] + +// ───────────────────────────────────────────────────────────────────────────── +// ReasoningEffort +// ───────────────────────────────────────────────────────────────────────────── + +export const ReasoningEffort = { + NONE: 'none', + MINIMAL: 'minimal', + LOW: 'low', + MEDIUM: 'medium', + HIGH: 'high', + MAX: 'max', + AUTO: 'auto' +} as const +export type ReasoningEffort = (typeof ReasoningEffort)[keyof typeof ReasoningEffort] + +// ───────────────────────────────────────────────────────────────────────────── +// Provider-specific reasoning effort enums +// ───────────────────────────────────────────────────────────────────────────── + +export const OpenAIReasoningEffort = { + LOW: 'low', + MEDIUM: 'medium', + HIGH: 'high' +} as const +export type OpenAIReasoningEffort = (typeof OpenAIReasoningEffort)[keyof typeof OpenAIReasoningEffort] + +export const AnthropicReasoningEffort = { + LOW: 'low', + MEDIUM: 'medium', + HIGH: 'high', + MAX: 'max' +} as const +export type AnthropicReasoningEffort = (typeof AnthropicReasoningEffort)[keyof typeof AnthropicReasoningEffort] // ───────────────────────────────────────────────────────────────────────────── // Backward-compatible aliases // ───────────────────────────────────────────────────────────────────────────── -// These allow existing `ENDPOINT_TYPE.OPENAI_CHAT_COMPLETIONS` syntax to keep working. -// Values change from strings to numbers — consumers must be updated. export const ENDPOINT_TYPE = EndpointType export const MODEL_CAPABILITY = ModelCapability export const MODALITY = Modality // ───────────────────────────────────────────────────────────────────────────── -// Utility (kept for backward compatibility, will be removed with Zod schemas) +// Utility // ───────────────────────────────────────────────────────────────────────────── /** Extract the value tuple from a const object for use with z.enum(). */ diff --git a/packages/provider-registry/src/schemas/model.ts b/packages/provider-registry/src/schemas/model.ts index 8979cdcf12a..0978235e37d 100644 --- a/packages/provider-registry/src/schemas/model.ts +++ b/packages/provider-registry/src/schemas/model.ts @@ -13,12 +13,12 @@ import { VersionSchema, ZodCurrencySchema } from './common' -import { Modality, ModelCapability, ReasoningEffort } from './enums' +import { Modality, ModelCapability, objectValues, ReasoningEffort } from './enums' -export const ModalitySchema = z.enum(Modality) +export const ModalitySchema = z.enum(objectValues(Modality)) export type ModalityType = z.infer -export const ModelCapabilityTypeSchema = z.enum(ModelCapability) +export const ModelCapabilityTypeSchema = z.enum(objectValues(ModelCapability)) export type ModelCapabilityType = z.infer // Thinking token limits schema (shared across reasoning types) @@ -29,7 +29,7 @@ export const ThinkingTokenLimitsSchema = z.object({ }) /** Reasoning effort levels shared across providers */ -export const ReasoningEffortSchema = z.enum(ReasoningEffort) +export const ReasoningEffortSchema = z.enum(objectValues(ReasoningEffort)) // Common reasoning fields shared across all reasoning type variants // Exported for shared/runtime types to reuse diff --git a/packages/provider-registry/src/schemas/provider.ts b/packages/provider-registry/src/schemas/provider.ts index a0b6845310a..1036ec2e307 100644 --- a/packages/provider-registry/src/schemas/provider.ts +++ b/packages/provider-registry/src/schemas/provider.ts @@ -6,10 +6,10 @@ import * as z from 'zod' import { MetadataSchema, ProviderIdSchema, VersionSchema } from './common' -import { EndpointType, ReasoningEffort } from './enums' +import { EndpointType, objectValues, ReasoningEffort } from './enums' import { CommonReasoningFieldsSchema } from './model' -export const EndpointTypeSchema = z.enum(EndpointType) +export const EndpointTypeSchema = z.enum(objectValues(EndpointType)) // ═══════════════════════════════════════════════════════════════════════════════ // API Features @@ -44,7 +44,7 @@ export const ApiFeaturesSchema = z.object({ // (effort levels, token limits) are in model.ts ReasoningSupportSchema. // ═══════════════════════════════════════════════════════════════════════════════ -const ReasoningEffortSchema = z.enum(ReasoningEffort) +const ReasoningEffortSchema = z.enum(objectValues(ReasoningEffort)) /** Provider reasoning format — discriminated union by format type */ export const ProviderReasoningFormatSchema = z.discriminatedUnion('type', [ diff --git a/packages/shared/data/api/schemas/models.ts b/packages/shared/data/api/schemas/models.ts index 81c44d233ce..75318ede465 100644 --- a/packages/shared/data/api/schemas/models.ts +++ b/packages/shared/data/api/schemas/models.ts @@ -18,8 +18,8 @@ import { const ListModelsQuerySchema = z.object({ /** Filter by provider ID */ providerId: z.string().optional(), - /** Filter by capability (numeric ModelCapability enum value) */ - capability: z.number().optional(), + /** Filter by capability (ModelCapability string value) */ + capability: z.string().optional(), /** Filter by enabled status */ enabled: z.boolean().optional() }) @@ -40,13 +40,13 @@ const CreateModelDtoSchema = z.object({ /** UI grouping */ group: z.string().optional(), /** Capabilities (numeric ModelCapability enum values) */ - capabilities: z.array(z.number()).optional(), + capabilities: z.array(z.string()).optional(), /** Input modalities (numeric Modality enum values) */ - inputModalities: z.array(z.number()).optional(), + inputModalities: z.array(z.string()).optional(), /** Output modalities (numeric Modality enum values) */ - outputModalities: z.array(z.number()).optional(), + outputModalities: z.array(z.string()).optional(), /** Endpoint types */ - endpointTypes: z.array(z.number()).optional(), + endpointTypes: z.array(z.string()).optional(), /** Context window size */ contextWindow: z.number().optional(), /** Maximum output tokens */ @@ -86,7 +86,7 @@ const EnrichModelsDtoSchema = z.object({ name: z.string().optional(), group: z.string().optional(), description: z.string().optional(), - endpointTypes: z.array(z.number()).optional() + endpointTypes: z.array(z.string()).optional() }) ) }) diff --git a/packages/shared/data/types/model.ts b/packages/shared/data/types/model.ts index d961cbe3c5a..86496bf6e2b 100644 --- a/packages/shared/data/types/model.ts +++ b/packages/shared/data/types/model.ts @@ -18,6 +18,7 @@ import { Modality, MODEL_CAPABILITY, ModelCapability, + objectValues, ReasoningEffort } from '@cherrystudio/provider-registry' import * as z from 'zod' @@ -32,7 +33,7 @@ export { Currency, ENDPOINT_TYPE, EndpointType, MODALITY, Modality, MODEL_CAPABI /** Price per token schema */ export const PricePerTokenSchema = z.object({ perMillionTokens: z.number().nonnegative().nullable(), - currency: z.nativeEnum(Currency).default(Currency.USD).optional() + currency: z.enum(objectValues(Currency)).default(Currency.USD).optional() }) /** Thinking token limits */ @@ -43,7 +44,7 @@ export const ThinkingTokenLimitsSchema = z.object({ }) /** Reasoning effort levels */ -const ReasoningEffortSchema = z.enum(ReasoningEffort) +const ReasoningEffortSchema = z.enum(objectValues(ReasoningEffort)) /** Common reasoning fields shared across all reasoning type variants */ const CommonReasoningFieldsSchema = { @@ -153,7 +154,7 @@ export type ReasoningConfig = z.infer /** Runtime form: extends DB form — supportedEfforts required, adds defaultEffort */ export const RuntimeReasoningSchema = ReasoningConfigSchema.required({ supportedEfforts: true }).extend({ /** Default effort level */ - defaultEffort: z.enum(ReasoningEffort).optional() + defaultEffort: z.enum(objectValues(ReasoningEffort)).optional() }) export type RuntimeReasoning = z.infer @@ -238,11 +239,11 @@ export const ModelSchema = z.object({ // Capabilities /** Final capability list after all merges */ - capabilities: z.array(z.enum(ModelCapability)), + capabilities: z.array(z.enum(objectValues(ModelCapability))), /** Supported input modalities */ - inputModalities: z.array(z.enum(Modality)).optional(), + inputModalities: z.array(z.enum(objectValues(Modality))).optional(), /** Supported output modalities */ - outputModalities: z.array(z.enum(Modality)).optional(), + outputModalities: z.array(z.enum(objectValues(Modality))).optional(), // Configuration /** Context window size */ @@ -252,7 +253,7 @@ export const ModelSchema = z.object({ /** Maximum input tokens */ maxInputTokens: z.number().optional(), /** Supported endpoint types */ - endpointTypes: z.array(z.enum(EndpointType)).optional(), + endpointTypes: z.array(z.enum(objectValues(EndpointType))).optional(), /** Whether streaming is supported */ supportsStreaming: z.boolean(), /** Reasoning configuration */ diff --git a/packages/shared/data/types/provider.ts b/packages/shared/data/types/provider.ts index 38cf5d85bfc..78d22044c75 100644 --- a/packages/shared/data/types/provider.ts +++ b/packages/shared/data/types/provider.ts @@ -11,12 +11,12 @@ * Zod schemas are the single source of truth — all types derived via z.infer<> */ -import { EndpointType } from '@cherrystudio/provider-registry' +import { EndpointType, objectValues } from '@cherrystudio/provider-registry' import * as z from 'zod' // ─── Schemas formerly from provider-registry/schemas ───────────────────────── -const EndpointTypeSchema = z.enum(EndpointType) +const EndpointTypeSchema = z.enum(objectValues(EndpointType)) /** API feature flags controlling request construction at the SDK level */ const CatalogApiFeaturesSchema = z.object({ diff --git a/packages/shared/data/utils/modelMerger.ts b/packages/shared/data/utils/modelMerger.ts index 3be1c7e55a1..6dbdf94af4d 100644 --- a/packages/shared/data/utils/modelMerger.ts +++ b/packages/shared/data/utils/modelMerger.ts @@ -9,11 +9,10 @@ import type { ProtoModelConfig, ProtoProviderConfig, ProtoProviderModelOverride, - ProtoProviderReasoningFormat, ProtoReasoningSupport } from '@cherrystudio/provider-registry' import type { Modality, ModelCapability, ReasoningEffort as ReasoningEffortType } from '@cherrystudio/provider-registry' -import { EndpointType, ReasoningEffort } from '@cherrystudio/provider-registry' +import { EndpointType, objectValues, ReasoningEffort } from '@cherrystudio/provider-registry' import * as z from 'zod' import type { Model, RuntimeModelPricing, RuntimeReasoning } from '../types/model' @@ -47,7 +46,7 @@ export { DEFAULT_API_FEATURES, DEFAULT_PROVIDER_SETTINGS } */ export function applyCapabilityOverride( base: ModelCapability[], - override: { add: ModelCapability[]; remove: ModelCapability[]; force: ModelCapability[] } | null | undefined + override: { add?: ModelCapability[]; remove?: ModelCapability[]; force?: ModelCapability[] } | null | undefined ): ModelCapability[] { if (!override) { return [...base] @@ -61,12 +60,12 @@ export function applyCapabilityOverride( let result = [...base] // Add new capabilities - if (override.add.length) { + if (override.add?.length) { result = Array.from(new Set([...result, ...override.add])) } // Remove capabilities - if (override.remove.length) { + if (override.remove?.length) { const removeSet = new Set(override.remove) result = result.filter((c) => !removeSet.has(c)) } @@ -79,7 +78,7 @@ const UserProviderRowSchema = z.object({ presetProviderId: z.string().nullish(), name: z.string(), endpointConfigs: z.record(z.string(), EndpointConfigSchema).nullish(), - defaultChatEndpoint: z.nativeEnum(EndpointType).nullish(), + defaultChatEndpoint: z.enum(objectValues(EndpointType)).nullish(), apiKeys: z.array(ApiKeyEntrySchema.pick({ id: true, key: true, label: true, isEnabled: true })).nullish(), authConfig: z.object({ type: z.string() }).catchall(z.unknown()).nullish(), apiFeatures: ApiFeaturesSchema.nullish(), @@ -97,10 +96,10 @@ const UserModelRowSchema = z.object({ name: z.string().nullish(), description: z.string().nullish(), group: z.string().nullish(), - capabilities: z.array(z.number()).nullish(), - inputModalities: z.array(z.number()).nullish(), - outputModalities: z.array(z.number()).nullish(), - endpointTypes: z.array(z.number()).nullish(), + capabilities: z.array(z.string()).nullish(), + inputModalities: z.array(z.string()).nullish(), + outputModalities: z.array(z.string()).nullish(), + endpointTypes: z.array(z.string()).nullish(), customEndpointUrl: z.string().nullish(), contextWindow: z.number().nullish(), maxOutputTokens: z.number().nullish(), @@ -163,11 +162,11 @@ export function mergeModelConfig( const modelId = presetModel.id // Start from preset - let capabilities: ModelCapability[] = [...presetModel.capabilities] - let inputModalities: Modality[] | undefined = presetModel.inputModalities.length + let capabilities: ModelCapability[] = [...(presetModel.capabilities ?? [])] + let inputModalities: Modality[] | undefined = presetModel.inputModalities?.length ? [...presetModel.inputModalities] : undefined - let outputModalities: Modality[] | undefined = presetModel.outputModalities.length + let outputModalities: Modality[] | undefined = presetModel.outputModalities?.length ? [...presetModel.outputModalities] : undefined let endpointTypes: EndpointType[] | undefined = undefined @@ -220,13 +219,13 @@ export function mergeModelConfig( if (catalogOverride.limits?.maxInputTokens != null) { maxInputTokens = catalogOverride.limits.maxInputTokens } - if (catalogOverride.endpointTypes.length) { + if (catalogOverride.endpointTypes?.length) { endpointTypes = [...catalogOverride.endpointTypes] } - if (catalogOverride.inputModalities.length) { + if (catalogOverride.inputModalities?.length) { inputModalities = [...catalogOverride.inputModalities] } - if (catalogOverride.outputModalities.length) { + if (catalogOverride.outputModalities?.length) { outputModalities = [...catalogOverride.outputModalities] } if (catalogOverride.replaceWith) { @@ -383,19 +382,6 @@ export function mergeProviderConfig( // Helper Functions // ═══════════════════════════════════════════════════════════════════════════════ -/** Map proto ProviderReasoningFormat.format.case to runtime reasoning type string */ -const REASONING_FORMAT_CASE_TO_TYPE: Record = { - openaiChat: 'openai-chat', - openaiResponses: 'openai-responses', - anthropic: 'anthropic', - gemini: 'gemini', - openrouter: 'openrouter', - enableThinking: 'enable-thinking', - thinkingType: 'thinking-type', - dashscope: 'dashscope', - selfHosted: 'self-hosted' -} - const CHAT_REASONING_ENDPOINT_PRIORITY: EndpointType[] = [ EndpointType.OPENAI_RESPONSES, EndpointType.OPENAI_CHAT_COMPLETIONS, @@ -433,40 +419,24 @@ function isChatReasoningEndpointType(endpointType: EndpointType): boolean { } /** - * Build endpointConfigs from preset provider's proto data. - * Converts proto endpointConfigs (with proto message types) into runtime EndpointConfig. + * Build runtime endpointConfigs from preset provider's registry data. + * Maps registry reasoningFormat (discriminated union) to runtime reasoningFormatType string. */ function buildPresetEndpointConfigs( presetProvider: ProtoProviderConfig | null ): Partial> { - if (!presetProvider) return {} + if (!presetProvider?.endpointConfigs) return {} const configs: Partial> = {} - for (const [k, protoConfig] of Object.entries(presetProvider.endpointConfigs)) { - const ep = Number(k) as EndpointType + for (const [k, regConfig] of Object.entries(presetProvider.endpointConfigs)) { + const ep = k as EndpointType const config: EndpointConfig = {} - if (protoConfig.baseUrl) { - config.baseUrl = protoConfig.baseUrl - } - - // Convert proto ModelsApiUrls message to plain object - if (protoConfig.modelsApiUrls) { - const modelsApiUrls: Record = {} - if (protoConfig.modelsApiUrls.default) modelsApiUrls.default = protoConfig.modelsApiUrls.default - if (protoConfig.modelsApiUrls.embedding) modelsApiUrls.embedding = protoConfig.modelsApiUrls.embedding - if (protoConfig.modelsApiUrls.reranker) modelsApiUrls.reranker = protoConfig.modelsApiUrls.reranker - if (Object.keys(modelsApiUrls).length > 0) { - config.modelsApiUrls = modelsApiUrls - } - } - - // Convert proto ProviderReasoningFormat to runtime type string - const reasoningFormatType = extractReasoningFormatType(protoConfig.reasoningFormat) - if (reasoningFormatType) { - config.reasoningFormatType = reasoningFormatType - } + if (regConfig.baseUrl) config.baseUrl = regConfig.baseUrl + if (regConfig.modelsApiUrls) config.modelsApiUrls = regConfig.modelsApiUrls + if (regConfig.reasoningFormat?.type) + config.reasoningFormatType = regConfig.reasoningFormat.type as ReasoningFormatType if (Object.keys(config).length > 0) { configs[ep] = config @@ -488,7 +458,7 @@ function mergeEndpointConfigs( const allKeys = new Set([...Object.keys(preset ?? {}), ...Object.keys(user ?? {})]) for (const k of allKeys) { - const endpointType = Number(k) as EndpointType + const endpointType = k as EndpointType const presetConfig = preset?.[endpointType] const userConfig = user?.[endpointType] result[endpointType] = { @@ -510,7 +480,7 @@ export function extractReasoningFormatTypes( const result: Partial> = {} for (const [k, v] of Object.entries(endpointConfigs)) { if (v?.reasoningFormatType) { - result[Number(k) as EndpointType] = v.reasoningFormatType + result[k as EndpointType] = v.reasoningFormatType } } return Object.keys(result).length > 0 ? result : undefined @@ -554,14 +524,6 @@ function resolveReasoningFormatType( return reasoningFormatTypes[endpointType] } -/** - * Extract runtime reasoning type string from proto ProviderReasoningFormat - */ -function extractReasoningFormatType(format: ProtoProviderReasoningFormat | undefined): ReasoningFormatType | undefined { - if (!format?.format.case) return undefined - return REASONING_FORMAT_CASE_TO_TYPE[format.format.case] -} - /** * Convert proto ReasoningSupport to runtime RuntimeReasoning * The `type` comes from the provider's reasoningFormat, not from the model. diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a0a2f31629d..5b04531a9c6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1419,9 +1419,6 @@ importers: packages/provider-registry: dependencies: - '@bufbuild/protobuf': - specifier: ^2.11.0 - version: 2.11.0 class-variance-authority: specifier: ^0.7.1 version: 0.7.1 @@ -1432,12 +1429,6 @@ importers: specifier: ^0.563.0 version: 0.563.0(react@19.2.3) devDependencies: - '@bufbuild/buf': - specifier: ^1.66.0 - version: 1.67.0 - '@bufbuild/protoc-gen-es': - specifier: ^2.11.0 - version: 2.11.0(@bufbuild/protobuf@2.11.0) '@types/json-schema': specifier: ^7.0.15 version: 7.0.15 @@ -2210,72 +2201,9 @@ packages: openai: ^4.62.1 zod: ^3.23.8 - '@bufbuild/buf-darwin-arm64@1.67.0': - resolution: {integrity: sha512-9h/1E2FNCSIt9m4wriGiXt8gHrg8VBOOpmUPVr68axZxb17krPQrIZBPsx05yNpbyvSrPj26/jO2aoqpZsG1vw==} - engines: {node: '>=12'} - cpu: [arm64] - os: [darwin] - - '@bufbuild/buf-darwin-x64@1.67.0': - resolution: {integrity: sha512-9kNu0JBR+TQvxCD6NBooy03g8sLNZGEd0umkWHzdO/05HuV/J6GecMGx1kJ2MYlZQHM4/MljfIuYQUblP1nP4A==} - engines: {node: '>=12'} - cpu: [x64] - os: [darwin] - - '@bufbuild/buf-linux-aarch64@1.67.0': - resolution: {integrity: sha512-hlA20Oot20nW/9CzPBMPPPMfUarKvzqni+Njgrw8T43IFoQWQv8iIRoWWOgOQTGCm4PmjYwiojzEHOEaaKrzTg==} - engines: {node: '>=12'} - cpu: [arm64] - os: [linux] - - '@bufbuild/buf-linux-armv7@1.67.0': - resolution: {integrity: sha512-hO9FEEtloITNaxW89rzKUjAsgnX1+rth7IZbK0Z+ohatXdanYg7Kv66yWffytaYf2iHltTbY6W/H4C3x0Uimbg==} - engines: {node: '>=12'} - cpu: [arm] - os: [linux] - - '@bufbuild/buf-linux-x64@1.67.0': - resolution: {integrity: sha512-KBOWZ0NbhJSfXLM3JEX2AEs32jyHvTKD7wkIYudqOTxPUqwM1MXUg7m2Xw5nP1pcKH4RKS5HFijPMeOW/XUQ8Q==} - engines: {node: '>=12'} - cpu: [x64] - os: [linux] - - '@bufbuild/buf-win32-arm64@1.67.0': - resolution: {integrity: sha512-ARGPwOv0lkUp3FU7bUMpYzqoJInx2qkk1ECBEC9XZMnRKmhCbyzmBoBKChBBJhEyDFdzPivhjg//zk5AlQ3bFA==} - engines: {node: '>=12'} - cpu: [arm64] - os: [win32] - - '@bufbuild/buf-win32-x64@1.67.0': - resolution: {integrity: sha512-x9fkxEbjb2U4petBbESvNx+sfSQJONJxKOQzPfEKALksqRlvh7ktoHrYbygErnRZBSTNgrXzAqFI1GxMGEGSLQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [win32] - - '@bufbuild/buf@1.67.0': - resolution: {integrity: sha512-BLfgGmNFiHM79PcaafFNiP/+xxbdyFp1neDDdJd6R0tu7McO+WgJHM6vyNYRm7vXOSgO1uUPE4X3YFdBgcWk2Q==} - engines: {node: '>=12'} - hasBin: true - '@bufbuild/protobuf@2.10.2': resolution: {integrity: sha512-uFsRXwIGyu+r6AMdz+XijIIZJYpoWeYzILt5yZ2d3mCjQrWUTVpVD9WL/jZAbvp+Ed04rOhrsk7FiTcEDseB5A==} - '@bufbuild/protobuf@2.11.0': - resolution: {integrity: sha512-sBXGT13cpmPR5BMgHE6UEEfEaShh5Ror6rfN3yEK5si7QVrtZg8LEPQb0VVhiLRUslD2yLnXtnRzG035J/mZXQ==} - - '@bufbuild/protoc-gen-es@2.11.0': - resolution: {integrity: sha512-VzQuwEQDXipbZ1soWUuAWm1Z0C3B/IDWGeysnbX6ogJ6As91C2mdvAND/ekQ4YIWgen4d5nqLfIBOWLqCCjYUA==} - engines: {node: '>=20'} - hasBin: true - peerDependencies: - '@bufbuild/protobuf': 2.11.0 - peerDependenciesMeta: - '@bufbuild/protobuf': - optional: true - - '@bufbuild/protoplugin@2.11.0': - resolution: {integrity: sha512-lyZVNFUHArIOt4W0+dwYBe5GBwbKzbOy8ObaloEqsw9Mmiwv2O48TwddDoHN4itylC+BaEGqFdI1W8WQt2vWJQ==} - '@buttercup/fetch@0.2.1': resolution: {integrity: sha512-sCgECOx8wiqY8NN1xN22BqqKzXYIG2AicNLlakOAI4f0WgyLVUbAigMf8CZhBtJxdudTcB1gD5lciqi44jwJvg==} @@ -8027,11 +7955,6 @@ packages: resolution: {integrity: sha512-o1R4QGAjCWG0LOqpDOBuH9H+BgaM+7Ps8AuvHJkf4V1tmTFqeGMXGNbv02f3HQIZiI3KMqsRGoBGe/LvCc4SFg==} hasBin: true - '@typescript/vfs@1.6.4': - resolution: {integrity: sha512-PJFXFS4ZJKiJ9Qiuix6Dz/OwEIqHD7Dme1UwZhTK11vR+5dqW2ACbdndWQexBzCx+CPuMe5WBYQWCsFyGlQLlQ==} - peerDependencies: - typescript: '*' - '@uiw/codemirror-extensions-basic-setup@4.25.7': resolution: {integrity: sha512-tPV/AGjF4yM22D5mnyH7EuYBkWO05wF5Y4x3lmQJo6LuHmhjh0RQsVDjqeIgNOkXT3UO9OdkL4dzxw465/JZVg==} peerDependencies: @@ -15022,11 +14945,6 @@ packages: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - typescript@5.4.5: - resolution: {integrity: sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==} - engines: {node: '>=14.17'} - hasBin: true - typescript@5.8.3: resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} engines: {node: '>=14.17'} @@ -16785,57 +16703,8 @@ snapshots: - encoding - utf-8-validate - '@bufbuild/buf-darwin-arm64@1.67.0': - optional: true - - '@bufbuild/buf-darwin-x64@1.67.0': - optional: true - - '@bufbuild/buf-linux-aarch64@1.67.0': - optional: true - - '@bufbuild/buf-linux-armv7@1.67.0': - optional: true - - '@bufbuild/buf-linux-x64@1.67.0': - optional: true - - '@bufbuild/buf-win32-arm64@1.67.0': - optional: true - - '@bufbuild/buf-win32-x64@1.67.0': - optional: true - - '@bufbuild/buf@1.67.0': - optionalDependencies: - '@bufbuild/buf-darwin-arm64': 1.67.0 - '@bufbuild/buf-darwin-x64': 1.67.0 - '@bufbuild/buf-linux-aarch64': 1.67.0 - '@bufbuild/buf-linux-armv7': 1.67.0 - '@bufbuild/buf-linux-x64': 1.67.0 - '@bufbuild/buf-win32-arm64': 1.67.0 - '@bufbuild/buf-win32-x64': 1.67.0 - '@bufbuild/protobuf@2.10.2': {} - '@bufbuild/protobuf@2.11.0': {} - - '@bufbuild/protoc-gen-es@2.11.0(@bufbuild/protobuf@2.11.0)': - dependencies: - '@bufbuild/protoplugin': 2.11.0 - optionalDependencies: - '@bufbuild/protobuf': 2.11.0 - transitivePeerDependencies: - - supports-color - - '@bufbuild/protoplugin@2.11.0': - dependencies: - '@bufbuild/protobuf': 2.11.0 - '@typescript/vfs': 1.6.4(typescript@5.4.5) - typescript: 5.4.5 - transitivePeerDependencies: - - supports-color - '@buttercup/fetch@0.2.1': optionalDependencies: node-fetch: 3.3.2 @@ -23667,13 +23536,6 @@ snapshots: '@typescript/native-preview-win32-arm64': 7.0.0-dev.20260204.1 '@typescript/native-preview-win32-x64': 7.0.0-dev.20260204.1 - '@typescript/vfs@1.6.4(typescript@5.4.5)': - dependencies: - debug: 4.4.3 - typescript: 5.4.5 - transitivePeerDependencies: - - supports-color - '@uiw/codemirror-extensions-basic-setup@4.25.7(@codemirror/autocomplete@6.20.1)(@codemirror/commands@6.10.2)(@codemirror/language@6.12.2)(@codemirror/lint@6.9.5)(@codemirror/search@6.5.11)(@codemirror/state@6.5.4)(@codemirror/view@6.39.16)': dependencies: '@codemirror/autocomplete': 6.20.1 @@ -32308,8 +32170,6 @@ snapshots: transitivePeerDependencies: - supports-color - typescript@5.4.5: {} - typescript@5.8.3: {} typescript@5.9.3: {} diff --git a/src/main/data/db/schemas/userModel.ts b/src/main/data/db/schemas/userModel.ts index cfcd9a16ab7..62d4a36e187 100644 --- a/src/main/data/db/schemas/userModel.ts +++ b/src/main/data/db/schemas/userModel.ts @@ -156,10 +156,10 @@ export type UserModel = typeof userModelTable.$inferSelect export type NewUserModel = typeof userModelTable.$inferInsert const jsonColumnOverrides = { - capabilities: () => z.array(z.number()).nullable(), - inputModalities: () => z.array(z.number()).nullable(), - outputModalities: () => z.array(z.number()).nullable(), - endpointTypes: () => z.array(z.number()).nullable(), + capabilities: () => z.array(z.string()).nullable() as z.ZodNullable>>, + inputModalities: () => z.array(z.string()).nullable() as z.ZodNullable>>, + outputModalities: () => z.array(z.string()).nullable() as z.ZodNullable>>, + endpointTypes: () => z.array(z.string()).nullable() as z.ZodNullable>>, reasoning: () => ReasoningConfigSchema.nullable(), parameters: () => ParameterSupportDbSchema.nullable(), pricing: () => RuntimeModelPricingSchema.nullable(), diff --git a/src/main/data/migration/v2/migrators/mappings/ProviderModelMappings.ts b/src/main/data/migration/v2/migrators/mappings/ProviderModelMappings.ts index fdc6650dd24..9edad56c9e0 100644 --- a/src/main/data/migration/v2/migrators/mappings/ProviderModelMappings.ts +++ b/src/main/data/migration/v2/migrators/mappings/ProviderModelMappings.ts @@ -59,7 +59,7 @@ const CAPABILITY_MAP: Partial> = rerank: MODEL_CAPABILITY.RERANK } -/** Legacy string endpoint/provider-type keys → catalog numeric EndpointType */ +/** Legacy string endpoint/provider-type keys → EndpointType */ const ENDPOINT_MAP: Partial> = { openai: ENDPOINT_TYPE.OPENAI_CHAT_COMPLETIONS, 'openai-response': ENDPOINT_TYPE.OPENAI_RESPONSES, diff --git a/src/main/data/services/ModelService.ts b/src/main/data/services/ModelService.ts index d4f8e70c5b8..dc6e7bf1998 100644 --- a/src/main/data/services/ModelService.ts +++ b/src/main/data/services/ModelService.ts @@ -13,7 +13,14 @@ import { loggerService } from '@logger' import { application } from '@main/core/application' import { DataApiErrorFactory } from '@shared/data/api' import type { CreateModelDto, ListModelsQuery, UpdateModelDto } from '@shared/data/api/schemas/models' -import type { Model, RuntimeParameterSupport, RuntimeReasoning } from '@shared/data/types/model' +import type { + EndpointType, + Modality, + Model, + ModelCapability, + RuntimeParameterSupport, + RuntimeReasoning +} from '@shared/data/types/model' import { createUniqueModelId } from '@shared/data/types/model' import { mergeModelConfig } from '@shared/data/utils/modelMerger' import { and, eq, inArray, type SQL } from 'drizzle-orm' @@ -87,7 +94,7 @@ export class ModelService { // Post-filter by capability (JSON array column, can't filter in SQL easily) if (query.capability !== undefined) { - const cap = query.capability + const cap = query.capability as ModelCapability models = models.filter((m) => m.capabilities.includes(cap)) } @@ -138,10 +145,10 @@ export class ModelService { name: dto.name ?? null, description: dto.description ?? null, group: dto.group ?? null, - capabilities: dto.capabilities ?? null, - inputModalities: dto.inputModalities ?? null, - outputModalities: dto.outputModalities ?? null, - endpointTypes: dto.endpointTypes ?? null, + capabilities: (dto.capabilities as ModelCapability[]) ?? null, + inputModalities: (dto.inputModalities as Modality[]) ?? null, + outputModalities: (dto.outputModalities as Modality[]) ?? null, + endpointTypes: (dto.endpointTypes as EndpointType[]) ?? null, contextWindow: dto.contextWindow ?? null, maxOutputTokens: dto.maxOutputTokens ?? null, supportsStreaming: dto.supportsStreaming ?? null, @@ -190,10 +197,10 @@ export class ModelService { name: dto.name ?? null, description: dto.description ?? null, group: dto.group ?? null, - capabilities: dto.capabilities ?? null, - inputModalities: dto.inputModalities ?? null, - outputModalities: dto.outputModalities ?? null, - endpointTypes: dto.endpointTypes ?? null, + capabilities: (dto.capabilities as ModelCapability[]) ?? null, + inputModalities: (dto.inputModalities as Modality[]) ?? null, + outputModalities: (dto.outputModalities as Modality[]) ?? null, + endpointTypes: (dto.endpointTypes as EndpointType[]) ?? null, contextWindow: dto.contextWindow ?? null, maxOutputTokens: dto.maxOutputTokens ?? null, supportsStreaming: dto.supportsStreaming ?? null, @@ -236,8 +243,8 @@ export class ModelService { if (dto.name !== undefined) updates.name = dto.name if (dto.description !== undefined) updates.description = dto.description if (dto.group !== undefined) updates.group = dto.group - if (dto.capabilities !== undefined) updates.capabilities = dto.capabilities - if (dto.endpointTypes !== undefined) updates.endpointTypes = dto.endpointTypes + if (dto.capabilities !== undefined) updates.capabilities = dto.capabilities as ModelCapability[] + if (dto.endpointTypes !== undefined) updates.endpointTypes = dto.endpointTypes as EndpointType[] if (dto.supportsStreaming !== undefined) updates.supportsStreaming = dto.supportsStreaming if (dto.contextWindow !== undefined) updates.contextWindow = dto.contextWindow if (dto.maxOutputTokens !== undefined) updates.maxOutputTokens = dto.maxOutputTokens diff --git a/src/main/data/services/ProviderRegistryService.ts b/src/main/data/services/ProviderRegistryService.ts index d56e6a0c152..261ed8f2fc9 100644 --- a/src/main/data/services/ProviderRegistryService.ts +++ b/src/main/data/services/ProviderRegistryService.ts @@ -2,7 +2,7 @@ * Registry Service - imports registry data into SQLite * * Responsible for: - * - Reading registry protobuf files (models.pb, provider-models.pb, providers.pb) + * - Reading registry JSON files (models.json, provider-models.json, providers.json) * - Merging configurations using mergeModelConfig/mergeProviderConfig * - Writing resolved data to user_model / user_provider tables * @@ -11,7 +11,12 @@ import { join } from 'node:path' -import type { ProtoModelConfig, ProtoProviderConfig, ProtoProviderModelOverride } from '@cherrystudio/provider-registry' +import type { + ProtoModelConfig, + ProtoProviderConfig, + ProtoProviderModelOverride, + RegistryEndpointConfig +} from '@cherrystudio/provider-registry' import { EndpointType, readModelRegistry, @@ -37,55 +42,27 @@ import { providerService } from './ProviderService' const logger = loggerService.withContext('DataApi:ProviderRegistryService') -/** Map proto ProviderReasoningFormat oneof case to runtime type string */ -const CASE_TO_TYPE: Record = { - openaiChat: 'openai-chat', - openaiResponses: 'openai-responses', - anthropic: 'anthropic', - gemini: 'gemini', - openrouter: 'openrouter', - enableThinking: 'enable-thinking', - thinkingType: 'thinking-type', - dashscope: 'dashscope', - selfHosted: 'self-hosted' -} - /** - * Build runtime endpointConfigs from proto provider data. - * Converts proto EndpointConfig messages to plain runtime objects. + * Convert registry endpointConfigs (with reasoningFormat discriminated union) + * to runtime endpointConfigs (with reasoningFormatType string). */ -function buildEndpointConfigsFromProto(p: ProtoProviderConfig): Partial> | null { +function buildRuntimeEndpointConfigs( + registryConfigs: Record | undefined +): Partial> | null { + if (!registryConfigs || Object.keys(registryConfigs).length === 0) return null + const configs: Partial> = {} - for (const [k, protoConfig] of Object.entries(p.endpointConfigs)) { - const ep = Number(k) as EndpointType + for (const [k, regConfig] of Object.entries(registryConfigs)) { + const ep = k as EndpointType const config: EndpointConfig = {} - if (protoConfig.baseUrl) { - config.baseUrl = protoConfig.baseUrl - } - - // Convert proto ModelsApiUrls message to plain object - if (protoConfig.modelsApiUrls) { - const modelsApiUrls: Record = {} - if (protoConfig.modelsApiUrls.default) modelsApiUrls.default = protoConfig.modelsApiUrls.default - if (protoConfig.modelsApiUrls.embedding) modelsApiUrls.embedding = protoConfig.modelsApiUrls.embedding - if (protoConfig.modelsApiUrls.reranker) modelsApiUrls.reranker = protoConfig.modelsApiUrls.reranker - if (Object.keys(modelsApiUrls).length > 0) { - config.modelsApiUrls = modelsApiUrls - } - } - - // Convert proto ProviderReasoningFormat to runtime type string - const formatCase = protoConfig.reasoningFormat?.format.case - const reasoningFormatType = formatCase ? CASE_TO_TYPE[formatCase] : undefined - if (reasoningFormatType) { - config.reasoningFormatType = reasoningFormatType - } + if (regConfig.baseUrl) config.baseUrl = regConfig.baseUrl + if (regConfig.modelsApiUrls) config.modelsApiUrls = regConfig.modelsApiUrls + if (regConfig.reasoningFormat?.type) + config.reasoningFormatType = regConfig.reasoningFormat.type as ReasoningFormatType - if (Object.keys(config).length > 0) { - configs[ep] = config - } + if (Object.keys(config).length > 0) configs[ep] = config } return Object.keys(configs).length > 0 ? configs : null @@ -100,9 +77,6 @@ export class ProviderRegistryService extends BaseService { private registryProviders: ProtoProviderConfig[] | null = null protected async onInit(): Promise { - // Sync preset registry data on every startup so that provider websites/baseUrls - // and model capabilities/modalities/contextWindow/pricing stay up-to-date - // when the registry protobuf files are updated between app versions. await this.initializeAllPresetProviders() } @@ -110,9 +84,6 @@ export class ProviderRegistryService extends BaseService { this.clearCache() } - /** - * Get the path to registry data directory - */ private getRegistryDataPath(): string { if (isDev) { return join(__dirname, '..', '..', 'packages', 'provider-registry', 'data') @@ -120,72 +91,60 @@ export class ProviderRegistryService extends BaseService { return join(process.resourcesPath, 'packages', 'provider-registry', 'data') } - /** - * Load and cache registry models from models.pb - */ private loadRegistryModels(): ProtoModelConfig[] { if (this.registryModels) return this.registryModels try { const dataPath = this.getRegistryDataPath() - const data = readModelRegistry(join(dataPath, 'models.pb')) + const data = readModelRegistry(join(dataPath, 'models.json')) const models = data.models ?? [] this.registryModels = models logger.info('Loaded registry models', { count: models.length }) return models } catch (error) { - logger.warn('Failed to load registry models.pb', { error }) + logger.warn('Failed to load registry models.json', { error }) return [] } } - /** - * Load and cache provider-model overrides from provider-models.pb - */ private loadProviderModels(): ProtoProviderModelOverride[] { if (this.registryProviderModels) return this.registryProviderModels try { const dataPath = this.getRegistryDataPath() - const data = readProviderModelRegistry(join(dataPath, 'provider-models.pb')) + const data = readProviderModelRegistry(join(dataPath, 'provider-models.json')) const overrides = data.overrides ?? [] this.registryProviderModels = overrides logger.info('Loaded registry provider-models', { count: overrides.length }) return overrides } catch (error) { - logger.warn('Failed to load registry provider-models.pb', { error }) + logger.warn('Failed to load registry provider-models.json', { error }) return [] } } - /** - * Load and cache registry providers from providers.pb - */ private loadRegistryProviders(): ProtoProviderConfig[] { if (this.registryProviders) return this.registryProviders try { const dataPath = this.getRegistryDataPath() - const data = readProviderRegistry(join(dataPath, 'providers.pb')) + const data = readProviderRegistry(join(dataPath, 'providers.json')) const providers = data.providers ?? [] this.registryProviders = providers return providers } catch (error) { - logger.warn('Failed to load registry providers.pb', { error }) + logger.warn('Failed to load registry providers.json', { error }) return [] } } - /** - * Get provider reasoning config from registry data. - */ private getRegistryReasoningConfig(providerId: string): { defaultChatEndpoint?: EndpointType reasoningFormatTypes?: Partial> } { const providers = this.loadRegistryProviders() const provider = providers.find((p) => p.id === providerId) - const endpointConfigs = provider ? buildEndpointConfigsFromProto(provider) : null + const endpointConfigs = provider ? buildRuntimeEndpointConfigs(provider.endpointConfigs) : null return { defaultChatEndpoint: provider?.defaultChatEndpoint, @@ -193,9 +152,6 @@ export class ProviderRegistryService extends BaseService { } } - /** - * Get provider reasoning config, preferring persisted provider data when available. - */ private async getEffectiveReasoningConfig(providerId: string): Promise<{ defaultChatEndpoint?: EndpointType reasoningFormatTypes?: Partial> @@ -216,28 +172,17 @@ export class ProviderRegistryService extends BaseService { const reasoningFormatTypes = extractReasoningFormatTypes(provider.endpointConfigs) ?? registryConfig.reasoningFormatTypes - return { - defaultChatEndpoint, - reasoningFormatTypes - } + return { defaultChatEndpoint, reasoningFormatTypes } } return registryConfig } - /** - * Initialize models for a specific provider - * - * Reads registry data, merges configurations, and writes to SQLite. - * - * @param providerId - The provider ID to initialize models for - */ async initializeProvider(providerId: string): Promise { const registryModels = this.loadRegistryModels() const providerModels = this.loadProviderModels() const { defaultChatEndpoint, reasoningFormatTypes } = await this.getEffectiveReasoningConfig(providerId) - // Find all overrides for this provider const overrides = providerModels.filter((pm) => pm.providerId === providerId) if (overrides.length === 0) { @@ -245,13 +190,11 @@ export class ProviderRegistryService extends BaseService { return [] } - // Build a map of registry models by ID for fast lookup const modelMap = new Map() for (const model of registryModels) { modelMap.set(model.id, model) } - // Merge each override with its base model const mergedModels: Model[] = [] const dbRows: NewUserModel[] = [] @@ -259,18 +202,13 @@ export class ProviderRegistryService extends BaseService { const baseModel = modelMap.get(override.modelId) ?? null if (!baseModel) { - logger.warn('Base model not found for override', { - providerId, - modelId: override.modelId - }) + logger.warn('Base model not found for override', { providerId, modelId: override.modelId }) continue } - // Merge: no user override (null), registry override, preset model const merged = mergeModelConfig(null, override, baseModel, providerId, reasoningFormatTypes, defaultChatEndpoint) mergedModels.push(merged) - // Convert to DB row format — capabilities/modalities are now numeric arrays dbRows.push({ providerId, modelId: baseModel.id, @@ -292,29 +230,20 @@ export class ProviderRegistryService extends BaseService { }) } - // Batch upsert to database await modelService.batchUpsert(dbRows) - logger.info('Initialized provider models from registry', { - providerId, - count: mergedModels.length - }) + logger.info('Initialized provider models from registry', { providerId, count: mergedModels.length }) return mergedModels } - /** - * Get registry preset models for a provider (read-only, no DB writes). - */ getRegistryModelsByProvider(providerId: string): Model[] { const registryModels = this.loadRegistryModels() const providerModels = this.loadProviderModels() const { defaultChatEndpoint, reasoningFormatTypes } = this.getRegistryReasoningConfig(providerId) const overrides = providerModels.filter((pm) => pm.providerId === providerId) - if (overrides.length === 0) { - return [] - } + if (overrides.length === 0) return [] const modelMap = new Map() for (const model of registryModels) { @@ -324,9 +253,7 @@ export class ProviderRegistryService extends BaseService { const mergedModels: Model[] = [] for (const override of overrides) { const baseModel = modelMap.get(override.modelId) ?? null - if (!baseModel) { - continue - } + if (!baseModel) continue mergedModels.push( mergeModelConfig(null, override, baseModel, providerId, reasoningFormatTypes, defaultChatEndpoint) ) @@ -335,26 +262,19 @@ export class ProviderRegistryService extends BaseService { return mergedModels } - /** - * Initialize preset providers from registry into SQLite. - * - * Reads providers.pb, maps fields to NewUserProvider, and batch upserts. - * Also seeds the cherryai provider which is not in providers.pb. - */ async initializePresetProviders(): Promise { const dataPath = this.getRegistryDataPath() let rawProviders: ReturnType['providers'] = [] try { - const data = readProviderRegistry(join(dataPath, 'providers.pb')) + const data = readProviderRegistry(join(dataPath, 'providers.json')) rawProviders = data.providers } catch (error) { - logger.warn('Failed to load providers.pb for provider import', { error }) + logger.warn('Failed to load providers.json for provider import', { error }) return } const dbRows: NewUserProvider[] = rawProviders.map((p) => { - // Map registry metadata.website to runtime websites field const registryWebsite = p.metadata?.website const websites = registryWebsite && @@ -378,8 +298,7 @@ export class ProviderRegistryService extends BaseService { } : null - // Build unified endpointConfigs from proto baseUrls + modelsApiUrls + reasoningFormat - const endpointConfigs = buildEndpointConfigsFromProto(p) + const endpointConfigs = buildRuntimeEndpointConfigs(p.endpointConfigs) return { providerId: p.id, @@ -408,11 +327,6 @@ export class ProviderRegistryService extends BaseService { logger.info('Initialized preset providers from registry', { count: dbRows.length }) } - /** - * Initialize all preset providers from registry - * - * Seeds provider configurations and enriches existing user models with registry data. - */ private async initializeAllPresetProviders(): Promise { await this.initializePresetProviders() await this.enrichExistingModels() @@ -420,19 +334,6 @@ export class ProviderRegistryService extends BaseService { logger.info('Initialized all preset providers and enriched existing models') } - /** - * Enrich existing user models with registry data - * - * For each user model that has a presetModelId, looks up the registry model - * and updates capabilities, modalities, contextWindow, maxOutputTokens, - * reasoning, pricing, etc. - * - * This bridges the gap between: - * - Migration: inserts user models with null registry fields - * - Registry: has rich model metadata (capabilities, limits, pricing) - * - * Uses presetModelId to match user models to registry models. - */ async enrichExistingModels(): Promise { const registryModels = this.loadRegistryModels() const providerModels = this.loadProviderModels() @@ -442,7 +343,6 @@ export class ProviderRegistryService extends BaseService { return } - // Build lookup maps const modelMap = new Map() for (const m of registryModels) { modelMap.set(m.id, m) @@ -458,7 +358,6 @@ export class ProviderRegistryService extends BaseService { providerMap.set(pm.modelId, pm) } - // Query all user models that have a presetModelId const db = application.get('DbService').getDb() const userModels = await db.select().from(userModelTable).where(isNotNull(userModelTable.presetModelId)) @@ -495,7 +394,6 @@ export class ProviderRegistryService extends BaseService { const reasoningFormatTypes = extractReasoningFormatTypes(providerConfig?.endpointConfigs) ?? registryReasoningConfig.reasoningFormatTypes - // Merge registry data with user data const merged = mergeModelConfig( { providerId: row.providerId, @@ -555,12 +453,6 @@ export class ProviderRegistryService extends BaseService { }) } - /** - * Look up registry data for a single model - * - * Returns the preset base model and provider-level override (if any). - * Used by ModelService.create to auto-enrich models at save time. - */ async lookupModel( providerId: string, modelId: string @@ -580,16 +472,6 @@ export class ProviderRegistryService extends BaseService { return { presetModel, registryOverride, ...reasoningConfig } } - /** - * Resolve raw model entries against registry data - * - * For each raw entry, looks up registry preset + provider override - * and produces an enriched Model via mergeModelConfig. - * Models not found in registry are returned with minimal data. - * - * Used by the renderer to display enriched models in ManageModelsPopup - * before the user adds them. - */ async resolveModels( providerId: string, rawModels: Array<{ @@ -597,14 +479,13 @@ export class ProviderRegistryService extends BaseService { name?: string group?: string description?: string - endpointTypes?: number[] + endpointTypes?: string[] }> ): Promise { const registryModels = this.loadRegistryModels() const providerModels = this.loadProviderModels() const { defaultChatEndpoint, reasoningFormatTypes } = await this.getEffectiveReasoningConfig(providerId) - // Build lookup maps const modelMap = new Map() for (const m of registryModels) { modelMap.set(m.id, m) @@ -626,7 +507,6 @@ export class ProviderRegistryService extends BaseService { const presetModel = modelMap.get(raw.modelId) ?? null const registryOverride = overrideMap.get(raw.modelId) ?? null - // Build a minimal user row from the raw entry const userRow = { providerId, modelId: raw.modelId, @@ -639,7 +519,6 @@ export class ProviderRegistryService extends BaseService { try { if (presetModel) { - // Registry match found — merge with preset data results.push( mergeModelConfig( userRow, @@ -651,7 +530,6 @@ export class ProviderRegistryService extends BaseService { ) ) } else { - // No registry match — return as custom model (no presetModelId) results.push(mergeModelConfig({ ...userRow, presetModelId: null }, null, null, providerId)) } } catch (error) { @@ -662,9 +540,6 @@ export class ProviderRegistryService extends BaseService { return results } - /** - * Clear cached registry data - */ clearCache(): void { this.registryModels = null this.registryProviderModels = null From 985d90a9cbe6b521b13e001b3f470e4c1c9367b9 Mon Sep 17 00:00:00 2001 From: suyao Date: Tue, 7 Apr 2026 15:37:15 +0800 Subject: [PATCH 23/29] fix: relax VersionSchema regex to accept dot-separated dates The registry data uses "2026.03.09" format (from proto), but the VersionSchema regex only allowed "YYYY-MM-DD". This caused Zod validation to fail silently when loading registry JSON, resulting in empty model data and no enrichment of capabilities/modalities. Co-Authored-By: Claude Opus 4.6 Signed-off-by: suyao --- packages/provider-registry/src/schemas/common.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/provider-registry/src/schemas/common.ts b/packages/provider-registry/src/schemas/common.ts index 3ad5c2c26b0..459a2e39625 100644 --- a/packages/provider-registry/src/schemas/common.ts +++ b/packages/provider-registry/src/schemas/common.ts @@ -11,9 +11,9 @@ import { Currency, objectValues } from './enums' export const ModelIdSchema = z.string() export const ProviderIdSchema = z.string() -/** Version in YYYY-MM-DD format */ -export const VersionSchema = z.string().regex(/^\d{4}-\d{2}-\d{2}$/, { - message: 'Version must be in YYYY-MM-DD format' +/** Version string (e.g., "2026-03-09" or "2026.03.09") */ +export const VersionSchema = z.string().regex(/^\d{4}[-./]\d{2}[-./]\d{2}$/, { + message: 'Version must be a date-like string (e.g., YYYY-MM-DD or YYYY.MM.DD)' }) /** ISO 8601 datetime timestamp */ From d92418731a7b953d9cc91909be8a7b89b0943fc1 Mon Sep 17 00:00:00 2001 From: suyao Date: Tue, 7 Apr 2026 15:43:58 +0800 Subject: [PATCH 24/29] fix: separate Node.js registry reader from browser-safe exports registry-reader.ts uses node:fs which crashes in renderer. Split into a separate @cherrystudio/provider-registry/node sub-path export so the main entry stays browser-compatible. Also fix endpointType casts from number to string in renderer popups. Co-Authored-By: Claude Opus 4.6 Signed-off-by: suyao --- electron.vite.config.ts | 2 ++ packages/provider-registry/package.json | 6 ++++++ packages/provider-registry/src/index.ts | 3 --- src/main/data/services/ProviderRegistryService.ts | 4 ++-- .../ProviderSettings/ModelList/NewApiAddModelPopup.tsx | 2 +- .../ProviderSettings/ModelList/NewApiBatchAddModelPopup.tsx | 2 +- tsconfig.node.json | 1 + 7 files changed, 13 insertions(+), 7 deletions(-) diff --git a/electron.vite.config.ts b/electron.vite.config.ts index 25ff98f1a15..d817c8f8431 100644 --- a/electron.vite.config.ts +++ b/electron.vite.config.ts @@ -36,6 +36,7 @@ export default defineConfig({ '@logger': resolve('src/main/services/LoggerService'), '@mcp-trace/trace-core': resolve('packages/mcp-trace/trace-core'), '@mcp-trace/trace-node': resolve('packages/mcp-trace/trace-node'), + '@cherrystudio/provider-registry/node': resolve('packages/provider-registry/src/registry-reader'), '@cherrystudio/provider-registry': resolve('packages/provider-registry/src'), '@test-mocks': resolve('tests/__mocks__') } @@ -117,6 +118,7 @@ export default defineConfig({ '@cherrystudio/ai-core': resolve('packages/aiCore/src'), '@cherrystudio/extension-table-plus': resolve('packages/extension-table-plus/src'), '@cherrystudio/ai-sdk-provider': resolve('packages/ai-sdk-provider/src'), + '@cherrystudio/provider-registry/node': resolve('packages/provider-registry/src/registry-reader'), '@cherrystudio/provider-registry': resolve('packages/provider-registry/src'), '@cherrystudio/ui/icons': resolve('packages/ui/src/components/icons'), '@cherrystudio/ui': resolve('packages/ui/src'), diff --git a/packages/provider-registry/package.json b/packages/provider-registry/package.json index 726cf80e763..88bf5438575 100644 --- a/packages/provider-registry/package.json +++ b/packages/provider-registry/package.json @@ -41,6 +41,12 @@ "import": "./dist/index.mjs", "require": "./dist/index.js", "default": "./dist/index.js" + }, + "./node": { + "types": "./src/registry-reader.ts", + "import": "./dist/registry-reader.mjs", + "require": "./dist/registry-reader.js", + "default": "./dist/registry-reader.js" } }, "devDependencies": { diff --git a/packages/provider-registry/src/index.ts b/packages/provider-registry/src/index.ts index c31afe9d447..07fd782b604 100644 --- a/packages/provider-registry/src/index.ts +++ b/packages/provider-registry/src/index.ts @@ -39,8 +39,5 @@ export type { ProviderModelOverride } from './schemas/provider-models' -// Registry reader (read .json files and return validated types) -export { readModelRegistry, readProviderModelRegistry, readProviderRegistry } from './registry-reader' - // Model ID normalization utilities export { normalizeModelId } from './utils/importers/base/base-transformer' diff --git a/src/main/data/services/ProviderRegistryService.ts b/src/main/data/services/ProviderRegistryService.ts index 261ed8f2fc9..c355fa36c96 100644 --- a/src/main/data/services/ProviderRegistryService.ts +++ b/src/main/data/services/ProviderRegistryService.ts @@ -17,12 +17,12 @@ import type { ProtoProviderModelOverride, RegistryEndpointConfig } from '@cherrystudio/provider-registry' +import { EndpointType } from '@cherrystudio/provider-registry' import { - EndpointType, readModelRegistry, readProviderModelRegistry, readProviderRegistry -} from '@cherrystudio/provider-registry' +} from '@cherrystudio/provider-registry/node' import type { NewUserModel } from '@data/db/schemas/userModel' import { userModelTable } from '@data/db/schemas/userModel' import type { NewUserProvider } from '@data/db/schemas/userProvider' diff --git a/src/renderer/src/pages/settings/ProviderSettings/ModelList/NewApiAddModelPopup.tsx b/src/renderer/src/pages/settings/ProviderSettings/ModelList/NewApiAddModelPopup.tsx index e4da0e881c3..608e4404525 100644 --- a/src/renderer/src/pages/settings/ProviderSettings/ModelList/NewApiAddModelPopup.tsx +++ b/src/renderer/src/pages/settings/ProviderSettings/ModelList/NewApiAddModelPopup.tsx @@ -64,7 +64,7 @@ const PopupContainer: React.FC = ({ title, provider, resolve, model, endp modelId, name: values.name ? values.name : modelId.toUpperCase(), group: values.group ?? getDefaultGroupName(modelId), - endpointTypes: isNewApiProvider(provider) && values.endpointType ? [values.endpointType as number] : undefined + endpointTypes: isNewApiProvider(provider) && values.endpointType ? [values.endpointType as string] : undefined }) return true diff --git a/src/renderer/src/pages/settings/ProviderSettings/ModelList/NewApiBatchAddModelPopup.tsx b/src/renderer/src/pages/settings/ProviderSettings/ModelList/NewApiBatchAddModelPopup.tsx index 6c864e67426..e3ed918f5e4 100644 --- a/src/renderer/src/pages/settings/ProviderSettings/ModelList/NewApiBatchAddModelPopup.tsx +++ b/src/renderer/src/pages/settings/ProviderSettings/ModelList/NewApiBatchAddModelPopup.tsx @@ -52,7 +52,7 @@ const PopupContainer: React.FC = ({ title, provider, resolve, batchModels modelId, name: model.name, group: model.group, - endpointTypes: values.endpointType ? [values.endpointType as number] : undefined + endpointTypes: values.endpointType ? [values.endpointType as string] : undefined }) } return true diff --git a/tsconfig.node.json b/tsconfig.node.json index 8d3957a8dec..863c228eafd 100644 --- a/tsconfig.node.json +++ b/tsconfig.node.json @@ -7,6 +7,7 @@ "moduleResolution": "bundler", "paths": { "@cherrystudio/provider-registry": ["./packages/provider-registry/src/index.ts"], + "@cherrystudio/provider-registry/node": ["./packages/provider-registry/src/registry-reader.ts"], "@data/*": ["./src/main/data/*"], "@logger": ["./src/main/services/LoggerService"], "@main/*": ["./src/main/*"], From 702eec133d7750a2a0dc95e421602a612a668da3 Mon Sep 17 00:00:00 2001 From: suyao Date: Tue, 7 Apr 2026 16:00:03 +0800 Subject: [PATCH 25/29] fix: remove unnecessary type assertions flagged by oxlint Co-Authored-By: Claude Opus 4.6 Signed-off-by: suyao --- packages/shared/data/utils/modelMerger.ts | 3 +-- src/main/data/services/ProviderRegistryService.ts | 3 +-- src/renderer/src/data/hooks/useProviders.ts | 2 +- .../src/pages/settings/ProviderSettings/ProviderList.tsx | 2 +- .../src/pages/settings/ProviderSettings/ProviderSetting.tsx | 2 +- 5 files changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/shared/data/utils/modelMerger.ts b/packages/shared/data/utils/modelMerger.ts index 6dbdf94af4d..889257c9784 100644 --- a/packages/shared/data/utils/modelMerger.ts +++ b/packages/shared/data/utils/modelMerger.ts @@ -435,8 +435,7 @@ function buildPresetEndpointConfigs( if (regConfig.baseUrl) config.baseUrl = regConfig.baseUrl if (regConfig.modelsApiUrls) config.modelsApiUrls = regConfig.modelsApiUrls - if (regConfig.reasoningFormat?.type) - config.reasoningFormatType = regConfig.reasoningFormat.type as ReasoningFormatType + if (regConfig.reasoningFormat?.type) config.reasoningFormatType = regConfig.reasoningFormat.type if (Object.keys(config).length > 0) { configs[ep] = config diff --git a/src/main/data/services/ProviderRegistryService.ts b/src/main/data/services/ProviderRegistryService.ts index c355fa36c96..9834a7b71e4 100644 --- a/src/main/data/services/ProviderRegistryService.ts +++ b/src/main/data/services/ProviderRegistryService.ts @@ -59,8 +59,7 @@ function buildRuntimeEndpointConfigs( if (regConfig.baseUrl) config.baseUrl = regConfig.baseUrl if (regConfig.modelsApiUrls) config.modelsApiUrls = regConfig.modelsApiUrls - if (regConfig.reasoningFormat?.type) - config.reasoningFormatType = regConfig.reasoningFormat.type as ReasoningFormatType + if (regConfig.reasoningFormat?.type) config.reasoningFormatType = regConfig.reasoningFormat.type if (Object.keys(config).length > 0) configs[ep] = config } diff --git a/src/renderer/src/data/hooks/useProviders.ts b/src/renderer/src/data/hooks/useProviders.ts index 3f110a6caaa..2f7027a52b5 100644 --- a/src/renderer/src/data/hooks/useProviders.ts +++ b/src/renderer/src/data/hooks/useProviders.ts @@ -34,7 +34,7 @@ export function useProviders(query?: { enabled?: boolean }) { [mutate] ) - const providers = useMemo(() => (data as Provider[] | undefined) ?? EMPTY_PROVIDERS, [data]) + const providers = useMemo(() => data ?? EMPTY_PROVIDERS, [data]) return { providers, diff --git a/src/renderer/src/pages/settings/ProviderSettings/ProviderList.tsx b/src/renderer/src/pages/settings/ProviderSettings/ProviderList.tsx index c291f6443a4..5698d00da3a 100644 --- a/src/renderer/src/pages/settings/ProviderSettings/ProviderList.tsx +++ b/src/renderer/src/pages/settings/ProviderSettings/ProviderList.tsx @@ -205,7 +205,7 @@ const ProviderList: FC = ({ isOnboarding = false }) => { } } - const newProvider = (await addProvider({ providerId, name: providerName.trim() })) as Provider + const newProvider = await addProvider({ providerId, name: providerName.trim() }) setSelectedProvider(newProvider) } diff --git a/src/renderer/src/pages/settings/ProviderSettings/ProviderSetting.tsx b/src/renderer/src/pages/settings/ProviderSettings/ProviderSetting.tsx index 321bc307279..ab815b51e7e 100644 --- a/src/renderer/src/pages/settings/ProviderSettings/ProviderSetting.tsx +++ b/src/renderer/src/pages/settings/ProviderSettings/ProviderSetting.tsx @@ -597,7 +597,7 @@ const ProviderSettingContent: FC = ({ provider, providerId, isOnbo setActiveHostField(value as HostField)} + onChange={(value) => setActiveHostField(value)} options={hostSelectorOptions} style={{ paddingLeft: 1, fontWeight: 'bold' }} placement="bottomLeft" From 6cf2d7ed88496a81acc751e9220c6f5b80a9223d Mon Sep 17 00:00:00 2001 From: suyao Date: Tue, 7 Apr 2026 16:10:39 +0800 Subject: [PATCH 26/29] fix: resolve CI lint and type errors from v2 merge - Add void to all floating promises in ProviderSettings components - Remove closeDelay prop (not in TooltipProps) - Fix unused destructured variable with delete pattern Co-Authored-By: Claude Opus 4.6 Signed-off-by: suyao --- .../ProviderSettings/AddProviderPopup.tsx | 4 ++-- .../ApiOptionsSettings/ApiOptionsSettings.tsx | 2 +- .../settings/ProviderSettings/CherryINOAuth.tsx | 2 +- .../ProviderSettings/CustomHeaderPopup.tsx | 2 +- .../ModelList/ManageModelsList.tsx | 2 +- .../ModelList/ManageModelsPopup.tsx | 6 +++--- .../ProviderSettings/ModelList/ModelList.tsx | 8 ++++---- .../settings/ProviderSettings/ProviderList.tsx | 12 ++++++------ .../ProviderSettings/ProviderSetting.tsx | 17 +++++++++-------- 9 files changed, 28 insertions(+), 27 deletions(-) diff --git a/src/renderer/src/pages/settings/ProviderSettings/AddProviderPopup.tsx b/src/renderer/src/pages/settings/ProviderSettings/AddProviderPopup.tsx index 5ff9d5d55d2..f51830abc5c 100644 --- a/src/renderer/src/pages/settings/ProviderSettings/AddProviderPopup.tsx +++ b/src/renderer/src/pages/settings/ProviderSettings/AddProviderPopup.tsx @@ -43,7 +43,7 @@ const PopupContainer: React.FC = ({ provider, resolve }) => { logger.error('Failed to load logo', error as Error) } } - loadLogo() + void loadLogo() } }, [provider]) @@ -241,7 +241,7 @@ const PopupContainer: React.FC = ({ provider, resolve }) => { placeholder={t('settings.provider.add.name.placeholder')} onKeyDown={(e) => { if (e.key === 'Enter' && !e.nativeEvent.isComposing) { - onOk() + void onOk() } }} maxLength={32} diff --git a/src/renderer/src/pages/settings/ProviderSettings/ApiOptionsSettings/ApiOptionsSettings.tsx b/src/renderer/src/pages/settings/ProviderSettings/ApiOptionsSettings/ApiOptionsSettings.tsx index 5759a9c8bf8..008388c9a97 100644 --- a/src/renderer/src/pages/settings/ProviderSettings/ApiOptionsSettings/ApiOptionsSettings.tsx +++ b/src/renderer/src/pages/settings/ProviderSettings/ApiOptionsSettings/ApiOptionsSettings.tsx @@ -25,7 +25,7 @@ const ApiOptionsSettings = ({ providerId }: Props) => { const patchProvider = useCallback( (updates: Record) => { startTransition(() => { - updateProvider(updates) + void updateProvider(updates) }) }, [updateProvider] diff --git a/src/renderer/src/pages/settings/ProviderSettings/CherryINOAuth.tsx b/src/renderer/src/pages/settings/ProviderSettings/CherryINOAuth.tsx index d3c4a8ba965..1af3140a135 100644 --- a/src/renderer/src/pages/settings/ProviderSettings/CherryINOAuth.tsx +++ b/src/renderer/src/pages/settings/ProviderSettings/CherryINOAuth.tsx @@ -74,7 +74,7 @@ const CherryINOAuth: FC = ({ providerId }) => { useEffect(() => { // Only fetch balance if logged in via OAuth if (isOAuthLoggedIn) { - fetchData() + void fetchData() } else { setBalanceInfo(null) } diff --git a/src/renderer/src/pages/settings/ProviderSettings/CustomHeaderPopup.tsx b/src/renderer/src/pages/settings/ProviderSettings/CustomHeaderPopup.tsx index bbb9accc660..c59e00a4771 100644 --- a/src/renderer/src/pages/settings/ProviderSettings/CustomHeaderPopup.tsx +++ b/src/renderer/src/pages/settings/ProviderSettings/CustomHeaderPopup.tsx @@ -43,7 +43,7 @@ const PopupContainer: React.FC = ({ providerId, resolve }) => { updateDefaultHeaders(parsedHeaders) } - updateProvider({ providerSettings: { ...provider?.settings, extraHeaders: parsedHeaders } }) + void updateProvider({ providerSettings: { ...provider?.settings, extraHeaders: parsedHeaders } }) window.toast.success(t('message.save.success.title')) } catch (error) { diff --git a/src/renderer/src/pages/settings/ProviderSettings/ModelList/ManageModelsList.tsx b/src/renderer/src/pages/settings/ProviderSettings/ModelList/ManageModelsList.tsx index d3b697b8746..88fe84957a0 100644 --- a/src/renderer/src/pages/settings/ProviderSettings/ModelList/ManageModelsList.tsx +++ b/src/renderer/src/pages/settings/ProviderSettings/ModelList/ManageModelsList.tsx @@ -103,7 +103,7 @@ const ManageModelsList: React.FC = ({ if (wouldAddModels.every(isValidNewApiModel)) { wouldAddModels.forEach(onAddModel) } else { - NewApiBatchAddModelPopup.show({ + void NewApiBatchAddModelPopup.show({ title: t('settings.models.add.batch_add_models'), batchModels: wouldAddModels, provider diff --git a/src/renderer/src/pages/settings/ProviderSettings/ModelList/ManageModelsPopup.tsx b/src/renderer/src/pages/settings/ProviderSettings/ModelList/ManageModelsPopup.tsx index fbba3c5a38d..e2c9510da16 100644 --- a/src/renderer/src/pages/settings/ProviderSettings/ModelList/ManageModelsPopup.tsx +++ b/src/renderer/src/pages/settings/ProviderSettings/ModelList/ManageModelsPopup.tsx @@ -145,7 +145,7 @@ const PopupContainer: React.FC = ({ providerId, resolve }) => { if (endpointTypes && endpointTypes.length > 0) { await createModel({ providerId, modelId, name: model.name, group: model.group, endpointTypes }) } else { - NewApiAddModelPopup.show({ + void NewApiAddModelPopup.show({ title: t('settings.models.add.add_model'), provider: provider as any, model: model as any @@ -182,7 +182,7 @@ const PopupContainer: React.FC = ({ providerId, resolve }) => { if (existingModels.every((m: any) => isValidNewApiModel(m))) { wouldAddModel.forEach((m) => onAddModel(m)) } else { - NewApiBatchAddModelPopup.show({ + void NewApiBatchAddModelPopup.show({ title: t('settings.models.add.batch_add_models'), batchModels: wouldAddModel as any, provider: provider as any @@ -244,7 +244,7 @@ const PopupContainer: React.FC = ({ providerId, resolve }) => { ) useEffect(() => { - if (provider) loadModels(provider) + if (provider) void loadModels(provider) // eslint-disable-next-line react-hooks/exhaustive-deps }, [provider?.id]) diff --git a/src/renderer/src/pages/settings/ProviderSettings/ModelList/ModelList.tsx b/src/renderer/src/pages/settings/ProviderSettings/ModelList/ModelList.tsx index 79df4dafd79..99386a48ae1 100644 --- a/src/renderer/src/pages/settings/ProviderSettings/ModelList/ModelList.tsx +++ b/src/renderer/src/pages/settings/ProviderSettings/ModelList/ModelList.tsx @@ -131,15 +131,15 @@ const ModelList: React.FC = ({ providerId }) => { }, [displayedModelGroups]) const onManageModel = useCallback(() => { - if (provider) ManageModelsPopup.show({ providerId: provider.id }) + if (provider) void ManageModelsPopup.show({ providerId: provider.id }) }, [provider]) const onAddModel = useCallback(() => { if (!provider) return if (isNewApiProvider(provider)) { - NewApiAddModelPopup.show({ title: t('settings.models.add.add_model'), provider: provider as any }) + void NewApiAddModelPopup.show({ title: t('settings.models.add.add_model'), provider: provider as any }) } else { - AddModelPopup.show({ title: t('settings.models.add.add_model'), provider: provider as any }) + void AddModelPopup.show({ title: t('settings.models.add.add_model'), provider: provider as any }) } }, [provider, t]) @@ -168,7 +168,7 @@ const ModelList: React.FC = ({ providerId }) => { /> - + diff --git a/src/renderer/src/pages/settings/ProviderSettings/ProviderList.tsx b/src/renderer/src/pages/settings/ProviderSettings/ProviderList.tsx index 5698d00da3a..cb24899cfb8 100644 --- a/src/renderer/src/pages/settings/ProviderSettings/ProviderList.tsx +++ b/src/renderer/src/pages/settings/ProviderSettings/ProviderList.tsx @@ -94,7 +94,7 @@ const ProviderList: FC = ({ isOnboarding = false }) => { setProviderLogos(logos) } - loadAllLogos() + void loadAllLogos() }, [providers]) useEffect(() => { @@ -129,7 +129,7 @@ const ProviderList: FC = ({ isOnboarding = false }) => { // Ideal: define validateSearch on the route so navigate({ search }) is fully typed, // and consumed params can be cleared without manual filtering or type casts. const restSearch = Object.fromEntries(Object.entries(search).filter(([key]) => key !== 'filter' && key !== 'id')) - navigate({ to: '/settings/provider', search: restSearch as Record, replace: true }) + void navigate({ to: '/settings/provider', search: restSearch as Record, replace: true }) } // eslint-disable-next-line react-hooks/exhaustive-deps }, [providers, search.filter, search.id, navigate, setSelectedProvider, setTimeoutTimer]) @@ -146,7 +146,7 @@ const ProviderList: FC = ({ isOnboarding = false }) => { const { id } = data const { updatedProvider, isNew, displayName } = await UrlSchemaInfoPopup.show(data as any) - navigate({ to: '/settings/provider', search: { id } }) + void navigate({ to: '/settings/provider', search: { id } }) if (!updatedProvider) { return @@ -174,14 +174,14 @@ const ProviderList: FC = ({ isOnboarding = false }) => { const { id, apiKey: newApiKey, baseUrl, type, name } = JSON.parse(addProviderData) if (!id || !newApiKey || !baseUrl) { window.toast.error(t('settings.models.provider_key_add_failed_by_invalid_data')) - navigate({ to: '/settings/provider' }) + void navigate({ to: '/settings/provider' }) return } - handleProviderAddKey({ id, apiKey: newApiKey, baseUrl, type, name }) + void handleProviderAddKey({ id, apiKey: newApiKey, baseUrl, type, name }) } catch (error) { window.toast.error(t('settings.models.provider_key_add_failed_by_invalid_data')) - navigate({ to: '/settings/provider' }) + void navigate({ to: '/settings/provider' }) } // eslint-disable-next-line react-hooks/exhaustive-deps }, [search.addProviderData]) diff --git a/src/renderer/src/pages/settings/ProviderSettings/ProviderSetting.tsx b/src/renderer/src/pages/settings/ProviderSettings/ProviderSetting.tsx index ab815b51e7e..fa31da76bf4 100644 --- a/src/renderer/src/pages/settings/ProviderSettings/ProviderSetting.tsx +++ b/src/renderer/src/pages/settings/ProviderSettings/ProviderSetting.tsx @@ -209,7 +209,7 @@ const ProviderSettingContent: FC = ({ provider, providerId, isOnbo // 同步 localApiKey 到 provider(防抖) useEffect(() => { if (localApiKey !== providerApiKey) { - debouncedUpdateApiKey(localApiKey) + void debouncedUpdateApiKey(localApiKey) } // 卸载时取消任何待执行的更新 @@ -231,7 +231,7 @@ const ProviderSettingContent: FC = ({ provider, providerId, isOnbo return } if (isVertexProvider(provider) || apiHost.trim()) { - patchProvider({ + void patchProvider({ endpointConfigs: { ...provider.endpointConfigs, [primaryEndpoint]: { ...provider.endpointConfigs?.[primaryEndpoint], baseUrl: apiHost } @@ -246,7 +246,7 @@ const ProviderSettingContent: FC = ({ provider, providerId, isOnbo const trimmedHost = anthropicApiHost?.trim() if (trimmedHost) { - patchProvider({ + void patchProvider({ endpointConfigs: { ...provider.endpointConfigs, [EndpointType.ANTHROPIC_MESSAGES]: { @@ -257,8 +257,9 @@ const ProviderSettingContent: FC = ({ provider, providerId, isOnbo }) setAnthropicHost(trimmedHost) } else { - const { [EndpointType.ANTHROPIC_MESSAGES]: _, ...restConfigs } = provider.endpointConfigs ?? {} - patchProvider({ endpointConfigs: restConfigs }) + const restConfigs = { ...provider.endpointConfigs } + delete restConfigs[EndpointType.ANTHROPIC_MESSAGES] + void patchProvider({ endpointConfigs: restConfigs }) setAnthropicHost(undefined) } } @@ -349,7 +350,7 @@ const ProviderSettingContent: FC = ({ provider, providerId, isOnbo const onReset = useCallback(() => { setApiHost(configuredApiHost) - patchProvider({ + void patchProvider({ endpointConfigs: { ...provider?.endpointConfigs, [primaryEndpoint]: { ...provider?.endpointConfigs?.[primaryEndpoint], baseUrl: configuredApiHost } @@ -499,7 +500,7 @@ const ProviderSettingContent: FC = ({ provider, providerId, isOnbo checked={provider.isEnabled} key={provider.id} onCheckedChange={(enabled) => { - patchProvider({ + void patchProvider({ isEnabled: enabled, endpointConfigs: { ...provider.endpointConfigs, @@ -507,7 +508,7 @@ const ProviderSettingContent: FC = ({ provider, providerId, isOnbo } }) if (enabled) { - moveProviderToTop() + void moveProviderToTop() } }} /> From 3e9cea9694509a0b6716f7fa38545eaa3c97618e Mon Sep 17 00:00:00 2001 From: suyao Date: Tue, 7 Apr 2026 20:13:41 +0800 Subject: [PATCH 27/29] fix: fix runtime crashes from numeric EndpointType usage in renderer - ManageModelsPopup: replace Math.floor(Number(defaultChatEndpoint)) with direct string enum access (was producing NaN after string enum migration, causing model list to fail loading) - provider.v2.ts: fix replaceEndpointConfigDomain type signature from Record to Record, remove Number(key) conversions - Update test file to use string EndpointType keys Co-Authored-By: Claude Opus 4.6 Signed-off-by: suyao --- .../ModelList/ManageModelsPopup.tsx | 4 +-- .../src/utils/__tests__/provider.v2.test.ts | 29 ++++++++++++------- src/renderer/src/utils/provider.v2.ts | 13 +++++---- 3 files changed, 28 insertions(+), 18 deletions(-) diff --git a/src/renderer/src/pages/settings/ProviderSettings/ModelList/ManageModelsPopup.tsx b/src/renderer/src/pages/settings/ProviderSettings/ModelList/ManageModelsPopup.tsx index e2c9510da16..f9f733b3650 100644 --- a/src/renderer/src/pages/settings/ProviderSettings/ModelList/ManageModelsPopup.tsx +++ b/src/renderer/src/pages/settings/ProviderSettings/ModelList/ManageModelsPopup.tsx @@ -21,7 +21,7 @@ import { filterModelsByKeywords } from '@renderer/utils' import { isFreeModel } from '@renderer/utils/model' import { getFancyProviderName, isNewApiProvider } from '@renderer/utils/provider.v2' import type { Model } from '@shared/data/types/model' -import { parseUniqueModelId } from '@shared/data/types/model' +import { EndpointType, parseUniqueModelId } from '@shared/data/types/model' import type { Provider } from '@shared/data/types/provider' import { Empty, Modal, Spin, Tabs } from 'antd' import Input from 'antd/es/input/Input' @@ -200,7 +200,7 @@ const PopupContainer: React.FC = ({ providerId, resolve }) => { setLoadingModels(true) try { // Bridge v2 Provider → v1 shape for fetchModels (reads apiHost/apiKey) - const endpointKey = Math.floor(Number(prov.defaultChatEndpoint ?? 1)) + const endpointKey = prov.defaultChatEndpoint ?? EndpointType.OPENAI_CHAT_COMPLETIONS const apiHost = prov.endpointConfigs?.[endpointKey]?.baseUrl ?? '' // Fetch key imperatively — useQuery may not have resolved yet at mount time let apiKey = '' diff --git a/src/renderer/src/utils/__tests__/provider.v2.test.ts b/src/renderer/src/utils/__tests__/provider.v2.test.ts index df4ee4e405a..14887598668 100644 --- a/src/renderer/src/utils/__tests__/provider.v2.test.ts +++ b/src/renderer/src/utils/__tests__/provider.v2.test.ts @@ -267,11 +267,14 @@ describe('provider.v2 - API Key helpers', () => { describe('provider.v2 - Endpoint config helpers', () => { it('replaceEndpointConfigDomain: replaces domain in all baseUrls while preserving paths', () => { const result = replaceEndpointConfigDomain( - { 1: { baseUrl: 'https://old.com/v1' }, 3: { baseUrl: 'https://old.com/anthropic' } }, + { + [EndpointType.OPENAI_CHAT_COMPLETIONS]: { baseUrl: 'https://old.com/v1' }, + [EndpointType.ANTHROPIC_MESSAGES]: { baseUrl: 'https://old.com/anthropic' } + }, 'new.com' ) - expect(result[1]?.baseUrl).toBe('https://new.com/v1') - expect(result[3]?.baseUrl).toBe('https://new.com/anthropic') + expect(result[EndpointType.OPENAI_CHAT_COMPLETIONS]?.baseUrl).toBe('https://new.com/v1') + expect(result[EndpointType.ANTHROPIC_MESSAGES]?.baseUrl).toBe('https://new.com/anthropic') }) it('replaceEndpointConfigDomain: returns empty object for undefined input', () => { @@ -279,22 +282,28 @@ describe('provider.v2 - Endpoint config helpers', () => { }) it('replaceEndpointConfigDomain: preserves invalid URLs unchanged', () => { - const result = replaceEndpointConfigDomain({ 1: { baseUrl: 'not-a-url' } }, 'new.com') - expect(result[1]?.baseUrl).toBe('not-a-url') + const result = replaceEndpointConfigDomain( + { [EndpointType.OPENAI_CHAT_COMPLETIONS]: { baseUrl: 'not-a-url' } }, + 'new.com' + ) + expect(result[EndpointType.OPENAI_CHAT_COMPLETIONS]?.baseUrl).toBe('not-a-url') }) it('replaceEndpointConfigDomain: handles URLs with ports and paths', () => { - const result = replaceEndpointConfigDomain({ 6: { baseUrl: 'http://localhost:11434/api' } }, '192.168.1.100') - expect(result[6]?.baseUrl).toBe('http://192.168.1.100:11434/api') + const result = replaceEndpointConfigDomain( + { [EndpointType.OLLAMA_CHAT]: { baseUrl: 'http://localhost:11434/api' } }, + '192.168.1.100' + ) + expect(result[EndpointType.OLLAMA_CHAT]?.baseUrl).toBe('http://192.168.1.100:11434/api') }) it('replaceEndpointConfigDomain: preserves other EndpointConfig fields', () => { const result = replaceEndpointConfigDomain( - { 1: { baseUrl: 'https://old.com/v1', reasoningFormatType: 'openai-chat' } }, + { [EndpointType.OPENAI_CHAT_COMPLETIONS]: { baseUrl: 'https://old.com/v1', reasoningFormatType: 'openai-chat' } }, 'new.com' ) - expect(result[1]?.baseUrl).toBe('https://new.com/v1') - expect(result[1]?.reasoningFormatType).toBe('openai-chat') + expect(result[EndpointType.OPENAI_CHAT_COMPLETIONS]?.baseUrl).toBe('https://new.com/v1') + expect(result[EndpointType.OPENAI_CHAT_COMPLETIONS]?.reasoningFormatType).toBe('openai-chat') }) }) diff --git a/src/renderer/src/utils/provider.v2.ts b/src/renderer/src/utils/provider.v2.ts index 4fae4572008..d3fb4ca472f 100644 --- a/src/renderer/src/utils/provider.v2.ts +++ b/src/renderer/src/utils/provider.v2.ts @@ -134,24 +134,25 @@ export function hasApiKeys(provider: Provider): boolean { * Used by CherryIN/DMXAPI domain switching. */ export function replaceEndpointConfigDomain( - endpointConfigs: Partial> | undefined, + endpointConfigs: Partial> | undefined, newDomain: string -): Partial> { +): Partial> { if (!endpointConfigs) return {} - const result: Partial> = {} + const result: Partial> = {} for (const [key, config] of Object.entries(endpointConfigs)) { if (!config) continue + const ep = key as EndpointType const baseUrl = config.baseUrl if (!baseUrl) { - result[Number(key)] = config + result[ep] = config continue } try { const parsed = new URL(baseUrl) parsed.hostname = newDomain - result[Number(key)] = { ...config, baseUrl: parsed.toString().replace(/\/$/, '') } + result[ep] = { ...config, baseUrl: parsed.toString().replace(/\/$/, '') } } catch { - result[Number(key)] = config + result[ep] = config } } return result From 9336d81e389aad6fcc6ee0ef34f53954791abc81 Mon Sep 17 00:00:00 2001 From: jidan745le <420511176@qq.com> Date: Tue, 7 Apr 2026 20:15:02 +0800 Subject: [PATCH 28/29] fix: stabilize useModels empty array reference to prevent infinite re-render loop When data is undefined (loading), `data ?? []` created a new array reference on every render, causing ModelList's useEffect to loop indefinitely via setDisplayedModelGroups with a new object each cycle. Use a module-level EMPTY_MODELS constant with useMemo, matching the pattern already used by useProviders. Signed-off-by: jidan745le <420511176@qq.com> Made-with: Cursor --- src/renderer/src/data/hooks/useModels.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/renderer/src/data/hooks/useModels.ts b/src/renderer/src/data/hooks/useModels.ts index 0299ef2914b..19bdd65253f 100644 --- a/src/renderer/src/data/hooks/useModels.ts +++ b/src/renderer/src/data/hooks/useModels.ts @@ -2,7 +2,7 @@ import { dataApiService } from '@data/DataApiService' import type { ConcreteApiPaths } from '@shared/data/api/apiTypes' import type { CreateModelDto, UpdateModelDto } from '@shared/data/api/schemas/models' import type { Model } from '@shared/data/types/model' -import { useCallback } from 'react' +import { useCallback, useMemo } from 'react' import { useInvalidateCache, useMutation, useQuery } from './useDataApi' @@ -12,6 +12,7 @@ function modelPath(providerId: string, modelId: string): ConcreteApiPaths { } const REFRESH_MODELS = ['/models'] as const +const EMPTY_MODELS: Model[] = [] // ─── Layer 1: List ──────────────────────────────────────────────────── export function useModels(query?: { providerId?: string; enabled?: boolean }) { @@ -24,7 +25,9 @@ export function useModels(query?: { providerId?: string; enabled?: boolean }) { ...(enabled !== undefined ? { enabled } : {}) }) as { data: Model[] | undefined; isLoading: boolean; mutate: any } - return { models: data ?? [], isLoading, refetch: mutate } + const models = useMemo(() => data ?? EMPTY_MODELS, [data]) + + return { models, isLoading, refetch: mutate } } // ─── Layer 2: Mutations ─────────────────────────────────────────────── From 15e94e40f49dd620ccbedbfef0f33ab35a6500fe Mon Sep 17 00:00:00 2001 From: jidan745le <420511176@qq.com> Date: Tue, 7 Apr 2026 21:16:13 +0800 Subject: [PATCH 29/29] fix: fallback undefined model group to providerId in ManageModelsPopup Signed-off-by: jidan745le <420511176@qq.com> Made-with: Cursor --- .../ModelList/ManageModelsPopup.tsx | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/renderer/src/pages/settings/ProviderSettings/ModelList/ManageModelsPopup.tsx b/src/renderer/src/pages/settings/ProviderSettings/ModelList/ManageModelsPopup.tsx index f9f733b3650..5b65d07dbc5 100644 --- a/src/renderer/src/pages/settings/ProviderSettings/ModelList/ManageModelsPopup.tsx +++ b/src/renderer/src/pages/settings/ProviderSettings/ModelList/ManageModelsPopup.tsx @@ -116,19 +116,18 @@ const PopupContainer: React.FC = ({ providerId, resolve }) => { [filterSearchText, actualFilterType, allModels] ) - const modelGroups = useMemo( - () => - provider?.id === 'dashscope' - ? { - ...groupBy( - list.filter((model) => !model.id.startsWith('qwen')), - 'group' - ), - ...groupQwenModels(list.filter((model) => model.id.startsWith('qwen'))) - } - : groupBy(list, 'group'), - [list, provider?.id] - ) + const modelGroups = useMemo(() => { + const groupFn = (m: Model) => m.group || provider?.id || '' + return provider?.id === 'dashscope' + ? { + ...groupBy( + list.filter((model) => !model.id.startsWith('qwen')), + groupFn + ), + ...groupQwenModels(list.filter((model) => model.id.startsWith('qwen'))) + } + : groupBy(list, groupFn) + }, [list, provider?.id]) const onOk = useCallback(() => setOpen(false), [])