@theokit/sdk 4.24.0 → 4.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/a2a/index.cjs +10 -171
- package/dist/a2a/index.cjs.map +1 -1
- package/dist/a2a/index.js +3 -170
- package/dist/a2a/index.js.map +1 -1
- package/dist/agent-factory-registry-UU46BUMU.cjs +16 -0
- package/dist/agent-factory-registry-UU46BUMU.cjs.map +1 -0
- package/dist/agent-factory-registry-ZVTABLEY.js +3 -0
- package/dist/agent-factory-registry-ZVTABLEY.js.map +1 -0
- package/dist/auth/index.cjs +63 -367
- package/dist/auth/index.cjs.map +1 -1
- package/dist/auth/index.js +6 -342
- package/dist/auth/index.js.map +1 -1
- package/dist/batch-2FSD2UNL.cjs +244 -0
- package/dist/batch-2FSD2UNL.cjs.map +1 -0
- package/dist/batch-MR2HKYMS.js +241 -0
- package/dist/batch-MR2HKYMS.js.map +1 -0
- package/dist/chunk-2HRYBRD7.cjs +31 -0
- package/dist/chunk-2HRYBRD7.cjs.map +1 -0
- package/dist/chunk-2MF3IE2D.cjs +2863 -0
- package/dist/chunk-2MF3IE2D.cjs.map +1 -0
- package/dist/chunk-2ONFGMBV.js +6 -0
- package/dist/chunk-2ONFGMBV.js.map +1 -0
- package/dist/chunk-2SFBB54R.js +23 -0
- package/dist/chunk-2SFBB54R.js.map +1 -0
- package/dist/chunk-2XLKLVVR.js +18 -0
- package/dist/chunk-2XLKLVVR.js.map +1 -0
- package/dist/chunk-4SXHEZIV.cjs +240 -0
- package/dist/chunk-4SXHEZIV.cjs.map +1 -0
- package/dist/chunk-4VPXM6UU.js +10 -0
- package/dist/chunk-4VPXM6UU.js.map +1 -0
- package/dist/chunk-4WL52CXU.cjs +148 -0
- package/dist/chunk-4WL52CXU.cjs.map +1 -0
- package/dist/chunk-5NBUH3NO.js +39 -0
- package/dist/chunk-5NBUH3NO.js.map +1 -0
- package/dist/chunk-5PZMMF4H.js +43 -0
- package/dist/chunk-5PZMMF4H.js.map +1 -0
- package/dist/chunk-6CLRANZA.js +289 -0
- package/dist/chunk-6CLRANZA.js.map +1 -0
- package/dist/chunk-6JSIEDKL.cjs +121 -0
- package/dist/chunk-6JSIEDKL.cjs.map +1 -0
- package/dist/chunk-6M4ODB2V.cjs +145 -0
- package/dist/chunk-6M4ODB2V.cjs.map +1 -0
- package/dist/chunk-6X6ID4MO.js +20 -0
- package/dist/chunk-6X6ID4MO.js.map +1 -0
- package/dist/chunk-77FOZCOT.js +165 -0
- package/dist/chunk-77FOZCOT.js.map +1 -0
- package/dist/chunk-7AD3ZTZL.js +25 -0
- package/dist/chunk-7AD3ZTZL.js.map +1 -0
- package/dist/chunk-7GFX45DD.js +57 -0
- package/dist/chunk-7GFX45DD.js.map +1 -0
- package/dist/chunk-7S7KKUO6.cjs +45 -0
- package/dist/chunk-7S7KKUO6.cjs.map +1 -0
- package/dist/chunk-7W7ZWMLQ.js +99 -0
- package/dist/chunk-7W7ZWMLQ.js.map +1 -0
- package/dist/chunk-A44COQJJ.cjs +353 -0
- package/dist/chunk-A44COQJJ.cjs.map +1 -0
- package/dist/chunk-AKSBP7OO.js +185 -0
- package/dist/chunk-AKSBP7OO.js.map +1 -0
- package/dist/chunk-ANIZEWTU.cjs +196 -0
- package/dist/chunk-ANIZEWTU.cjs.map +1 -0
- package/dist/chunk-BEURCLDV.cjs +139 -0
- package/dist/chunk-BEURCLDV.cjs.map +1 -0
- package/dist/chunk-BFWPZMG7.js +379 -0
- package/dist/chunk-BFWPZMG7.js.map +1 -0
- package/dist/chunk-BUUUWQMB.cjs +34 -0
- package/dist/chunk-BUUUWQMB.cjs.map +1 -0
- package/dist/chunk-BV2MWEMV.cjs +26 -0
- package/dist/chunk-BV2MWEMV.cjs.map +1 -0
- package/dist/chunk-BWT67BI4.cjs +268 -0
- package/dist/chunk-BWT67BI4.cjs.map +1 -0
- package/dist/chunk-BXV2QUIC.js +191 -0
- package/dist/chunk-BXV2QUIC.js.map +1 -0
- package/dist/chunk-C6Y6CWYD.cjs +44 -0
- package/dist/chunk-C6Y6CWYD.cjs.map +1 -0
- package/dist/chunk-C7PICHRF.cjs +68 -0
- package/dist/chunk-C7PICHRF.cjs.map +1 -0
- package/dist/chunk-CAH3G4IS.js +13 -0
- package/dist/chunk-CAH3G4IS.js.map +1 -0
- package/dist/chunk-D42XHI4O.cjs +565 -0
- package/dist/chunk-D42XHI4O.cjs.map +1 -0
- package/dist/chunk-DGRZNMTU.js +104 -0
- package/dist/chunk-DGRZNMTU.js.map +1 -0
- package/dist/chunk-DUK5WMKJ.js +71 -0
- package/dist/chunk-DUK5WMKJ.js.map +1 -0
- package/dist/chunk-EEZ7CFMM.js +14 -0
- package/dist/chunk-EEZ7CFMM.js.map +1 -0
- package/dist/chunk-EPYZOCOL.cjs +82 -0
- package/dist/chunk-EPYZOCOL.cjs.map +1 -0
- package/dist/chunk-EVADEGM7.cjs +168 -0
- package/dist/chunk-EVADEGM7.cjs.map +1 -0
- package/dist/chunk-FD2UT76F.js +3 -0
- package/dist/chunk-FD2UT76F.js.map +1 -0
- package/dist/chunk-FV3YEMQ7.js +179 -0
- package/dist/chunk-FV3YEMQ7.js.map +1 -0
- package/dist/chunk-G2F67XUR.cjs +184 -0
- package/dist/chunk-G2F67XUR.cjs.map +1 -0
- package/dist/chunk-G6GO6BXD.js +994 -0
- package/dist/chunk-G6GO6BXD.js.map +1 -0
- package/dist/chunk-GIW4L6HY.cjs +27 -0
- package/dist/chunk-GIW4L6HY.cjs.map +1 -0
- package/dist/chunk-GJAGKFTR.js +74 -0
- package/dist/chunk-GJAGKFTR.js.map +1 -0
- package/dist/chunk-HFPGOTVF.cjs +324 -0
- package/dist/chunk-HFPGOTVF.cjs.map +1 -0
- package/dist/chunk-HRA3CPGG.js +486 -0
- package/dist/chunk-HRA3CPGG.js.map +1 -0
- package/dist/chunk-HZQEDNF6.js +66 -0
- package/dist/chunk-HZQEDNF6.js.map +1 -0
- package/dist/chunk-IT3PWBW4.cjs +19 -0
- package/dist/chunk-IT3PWBW4.cjs.map +1 -0
- package/dist/chunk-JD74LN7R.cjs +171 -0
- package/dist/chunk-JD74LN7R.cjs.map +1 -0
- package/dist/chunk-JTFYYKDT.js +135 -0
- package/dist/chunk-JTFYYKDT.js.map +1 -0
- package/dist/chunk-K2BQQ445.js +30 -0
- package/dist/chunk-K2BQQ445.js.map +1 -0
- package/dist/chunk-KAP4GJHO.js +546 -0
- package/dist/chunk-KAP4GJHO.js.map +1 -0
- package/dist/chunk-KDNBTPY2.js +458 -0
- package/dist/chunk-KDNBTPY2.js.map +1 -0
- package/dist/chunk-KIO7QFIV.cjs +223 -0
- package/dist/chunk-KIO7QFIV.cjs.map +1 -0
- package/dist/chunk-KSKU6JY6.cjs +71 -0
- package/dist/chunk-KSKU6JY6.cjs.map +1 -0
- package/dist/chunk-LSLPF6OI.cjs +192 -0
- package/dist/chunk-LSLPF6OI.cjs.map +1 -0
- package/dist/chunk-LU7VALW5.js +312 -0
- package/dist/chunk-LU7VALW5.js.map +1 -0
- package/dist/chunk-LUSXDZPV.cjs +296 -0
- package/dist/chunk-LUSXDZPV.cjs.map +1 -0
- package/dist/chunk-M72RJVPC.js +69 -0
- package/dist/chunk-M72RJVPC.js.map +1 -0
- package/dist/chunk-MQLCZ4HJ.cjs +1010 -0
- package/dist/chunk-MQLCZ4HJ.cjs.map +1 -0
- package/dist/chunk-MUUQ2WFJ.cjs +4 -0
- package/dist/chunk-MUUQ2WFJ.cjs.map +1 -0
- package/dist/chunk-MZ5ZRORA.cjs +496 -0
- package/dist/chunk-MZ5ZRORA.cjs.map +1 -0
- package/dist/chunk-N24XBYNX.cjs +140 -0
- package/dist/chunk-N24XBYNX.cjs.map +1 -0
- package/dist/chunk-NINIJCHU.cjs +23 -0
- package/dist/chunk-NINIJCHU.cjs.map +1 -0
- package/dist/chunk-NRSH7RJY.js +2841 -0
- package/dist/chunk-NRSH7RJY.js.map +1 -0
- package/dist/chunk-OIYQBUW4.cjs +111 -0
- package/dist/chunk-OIYQBUW4.cjs.map +1 -0
- package/dist/chunk-PNVDQL5Y.cjs +15 -0
- package/dist/chunk-PNVDQL5Y.cjs.map +1 -0
- package/dist/chunk-POQT2OIF.js +214 -0
- package/dist/chunk-POQT2OIF.js.map +1 -0
- package/dist/chunk-PSJN532Q.js +10573 -0
- package/dist/chunk-PSJN532Q.js.map +1 -0
- package/dist/chunk-QUTUJ6P6.cjs +76 -0
- package/dist/chunk-QUTUJ6P6.cjs.map +1 -0
- package/dist/chunk-R43CNCES.cjs +460 -0
- package/dist/chunk-R43CNCES.cjs.map +1 -0
- package/dist/chunk-S2BBRVQX.js +68 -0
- package/dist/chunk-S2BBRVQX.js.map +1 -0
- package/dist/chunk-S5RCBTSF.js +145 -0
- package/dist/chunk-S5RCBTSF.js.map +1 -0
- package/dist/chunk-S7OEXQMI.cjs +136 -0
- package/dist/chunk-S7OEXQMI.cjs.map +1 -0
- package/dist/chunk-SKWVAQAY.js +129 -0
- package/dist/chunk-SKWVAQAY.js.map +1 -0
- package/dist/chunk-SQLZ7ONU.js +152 -0
- package/dist/chunk-SQLZ7ONU.js.map +1 -0
- package/dist/chunk-TIZ4TS5V.cjs +72 -0
- package/dist/chunk-TIZ4TS5V.cjs.map +1 -0
- package/dist/chunk-TQKCU7CL.js +142 -0
- package/dist/chunk-TQKCU7CL.js.map +1 -0
- package/dist/chunk-U54XCYG3.js +116 -0
- package/dist/chunk-U54XCYG3.js.map +1 -0
- package/dist/chunk-U6QGZHPN.js +29 -0
- package/dist/chunk-U6QGZHPN.js.map +1 -0
- package/dist/chunk-UCFONJBG.js +79 -0
- package/dist/chunk-UCFONJBG.js.map +1 -0
- package/dist/chunk-UJ7IQJAG.js +141 -0
- package/dist/chunk-UJ7IQJAG.js.map +1 -0
- package/dist/chunk-UVNG67DG.js +253 -0
- package/dist/chunk-UVNG67DG.js.map +1 -0
- package/dist/chunk-V4XRLPEF.js +25 -0
- package/dist/chunk-V4XRLPEF.js.map +1 -0
- package/dist/chunk-VJ26EKGJ.cjs +28 -0
- package/dist/chunk-VJ26EKGJ.cjs.map +1 -0
- package/dist/chunk-VK7MAV65.cjs +90 -0
- package/dist/chunk-VK7MAV65.cjs.map +1 -0
- package/dist/chunk-VYCKUKPA.cjs +106 -0
- package/dist/chunk-VYCKUKPA.cjs.map +1 -0
- package/dist/chunk-VYHJZVL5.cjs +29 -0
- package/dist/chunk-VYHJZVL5.cjs.map +1 -0
- package/dist/chunk-WA3FO4R7.js +155 -0
- package/dist/chunk-WA3FO4R7.js.map +1 -0
- package/dist/chunk-WJVHMTKB.cjs +12 -0
- package/dist/chunk-WJVHMTKB.cjs.map +1 -0
- package/dist/chunk-WLKFWQSJ.js +333 -0
- package/dist/chunk-WLKFWQSJ.js.map +1 -0
- package/dist/chunk-WPZTYOUX.cjs +59 -0
- package/dist/chunk-WPZTYOUX.cjs.map +1 -0
- package/dist/chunk-X2FR4OIT.js +27 -0
- package/dist/chunk-X2FR4OIT.js.map +1 -0
- package/dist/chunk-XIG2IE3L.js +134 -0
- package/dist/chunk-XIG2IE3L.js.map +1 -0
- package/dist/chunk-YLQQX5W2.cjs +20 -0
- package/dist/chunk-YLQQX5W2.cjs.map +1 -0
- package/dist/chunk-YPEPCZHF.cjs +147 -0
- package/dist/chunk-YPEPCZHF.cjs.map +1 -0
- package/dist/chunk-YPQF26X7.cjs +8 -0
- package/dist/chunk-YPQF26X7.cjs.map +1 -0
- package/dist/chunk-YU6KON2I.cjs +160 -0
- package/dist/chunk-YU6KON2I.cjs.map +1 -0
- package/dist/chunk-Z2OC3UIW.cjs +388 -0
- package/dist/chunk-Z2OC3UIW.cjs.map +1 -0
- package/dist/chunk-ZUBIEQJF.cjs +10590 -0
- package/dist/chunk-ZUBIEQJF.cjs.map +1 -0
- package/dist/chunk-ZZWTY3RX.js +230 -0
- package/dist/chunk-ZZWTY3RX.js.map +1 -0
- package/dist/compact-session-OO2GD76G.js +17 -0
- package/dist/compact-session-OO2GD76G.js.map +1 -0
- package/dist/compact-session-OO7NVWPB.cjs +54 -0
- package/dist/compact-session-OO7NVWPB.cjs.map +1 -0
- package/dist/compaction.cjs +54 -303
- package/dist/compaction.cjs.map +1 -1
- package/dist/compaction.js +3 -292
- package/dist/compaction.js.map +1 -1
- package/dist/concurrency.cjs +9 -81
- package/dist/concurrency.cjs.map +1 -1
- package/dist/concurrency.js +5 -80
- package/dist/concurrency.js.map +1 -1
- package/dist/context-GWANQLX4.js +5 -0
- package/dist/context-GWANQLX4.js.map +1 -0
- package/dist/context-KNWVTZSY.cjs +22 -0
- package/dist/context-KNWVTZSY.cjs.map +1 -0
- package/dist/{cron-x3muPNgg.d.cts → cron-D0JKR-yX.d.cts} +2 -1
- package/dist/{cron-B_NkE_VM.d.ts → cron-DivRaEmN.d.ts} +2 -1
- package/dist/cron.cjs +44 -20676
- package/dist/cron.cjs.map +1 -1
- package/dist/cron.d.cts +1 -1
- package/dist/cron.d.ts +1 -1
- package/dist/cron.js +38 -20676
- package/dist/cron.js.map +1 -1
- package/dist/errors.cjs +78 -470
- package/dist/errors.cjs.map +1 -1
- package/dist/errors.d.ts +3 -424
- package/dist/errors.js +2 -455
- package/dist/errors.js.map +1 -1
- package/dist/eval.cjs +72 -20411
- package/dist/eval.cjs.map +1 -1
- package/dist/eval.js +59 -20402
- package/dist/eval.js.map +1 -1
- package/dist/executor-ORJS5GV2.js +981 -0
- package/dist/executor-ORJS5GV2.js.map +1 -0
- package/dist/executor-Y4MKPIUD.cjs +985 -0
- package/dist/executor-Y4MKPIUD.cjs.map +1 -0
- package/dist/filesystem/index.cjs +7 -112
- package/dist/filesystem/index.cjs.map +1 -1
- package/dist/filesystem/index.js +4 -109
- package/dist/filesystem/index.js.map +1 -1
- package/dist/fork-agent-RSV4E3EA.js +61 -0
- package/dist/fork-agent-RSV4E3EA.js.map +1 -0
- package/dist/fork-agent-Y2IK2RYU.cjs +64 -0
- package/dist/fork-agent-Y2IK2RYU.cjs.map +1 -0
- package/dist/fs-session-store-KAUQYSGI.cjs +17 -0
- package/dist/fs-session-store-KAUQYSGI.cjs.map +1 -0
- package/dist/fs-session-store-ZKB5NN37.js +8 -0
- package/dist/fs-session-store-ZKB5NN37.js.map +1 -0
- package/dist/generate-object-H3RNAM6S.cjs +20 -0
- package/dist/generate-object-H3RNAM6S.cjs.map +1 -0
- package/dist/generate-object-YJPDV5BS.js +7 -0
- package/dist/generate-object-YJPDV5BS.js.map +1 -0
- package/dist/index-manager-CB6I7XFQ.cjs +19 -0
- package/dist/index-manager-CB6I7XFQ.cjs.map +1 -0
- package/dist/index-manager-L27Q3QAC.js +10 -0
- package/dist/index-manager-L27Q3QAC.js.map +1 -0
- package/dist/index.cjs +371 -22888
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +16 -21
- package/dist/index.d.ts +16 -21
- package/dist/index.js +92 -22713
- package/dist/index.js.map +1 -1
- package/dist/inject-session-6MJUSGZP.cjs +27 -0
- package/dist/inject-session-6MJUSGZP.cjs.map +1 -0
- package/dist/inject-session-KOFHS5K3.js +25 -0
- package/dist/inject-session-KOFHS5K3.js.map +1 -0
- package/dist/internal/judge/judge-call.d.ts +32 -19
- package/dist/internal/persistence/index.cjs +93 -508
- package/dist/internal/persistence/index.cjs.map +1 -1
- package/dist/internal/persistence/index.js +11 -489
- package/dist/internal/persistence/index.js.map +1 -1
- package/dist/internal/security/index.cjs +44 -361
- package/dist/internal/security/index.cjs.map +1 -1
- package/dist/internal/security/index.js +4 -355
- package/dist/internal/security/index.js.map +1 -1
- package/dist/judge-call-CEQ3FYYD.js +126 -0
- package/dist/judge-call-CEQ3FYYD.js.map +1 -0
- package/dist/judge-call-QG4WZ7F3.cjs +130 -0
- package/dist/judge-call-QG4WZ7F3.cjs.map +1 -0
- package/dist/models.cjs +24 -919
- package/dist/models.cjs.map +1 -1
- package/dist/models.js +11 -907
- package/dist/models.js.map +1 -1
- package/dist/oauth-transaction-store-7CKHPQRN.cjs +32 -0
- package/dist/oauth-transaction-store-7CKHPQRN.cjs.map +1 -0
- package/dist/oauth-transaction-store-W74I6EFD.js +3 -0
- package/dist/oauth-transaction-store-W74I6EFD.js.map +1 -0
- package/dist/path-safety.cjs +31 -200
- package/dist/path-safety.cjs.map +1 -1
- package/dist/path-safety.js +3 -197
- package/dist/path-safety.js.map +1 -1
- package/dist/persistence.cjs +73 -393
- package/dist/persistence.cjs.map +1 -1
- package/dist/persistence.js +9 -381
- package/dist/persistence.js.map +1 -1
- package/dist/project.cjs +8 -112
- package/dist/project.cjs.map +1 -1
- package/dist/project.js +6 -110
- package/dist/project.js.map +1 -1
- package/dist/registry-34IWWDQQ.js +9 -0
- package/dist/registry-34IWWDQQ.js.map +1 -0
- package/dist/registry-APYDISL6.cjs +46 -0
- package/dist/registry-APYDISL6.cjs.map +1 -0
- package/dist/retry.cjs +7 -87
- package/dist/retry.cjs.map +1 -1
- package/dist/retry.js +3 -90
- package/dist/retry.js.map +1 -1
- package/dist/run-to-completion-J73I6IY4.cjs +48 -0
- package/dist/run-to-completion-J73I6IY4.cjs.map +1 -0
- package/dist/run-to-completion-JDPIUKSH.js +3 -0
- package/dist/run-to-completion-JDPIUKSH.js.map +1 -0
- package/dist/run-until-PNO3QQLI.cjs +180 -0
- package/dist/run-until-PNO3QQLI.cjs.map +1 -0
- package/dist/run-until-XLHEHRQM.js +177 -0
- package/dist/run-until-XLHEHRQM.js.map +1 -0
- package/dist/sandbox/index.cjs +35 -337
- package/dist/sandbox/index.cjs.map +1 -1
- package/dist/sandbox/index.js +7 -323
- package/dist/sandbox/index.js.map +1 -1
- package/dist/sanitize/index.cjs +5 -112
- package/dist/sanitize/index.cjs.map +1 -1
- package/dist/sanitize/index.js +1 -114
- package/dist/sanitize/index.js.map +1 -1
- package/dist/server/auth/index.cjs +35 -175
- package/dist/server/auth/index.cjs.map +1 -1
- package/dist/server/auth/index.js +7 -161
- package/dist/server/auth/index.js.map +1 -1
- package/dist/server/errors-envelope.cjs +17 -328
- package/dist/server/errors-envelope.cjs.map +1 -1
- package/dist/server/errors-envelope.js +4 -317
- package/dist/server/errors-envelope.js.map +1 -1
- package/dist/session-transcript-LPNZR3YS.cjs +46 -0
- package/dist/session-transcript-LPNZR3YS.cjs.map +1 -0
- package/dist/session-transcript-T7LGJUGW.js +5 -0
- package/dist/session-transcript-T7LGJUGW.js.map +1 -0
- package/dist/skills.cjs +12 -280
- package/dist/skills.cjs.map +1 -1
- package/dist/skills.js +4 -282
- package/dist/skills.js.map +1 -1
- package/dist/stream-object-QEQJJDTI.cjs +20 -0
- package/dist/stream-object-QEQJJDTI.cjs.map +1 -0
- package/dist/stream-object-WUQSSRY2.js +7 -0
- package/dist/stream-object-WUQSSRY2.js.map +1 -0
- package/dist/stream-to-completion-DFJ5T3BK.js +30 -0
- package/dist/stream-to-completion-DFJ5T3BK.js.map +1 -0
- package/dist/stream-to-completion-H2MHISSK.cjs +32 -0
- package/dist/stream-to-completion-H2MHISSK.cjs.map +1 -0
- package/dist/subagents.cjs +2 -8
- package/dist/subagents.cjs.map +1 -1
- package/dist/subagents.js +1 -7
- package/dist/subagents.js.map +1 -1
- package/dist/subscription/index.cjs +4 -17
- package/dist/subscription/index.cjs.map +1 -1
- package/dist/subscription/index.js +3 -16
- package/dist/subscription/index.js.map +1 -1
- package/dist/task-store.cjs +16 -258
- package/dist/task-store.cjs.map +1 -1
- package/dist/task-store.js +4 -259
- package/dist/task-store.js.map +1 -1
- package/dist/types/goal-events.d.ts +2 -1
- package/dist/workflow.cjs +71 -2797
- package/dist/workflow.cjs.map +1 -1
- package/dist/workflow.js +8 -2812
- package/dist/workflow.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,2841 @@
|
|
|
1
|
+
import { currentCredentialPool, computeBackoffMs, sleepWithAbort, newPooledCredential, CredentialPool } from './chunk-6CLRANZA.js';
|
|
2
|
+
import { invalidateSessionCache } from './chunk-EEZ7CFMM.js';
|
|
3
|
+
import { tryJson, isPlainObject, sanitizeToolInput } from './chunk-U54XCYG3.js';
|
|
4
|
+
import { reconstructMessages, SessionTranscript } from './chunk-ZZWTY3RX.js';
|
|
5
|
+
import { estimateTokens } from './chunk-WA3FO4R7.js';
|
|
6
|
+
import { globalSingleton, registerProvider, registerBuiltins, getProviderProfile, inferModelDialect, inferRegionFromModelId, stripBedrockPrefix, resolveBedrockBaseUrl, resolveVertexBaseUrl, stripVertexPrefix } from './chunk-KAP4GJHO.js';
|
|
7
|
+
import { NetworkError, AuthenticationError, RateLimitError, ConfigurationError, UnknownAgentError, CredentialPoolExhaustedError } from './chunk-WLKFWQSJ.js';
|
|
8
|
+
import { redactSecrets } from './chunk-JTFYYKDT.js';
|
|
9
|
+
import { existsSync, readFileSync } from 'fs';
|
|
10
|
+
import { readdir } from 'fs/promises';
|
|
11
|
+
import { homedir } from 'os';
|
|
12
|
+
import { join } from 'path';
|
|
13
|
+
import { pathToFileURL } from 'url';
|
|
14
|
+
import { createRequire } from 'module';
|
|
15
|
+
|
|
16
|
+
var discoveryState = globalSingleton("theokit-sdk.providers.discovered", () => ({
|
|
17
|
+
done: false
|
|
18
|
+
}));
|
|
19
|
+
function pluginsRoot() {
|
|
20
|
+
return join(homedir(), ".theokit", "plugins", "model-providers");
|
|
21
|
+
}
|
|
22
|
+
function trustFilePath() {
|
|
23
|
+
return join(homedir(), ".theokit", "plugins", "trusted-providers.json");
|
|
24
|
+
}
|
|
25
|
+
function loadTrustedNames() {
|
|
26
|
+
const trusted = /* @__PURE__ */ new Set();
|
|
27
|
+
const path = trustFilePath();
|
|
28
|
+
if (existsSync(path)) {
|
|
29
|
+
try {
|
|
30
|
+
const parsed = JSON.parse(readFileSync(path, "utf-8"));
|
|
31
|
+
if (Array.isArray(parsed)) {
|
|
32
|
+
let discarded = 0;
|
|
33
|
+
for (const name of parsed) {
|
|
34
|
+
if (typeof name === "string" && name.length > 0) trusted.add(name);
|
|
35
|
+
else discarded += 1;
|
|
36
|
+
}
|
|
37
|
+
if (discarded > 0) {
|
|
38
|
+
process.stderr.write(
|
|
39
|
+
`[theokit-sdk] WARN: ${path} contains ${discarded} non-string entr${discarded === 1 ? "y" : "ies"} \u2014 discarded (entries must be plugin-name strings)
|
|
40
|
+
`
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
} else {
|
|
44
|
+
process.stderr.write(
|
|
45
|
+
`[theokit-sdk] WARN: ${path} is not a JSON array \u2014 trusting NO provider plugins (fail-closed)
|
|
46
|
+
`
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
} catch {
|
|
50
|
+
process.stderr.write(
|
|
51
|
+
`[theokit-sdk] WARN: ${path} is not valid JSON \u2014 trusting NO provider plugins (fail-closed)
|
|
52
|
+
`
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
for (const name of (process.env.THEOKIT_TRUSTED_PROVIDERS ?? "").split(",")) {
|
|
57
|
+
const trimmed = name.trim();
|
|
58
|
+
if (trimmed.length > 0) trusted.add(trimmed);
|
|
59
|
+
}
|
|
60
|
+
return trusted;
|
|
61
|
+
}
|
|
62
|
+
async function discoverProviderPlugins() {
|
|
63
|
+
if (discoveryState.done) return;
|
|
64
|
+
discoveryState.done = true;
|
|
65
|
+
const root = pluginsRoot();
|
|
66
|
+
if (!existsSync(root)) return;
|
|
67
|
+
let entries;
|
|
68
|
+
try {
|
|
69
|
+
entries = await readdir(root);
|
|
70
|
+
} catch {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const trusted = loadTrustedNames();
|
|
74
|
+
for (const entry of entries) {
|
|
75
|
+
if (!trusted.has(entry)) {
|
|
76
|
+
process.stderr.write(
|
|
77
|
+
`[theokit-sdk] WARN: provider plugin "${entry}" is present but NOT trusted \u2014 skipped. To trust it, add "${entry}" to ${trustFilePath()} (JSON array of names) or THEOKIT_TRUSTED_PROVIDERS (comma-separated), then restart the process.
|
|
78
|
+
`
|
|
79
|
+
);
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
await loadOne(join(root, entry), entry);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
async function loadOne(dir, entryName) {
|
|
86
|
+
for (const ext of ["index.mjs", "index.js"]) {
|
|
87
|
+
const indexPath = join(dir, ext);
|
|
88
|
+
if (!existsSync(indexPath)) continue;
|
|
89
|
+
try {
|
|
90
|
+
const mod = await import(pathToFileURL(indexPath).href);
|
|
91
|
+
const plugin = mod.default ?? mod[entryName] ?? mod;
|
|
92
|
+
if (plugin !== null && typeof plugin === "object" && plugin.kind === "model-provider") {
|
|
93
|
+
const profile = plugin.profile;
|
|
94
|
+
if (profile !== void 0 && typeof profile === "object") {
|
|
95
|
+
registerProvider(profile);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return;
|
|
99
|
+
} catch (err) {
|
|
100
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
101
|
+
process.stderr.write(`[theokit-sdk] failed to load provider plugin "${entryName}": ${msg}
|
|
102
|
+
`);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// src/internal/error-mappers/shared.ts
|
|
109
|
+
var RAW_MAX_BYTES = 2048;
|
|
110
|
+
function parseRetryAfter(headers) {
|
|
111
|
+
if (headers === void 0) return void 0;
|
|
112
|
+
const raw = headers.get("retry-after");
|
|
113
|
+
if (raw === null) return void 0;
|
|
114
|
+
const n = Number(raw);
|
|
115
|
+
if (Number.isFinite(n) && n >= 0) return Math.ceil(n);
|
|
116
|
+
const dateMs = Date.parse(raw);
|
|
117
|
+
if (Number.isFinite(dateMs)) return Math.max(0, Math.ceil((dateMs - Date.now()) / 1e3));
|
|
118
|
+
return void 0;
|
|
119
|
+
}
|
|
120
|
+
function truncateRaw(body) {
|
|
121
|
+
if (body === null || body === void 0) return void 0;
|
|
122
|
+
const s = typeof body === "string" ? body : JSON.stringify(body);
|
|
123
|
+
const truncated = s.length <= RAW_MAX_BYTES ? s : `${s.slice(0, RAW_MAX_BYTES)}\u2026`;
|
|
124
|
+
return redactSecrets(truncated);
|
|
125
|
+
}
|
|
126
|
+
function buildErrorMetadata(args) {
|
|
127
|
+
const retryAfter = parseRetryAfter(args.headers);
|
|
128
|
+
const raw = truncateRaw(args.body);
|
|
129
|
+
return {
|
|
130
|
+
provider: args.provider,
|
|
131
|
+
endpoint: args.endpoint,
|
|
132
|
+
code: args.code,
|
|
133
|
+
statusCode: args.status,
|
|
134
|
+
...retryAfter !== void 0 ? { retryAfter } : {},
|
|
135
|
+
...raw !== void 0 ? { raw } : {}
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// src/internal/error-mappers/anthropic.ts
|
|
140
|
+
function mapAnthropicError(args) {
|
|
141
|
+
const { status, body, headers, endpoint } = args;
|
|
142
|
+
const code = mapAnthropicStatusToCode(status, body);
|
|
143
|
+
const message = formatMessage(status, code);
|
|
144
|
+
const metadata = buildErrorMetadata({
|
|
145
|
+
provider: "anthropic",
|
|
146
|
+
endpoint,
|
|
147
|
+
code,
|
|
148
|
+
status,
|
|
149
|
+
headers,
|
|
150
|
+
body
|
|
151
|
+
});
|
|
152
|
+
if (status === 401 || status === 403) {
|
|
153
|
+
return new AuthenticationError(message, { code: "anthropic_auth_failed", metadata });
|
|
154
|
+
}
|
|
155
|
+
if (status === 429) {
|
|
156
|
+
return new RateLimitError(message, { code: "anthropic_rate_limit", metadata });
|
|
157
|
+
}
|
|
158
|
+
if (status === 400) {
|
|
159
|
+
return new ConfigurationError(message, {
|
|
160
|
+
code: `anthropic_${code}`,
|
|
161
|
+
metadata
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
if (status === 408) {
|
|
165
|
+
return new NetworkError(message, { code: "anthropic_timeout", metadata });
|
|
166
|
+
}
|
|
167
|
+
if (status >= 500 && status < 600) {
|
|
168
|
+
return new NetworkError(message, { code: "anthropic_server_error", metadata });
|
|
169
|
+
}
|
|
170
|
+
return new UnknownAgentError(message, { code: "anthropic_unknown", metadata });
|
|
171
|
+
}
|
|
172
|
+
function mapAnthropicStatusToCode(status, body) {
|
|
173
|
+
if (status === 401 || status === 403) return "auth_failed";
|
|
174
|
+
if (status === 429) return "rate_limit";
|
|
175
|
+
if (status === 408) return "timeout";
|
|
176
|
+
if (status === 400) {
|
|
177
|
+
const text = JSON.stringify(body ?? "").toLowerCase();
|
|
178
|
+
if (text.includes("context") && (text.includes("too long") || text.includes("too_long") || text.includes("length"))) {
|
|
179
|
+
return "context_too_long";
|
|
180
|
+
}
|
|
181
|
+
if (text.includes("filtered") || text.includes("content policy") || text.includes("content_policy")) {
|
|
182
|
+
return "content_filtered";
|
|
183
|
+
}
|
|
184
|
+
return "invalid_request";
|
|
185
|
+
}
|
|
186
|
+
if (status >= 500 && status < 600) return "server_error";
|
|
187
|
+
return "unknown";
|
|
188
|
+
}
|
|
189
|
+
function formatMessage(status, code) {
|
|
190
|
+
return `Anthropic API error: ${code} (HTTP ${status})`;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// src/internal/llm/finish.ts
|
|
194
|
+
function collapseSystemText(system) {
|
|
195
|
+
if (system === void 0) return "";
|
|
196
|
+
if (typeof system === "string") return system;
|
|
197
|
+
return system.map((b) => b.text).join("\n\n");
|
|
198
|
+
}
|
|
199
|
+
function parseToolArguments(buffered) {
|
|
200
|
+
if (buffered === void 0 || buffered.length === 0) return {};
|
|
201
|
+
try {
|
|
202
|
+
return JSON.parse(buffered);
|
|
203
|
+
} catch {
|
|
204
|
+
const repaired = tryJson(buffered, true);
|
|
205
|
+
if (isPlainObject(repaired)) return repaired;
|
|
206
|
+
return { raw: buffered };
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
function mapOpenAIFinish(reason) {
|
|
210
|
+
switch (reason) {
|
|
211
|
+
case "tool_calls":
|
|
212
|
+
return "tool_use";
|
|
213
|
+
case "length":
|
|
214
|
+
return "max_tokens";
|
|
215
|
+
default:
|
|
216
|
+
return "end_turn";
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
function makeLlmFinish(state) {
|
|
220
|
+
const finish = {
|
|
221
|
+
stopReason: state.stopReason,
|
|
222
|
+
text: state.text,
|
|
223
|
+
toolCalls: state.toolCalls
|
|
224
|
+
};
|
|
225
|
+
if (state.inputTokens !== void 0) finish.inputTokens = state.inputTokens;
|
|
226
|
+
if (state.outputTokens !== void 0) finish.outputTokens = state.outputTokens;
|
|
227
|
+
if (state.cacheReadTokens !== void 0) finish.cacheReadTokens = state.cacheReadTokens;
|
|
228
|
+
if (state.cacheWriteTokens !== void 0) finish.cacheWriteTokens = state.cacheWriteTokens;
|
|
229
|
+
if (state.reasoningTokens !== void 0) finish.reasoningTokens = state.reasoningTokens;
|
|
230
|
+
return finish;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// src/internal/llm/tool-result-content.ts
|
|
234
|
+
function toBlockToolResultContent(content) {
|
|
235
|
+
return content;
|
|
236
|
+
}
|
|
237
|
+
function toStringToolResultContent(content, providerName) {
|
|
238
|
+
if (typeof content === "string") return content;
|
|
239
|
+
if (content.some((block) => block.type === "image")) {
|
|
240
|
+
throw new ConfigurationError(
|
|
241
|
+
`provider "${providerName}" does not support image content in tool results`
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
return content.filter(
|
|
245
|
+
(block) => block.type === "text"
|
|
246
|
+
).map((block) => block.text).join("\n");
|
|
247
|
+
}
|
|
248
|
+
function renderToolResultContentText(content, fn) {
|
|
249
|
+
if (typeof content === "string") return fn(content);
|
|
250
|
+
return content.map(
|
|
251
|
+
(block) => block.type === "text" ? { type: "text", text: fn(block.text) } : block
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// src/internal/llm/anthropic-shared.ts
|
|
256
|
+
function extractAnthropicText(response) {
|
|
257
|
+
return (response.content ?? []).filter((c) => c.type === "text").map((c) => c.text).join("");
|
|
258
|
+
}
|
|
259
|
+
function extractAnthropicToolCalls(response) {
|
|
260
|
+
return (response.content ?? []).filter(
|
|
261
|
+
(c) => c.type === "tool_use"
|
|
262
|
+
).map((c) => ({
|
|
263
|
+
type: "tool_use",
|
|
264
|
+
id: c.id,
|
|
265
|
+
name: c.name,
|
|
266
|
+
input: parseToolArguments(JSON.stringify(c.input))
|
|
267
|
+
}));
|
|
268
|
+
}
|
|
269
|
+
function mapAnthropicStopReason(reason) {
|
|
270
|
+
switch (reason) {
|
|
271
|
+
case "tool_use":
|
|
272
|
+
return "tool_use";
|
|
273
|
+
case "max_tokens":
|
|
274
|
+
return "max_tokens";
|
|
275
|
+
case "stop_sequence":
|
|
276
|
+
return "stop_sequence";
|
|
277
|
+
case "end_turn":
|
|
278
|
+
case null:
|
|
279
|
+
return "end_turn";
|
|
280
|
+
default:
|
|
281
|
+
return "end_turn";
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
function toAnthropicWireMessage(message) {
|
|
285
|
+
const role = message.role === "system" ? "user" : message.role;
|
|
286
|
+
const content = message.content.map((part) => {
|
|
287
|
+
if (part.type === "text") return { type: "text", text: part.text };
|
|
288
|
+
if (part.type === "tool_use") {
|
|
289
|
+
return { type: "tool_use", id: part.id, name: part.name, input: part.input };
|
|
290
|
+
}
|
|
291
|
+
if (part.type === "image") {
|
|
292
|
+
return { type: "image", source: part.source };
|
|
293
|
+
}
|
|
294
|
+
return {
|
|
295
|
+
type: "tool_result",
|
|
296
|
+
tool_use_id: part.toolUseId,
|
|
297
|
+
// SE7 — this wire is block-capable: strings pass through, structured
|
|
298
|
+
// text/image blocks are forwarded natively.
|
|
299
|
+
content: toBlockToolResultContent(part.content),
|
|
300
|
+
...part.isError === true ? { is_error: true } : {}
|
|
301
|
+
};
|
|
302
|
+
});
|
|
303
|
+
return { role, content };
|
|
304
|
+
}
|
|
305
|
+
function postAnthropicRequest(args) {
|
|
306
|
+
return args.fetchImpl(args.url, {
|
|
307
|
+
method: "POST",
|
|
308
|
+
signal: args.signal,
|
|
309
|
+
headers: {
|
|
310
|
+
"content-type": "application/json",
|
|
311
|
+
accept: "application/json",
|
|
312
|
+
authorization: `Bearer ${args.token}`
|
|
313
|
+
},
|
|
314
|
+
body: JSON.stringify(args.body)
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
async function handleAnthropicResponse(args) {
|
|
318
|
+
if (!args.response.ok) {
|
|
319
|
+
throw args.errorMapper({
|
|
320
|
+
status: args.response.status,
|
|
321
|
+
body: await parseHttpErrorBody(args.response),
|
|
322
|
+
headers: args.response.headers,
|
|
323
|
+
endpoint: args.endpoint
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
return consumeAnthropicResponse(args.response);
|
|
327
|
+
}
|
|
328
|
+
async function consumeAnthropicResponse(response) {
|
|
329
|
+
const data = await response.json();
|
|
330
|
+
return {
|
|
331
|
+
text: extractAnthropicText(data),
|
|
332
|
+
toolCalls: extractAnthropicToolCalls(data),
|
|
333
|
+
stopReason: mapAnthropicStopReason(data.stop_reason ?? null),
|
|
334
|
+
inputTokens: data.usage?.input_tokens,
|
|
335
|
+
outputTokens: data.usage?.output_tokens
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
async function parseHttpErrorBody(response) {
|
|
339
|
+
const text = await response.text().catch(() => "");
|
|
340
|
+
try {
|
|
341
|
+
return JSON.parse(text);
|
|
342
|
+
} catch {
|
|
343
|
+
return text;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
function buildAnthropicCommonBody(request) {
|
|
347
|
+
const body = {
|
|
348
|
+
max_tokens: request.maxTokens ?? 4096,
|
|
349
|
+
messages: request.messages.map(toAnthropicWireMessage)
|
|
350
|
+
};
|
|
351
|
+
const wireSystem = encodeAnthropicSystem(request.system);
|
|
352
|
+
if (wireSystem !== void 0) body.system = wireSystem;
|
|
353
|
+
if (request.temperature !== void 0) body.temperature = request.temperature;
|
|
354
|
+
if (request.tools !== void 0 && request.tools.length > 0) {
|
|
355
|
+
body.tools = request.tools.map((tool) => ({
|
|
356
|
+
name: tool.name,
|
|
357
|
+
description: tool.description,
|
|
358
|
+
input_schema: tool.inputSchema
|
|
359
|
+
}));
|
|
360
|
+
}
|
|
361
|
+
return body;
|
|
362
|
+
}
|
|
363
|
+
function encodeAnthropicSystem(system) {
|
|
364
|
+
if (system === void 0) return void 0;
|
|
365
|
+
if (typeof system === "string") return system;
|
|
366
|
+
if (system.length === 0) return void 0;
|
|
367
|
+
return system.map((block) => {
|
|
368
|
+
const wire = { type: "text", text: block.text };
|
|
369
|
+
if (block.cacheable === true) wire.cache_control = { type: "ephemeral" };
|
|
370
|
+
return wire;
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// src/internal/llm/sse.ts
|
|
375
|
+
var DEFAULT_SSE_IDLE_MS = 6e4;
|
|
376
|
+
async function* parseSseStream(body, signal, idleTimeoutMs = DEFAULT_SSE_IDLE_MS) {
|
|
377
|
+
if (body === null) return;
|
|
378
|
+
const reader = body.getReader();
|
|
379
|
+
const decoder = new TextDecoder("utf-8");
|
|
380
|
+
const state = { buffer: "", event: "message", data: "" };
|
|
381
|
+
try {
|
|
382
|
+
for await (const chunk of readChunks(reader, signal, idleTimeoutMs)) {
|
|
383
|
+
state.buffer += decoder.decode(chunk, { stream: true });
|
|
384
|
+
for (const record of drainCompleteRecords(state)) yield record;
|
|
385
|
+
}
|
|
386
|
+
if (state.data.length > 0) yield { event: state.event, data: state.data };
|
|
387
|
+
} finally {
|
|
388
|
+
await cancelReaderQuietly(reader);
|
|
389
|
+
releaseReader(reader);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
async function* readChunks(reader, signal, idleTimeoutMs) {
|
|
393
|
+
while (true) {
|
|
394
|
+
if (signal.aborted) return;
|
|
395
|
+
const { value, done } = await readWithIdleTimeout(reader, idleTimeoutMs);
|
|
396
|
+
if (done) return;
|
|
397
|
+
if (value !== void 0) yield value;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
function readWithIdleTimeout(reader, idleTimeoutMs) {
|
|
401
|
+
if (idleTimeoutMs <= 0) return reader.read();
|
|
402
|
+
return new Promise(
|
|
403
|
+
(resolve, reject) => {
|
|
404
|
+
const timer = setTimeout(() => {
|
|
405
|
+
reject(
|
|
406
|
+
new NetworkError(`SSE stream idle for ${idleTimeoutMs}ms \u2014 upstream stalled`, {
|
|
407
|
+
code: "stream_idle_timeout"
|
|
408
|
+
})
|
|
409
|
+
);
|
|
410
|
+
}, idleTimeoutMs);
|
|
411
|
+
reader.read().then(
|
|
412
|
+
(result) => {
|
|
413
|
+
clearTimeout(timer);
|
|
414
|
+
resolve(result);
|
|
415
|
+
},
|
|
416
|
+
(err) => {
|
|
417
|
+
clearTimeout(timer);
|
|
418
|
+
reject(err);
|
|
419
|
+
}
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
);
|
|
423
|
+
}
|
|
424
|
+
async function cancelReaderQuietly(reader) {
|
|
425
|
+
try {
|
|
426
|
+
await reader.cancel();
|
|
427
|
+
} catch {
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
function drainCompleteRecords(state) {
|
|
431
|
+
const out = [];
|
|
432
|
+
let newlineIndex = state.buffer.indexOf("\n");
|
|
433
|
+
while (newlineIndex !== -1) {
|
|
434
|
+
const line = state.buffer.slice(0, newlineIndex).replace(/\r$/, "");
|
|
435
|
+
state.buffer = state.buffer.slice(newlineIndex + 1);
|
|
436
|
+
const finished = applyLine(state, line);
|
|
437
|
+
if (finished !== void 0) out.push(finished);
|
|
438
|
+
newlineIndex = state.buffer.indexOf("\n");
|
|
439
|
+
}
|
|
440
|
+
return out;
|
|
441
|
+
}
|
|
442
|
+
function applyLine(state, line) {
|
|
443
|
+
if (line === "") {
|
|
444
|
+
if (state.data.length === 0) {
|
|
445
|
+
state.event = "message";
|
|
446
|
+
return void 0;
|
|
447
|
+
}
|
|
448
|
+
const record = { event: state.event, data: state.data };
|
|
449
|
+
state.event = "message";
|
|
450
|
+
state.data = "";
|
|
451
|
+
return record;
|
|
452
|
+
}
|
|
453
|
+
if (line.startsWith(":")) return void 0;
|
|
454
|
+
if (line.startsWith("event:")) {
|
|
455
|
+
state.event = stripOneLeadingSpace(line.slice(6));
|
|
456
|
+
return void 0;
|
|
457
|
+
}
|
|
458
|
+
if (line.startsWith("data:")) {
|
|
459
|
+
const piece = stripOneLeadingSpace(line.slice(5));
|
|
460
|
+
state.data = state.data.length === 0 ? piece : `${state.data}
|
|
461
|
+
${piece}`;
|
|
462
|
+
}
|
|
463
|
+
return void 0;
|
|
464
|
+
}
|
|
465
|
+
function stripOneLeadingSpace(value) {
|
|
466
|
+
return value.startsWith(" ") ? value.slice(1) : value;
|
|
467
|
+
}
|
|
468
|
+
function releaseReader(reader) {
|
|
469
|
+
try {
|
|
470
|
+
reader.releaseLock();
|
|
471
|
+
} catch {
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// src/internal/llm/anthropic.ts
|
|
476
|
+
var AnthropicClient = class {
|
|
477
|
+
constructor(options) {
|
|
478
|
+
this.options = options;
|
|
479
|
+
this.baseUrl = options.baseUrl ?? "https://api.anthropic.com";
|
|
480
|
+
this.fetchImpl = options.fetch ?? fetch;
|
|
481
|
+
}
|
|
482
|
+
options;
|
|
483
|
+
name = "anthropic";
|
|
484
|
+
baseUrl;
|
|
485
|
+
fetchImpl;
|
|
486
|
+
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: HTTP+SSE handshake + accumulator is intentionally one block
|
|
487
|
+
async *stream(request, signal) {
|
|
488
|
+
const body = buildAnthropicBody(request);
|
|
489
|
+
const headers = {
|
|
490
|
+
"content-type": "application/json",
|
|
491
|
+
"x-api-key": this.options.apiKey,
|
|
492
|
+
"anthropic-version": this.options.version ?? "2023-06-01"
|
|
493
|
+
};
|
|
494
|
+
if (this.options.extraHeaders !== void 0) Object.assign(headers, this.options.extraHeaders);
|
|
495
|
+
const response = await this.fetchImpl(`${this.baseUrl}/v1/messages`, {
|
|
496
|
+
method: "POST",
|
|
497
|
+
signal,
|
|
498
|
+
headers,
|
|
499
|
+
body: JSON.stringify(body)
|
|
500
|
+
});
|
|
501
|
+
if (!response.ok) {
|
|
502
|
+
const text = await response.text().catch(() => "");
|
|
503
|
+
let body2 = text;
|
|
504
|
+
try {
|
|
505
|
+
body2 = JSON.parse(text);
|
|
506
|
+
} catch {
|
|
507
|
+
}
|
|
508
|
+
throw mapAnthropicError({
|
|
509
|
+
status: response.status,
|
|
510
|
+
body: body2,
|
|
511
|
+
headers: response.headers,
|
|
512
|
+
endpoint: "/v1/messages"
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
const accumulator = new AnthropicStreamAccumulator();
|
|
516
|
+
for await (const record of parseSseStream(response.body, signal)) {
|
|
517
|
+
if (record.event === "ping" || record.event === "message_start") continue;
|
|
518
|
+
let parsed;
|
|
519
|
+
try {
|
|
520
|
+
parsed = JSON.parse(record.data);
|
|
521
|
+
} catch {
|
|
522
|
+
continue;
|
|
523
|
+
}
|
|
524
|
+
const events = accumulator.consume(parsed);
|
|
525
|
+
for (const event of events) yield event;
|
|
526
|
+
}
|
|
527
|
+
if (!accumulator.finishReasonSeen) {
|
|
528
|
+
throw new NetworkError("Anthropic SSE stream truncated (no stop_reason)", {
|
|
529
|
+
code: "stream_truncated"
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
return accumulator.finish();
|
|
533
|
+
}
|
|
534
|
+
};
|
|
535
|
+
var AnthropicStreamAccumulator = class {
|
|
536
|
+
text = "";
|
|
537
|
+
stopReason = "end_turn";
|
|
538
|
+
/**
|
|
539
|
+
* M2 #61 — whether a `message_delta` carrying a real `stop_reason` was seen.
|
|
540
|
+
* A stream that closes before it is a truncation (server FIN / proxy hiccup),
|
|
541
|
+
* not a clean `end_turn` — the caller throws `stream_truncated` on `false`.
|
|
542
|
+
*/
|
|
543
|
+
sawStopReason = false;
|
|
544
|
+
inputTokens;
|
|
545
|
+
outputTokens;
|
|
546
|
+
cacheReadTokens;
|
|
547
|
+
cacheWriteTokens;
|
|
548
|
+
toolCalls = /* @__PURE__ */ new Map();
|
|
549
|
+
toolBuffers = /* @__PURE__ */ new Map();
|
|
550
|
+
consume(event) {
|
|
551
|
+
if (event.type === "content_block_start") {
|
|
552
|
+
this.handleToolStart(event);
|
|
553
|
+
return [];
|
|
554
|
+
}
|
|
555
|
+
if (event.type === "content_block_delta") {
|
|
556
|
+
return this.handleContentDelta(event);
|
|
557
|
+
}
|
|
558
|
+
if (event.type === "message_delta") {
|
|
559
|
+
this.handleMessageDelta(event);
|
|
560
|
+
}
|
|
561
|
+
return [];
|
|
562
|
+
}
|
|
563
|
+
handleToolStart(start) {
|
|
564
|
+
if (start.content_block.type !== "tool_use") return;
|
|
565
|
+
this.toolCalls.set(start.index, {
|
|
566
|
+
type: "tool_use",
|
|
567
|
+
id: start.content_block.id,
|
|
568
|
+
name: start.content_block.name,
|
|
569
|
+
input: { ...start.content_block.input }
|
|
570
|
+
});
|
|
571
|
+
this.toolBuffers.set(start.index, "");
|
|
572
|
+
}
|
|
573
|
+
handleContentDelta(delta) {
|
|
574
|
+
if (delta.delta.type === "text_delta") {
|
|
575
|
+
const text = delta.delta.text;
|
|
576
|
+
this.text += text;
|
|
577
|
+
return [{ type: "text_delta", text }];
|
|
578
|
+
}
|
|
579
|
+
const idx = delta.index;
|
|
580
|
+
const existing = this.toolBuffers.get(idx) ?? "";
|
|
581
|
+
this.toolBuffers.set(idx, existing + delta.delta.partial_json);
|
|
582
|
+
return [];
|
|
583
|
+
}
|
|
584
|
+
/**
|
|
585
|
+
* T3.8 — extract token counters from Anthropic's `message_delta` usage.
|
|
586
|
+
* Public on the class so the test seam can drive it directly without
|
|
587
|
+
* spinning the SSE parser.
|
|
588
|
+
*/
|
|
589
|
+
handleMessageDelta(md) {
|
|
590
|
+
if (md.delta.stop_reason !== void 0 && md.delta.stop_reason !== null) {
|
|
591
|
+
this.sawStopReason = true;
|
|
592
|
+
}
|
|
593
|
+
this.stopReason = mapAnthropicStopReason(md.delta.stop_reason);
|
|
594
|
+
if (md.usage?.input_tokens !== void 0) this.inputTokens = md.usage.input_tokens;
|
|
595
|
+
if (md.usage?.output_tokens !== void 0) this.outputTokens = md.usage.output_tokens;
|
|
596
|
+
if (md.usage?.cache_creation_input_tokens !== void 0 && md.usage.cache_creation_input_tokens > 0) {
|
|
597
|
+
this.cacheWriteTokens = md.usage.cache_creation_input_tokens;
|
|
598
|
+
}
|
|
599
|
+
if (md.usage?.cache_read_input_tokens !== void 0 && md.usage.cache_read_input_tokens > 0) {
|
|
600
|
+
this.cacheReadTokens = md.usage.cache_read_input_tokens;
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
/** M2 #61 — whether the terminal `message_delta` (stop_reason) was seen. */
|
|
604
|
+
get finishReasonSeen() {
|
|
605
|
+
return this.sawStopReason;
|
|
606
|
+
}
|
|
607
|
+
finish() {
|
|
608
|
+
const toolCalls = [];
|
|
609
|
+
for (const [index, tool] of this.toolCalls.entries()) {
|
|
610
|
+
const buffered = this.toolBuffers.get(index);
|
|
611
|
+
const parsed = parseToolArguments(buffered);
|
|
612
|
+
if (Object.keys(parsed).length > 0) tool.input = parsed;
|
|
613
|
+
toolCalls.push(tool);
|
|
614
|
+
}
|
|
615
|
+
return makeLlmFinish({
|
|
616
|
+
stopReason: this.stopReason,
|
|
617
|
+
text: this.text,
|
|
618
|
+
toolCalls,
|
|
619
|
+
inputTokens: this.inputTokens,
|
|
620
|
+
outputTokens: this.outputTokens,
|
|
621
|
+
// T3.8 — surface Anthropic native cache-token counters when the
|
|
622
|
+
// request used `cache_control: {type:"ephemeral"}` on system blocks.
|
|
623
|
+
cacheReadTokens: this.cacheReadTokens,
|
|
624
|
+
cacheWriteTokens: this.cacheWriteTokens
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
};
|
|
628
|
+
function buildAnthropicBody(request) {
|
|
629
|
+
return {
|
|
630
|
+
model: request.model,
|
|
631
|
+
stream: true,
|
|
632
|
+
...buildAnthropicCommonBody(request)
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
// src/internal/error-mappers/bedrock.ts
|
|
637
|
+
function classifyBedrockError(args, awsType, message) {
|
|
638
|
+
if (args.status === 429 || awsType.includes("Throttling") || awsType.includes("TooManyRequests")) {
|
|
639
|
+
return "rate_limit";
|
|
640
|
+
}
|
|
641
|
+
if (args.status === 401 || args.status === 403 || awsType.includes("AccessDenied") || awsType.includes("UnauthorizedOperation")) {
|
|
642
|
+
return "auth_failed";
|
|
643
|
+
}
|
|
644
|
+
if (args.status === 400 || args.status === 404 || awsType.includes("ValidationException") || awsType.includes("ResourceNotFound") || message.includes("use case details")) {
|
|
645
|
+
return "invalid_request";
|
|
646
|
+
}
|
|
647
|
+
if (args.status === 408 || awsType.includes("Timeout")) return "timeout";
|
|
648
|
+
if (args.status >= 500) return "server_error";
|
|
649
|
+
return "unknown";
|
|
650
|
+
}
|
|
651
|
+
function buildBedrockMetadata(args, code) {
|
|
652
|
+
return buildErrorMetadata({
|
|
653
|
+
provider: "bedrock",
|
|
654
|
+
endpoint: args.endpoint,
|
|
655
|
+
code,
|
|
656
|
+
status: args.status,
|
|
657
|
+
headers: args.headers,
|
|
658
|
+
body: args.body
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
function mapBedrockError(args) {
|
|
662
|
+
const parsed = parseBody(args.body);
|
|
663
|
+
const awsType = parsed.__type ?? "";
|
|
664
|
+
const message = parsed.message ?? parsed.Message ?? "Bedrock request failed";
|
|
665
|
+
const code = classifyBedrockError(args, awsType, message);
|
|
666
|
+
const metadata = buildBedrockMetadata(args, code);
|
|
667
|
+
switch (code) {
|
|
668
|
+
case "rate_limit":
|
|
669
|
+
return new RateLimitError(`Bedrock throttled: ${message}`, { metadata });
|
|
670
|
+
case "auth_failed":
|
|
671
|
+
return new AuthenticationError(`Bedrock auth: ${message}`, { metadata });
|
|
672
|
+
case "invalid_request": {
|
|
673
|
+
const friendly = message.includes("use case details") ? `Bedrock account setup required: ${message} (AWS Console \u2192 Bedrock \u2192 Model access \u2192 Anthropic \u2192 Submit use case form, then retry in 15 min.)` : `Bedrock validation: ${message}`;
|
|
674
|
+
return new ConfigurationError(friendly, { metadata });
|
|
675
|
+
}
|
|
676
|
+
case "timeout":
|
|
677
|
+
return new NetworkError(`Bedrock timeout: ${message}`, { metadata });
|
|
678
|
+
case "server_error":
|
|
679
|
+
return new NetworkError(`Bedrock server error: ${message}`, { metadata });
|
|
680
|
+
default:
|
|
681
|
+
return new UnknownAgentError(`Bedrock unknown: ${message}`, { metadata });
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
function parseBody(body) {
|
|
685
|
+
if (body !== null && typeof body === "object") return body;
|
|
686
|
+
if (typeof body === "string") {
|
|
687
|
+
try {
|
|
688
|
+
return JSON.parse(body);
|
|
689
|
+
} catch {
|
|
690
|
+
return {};
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
return {};
|
|
694
|
+
}
|
|
695
|
+
var cachedToken = null;
|
|
696
|
+
var CACHE_TTL_MS = 90 * 60 * 1e3;
|
|
697
|
+
async function resolveBedrockToken(region) {
|
|
698
|
+
const env = process.env.AWS_BEARER_TOKEN_BEDROCK;
|
|
699
|
+
if (env !== void 0 && env.length > 0) return env;
|
|
700
|
+
const now = Date.now();
|
|
701
|
+
if (cachedToken !== null && cachedToken.expiresAt > now) {
|
|
702
|
+
return cachedToken.value;
|
|
703
|
+
}
|
|
704
|
+
try {
|
|
705
|
+
const r = createRequire(import.meta.url);
|
|
706
|
+
const gen = r("@aws/bedrock-token-generator");
|
|
707
|
+
if (typeof gen.getToken !== "function") return void 0;
|
|
708
|
+
const providers = r("@aws-sdk/credential-providers");
|
|
709
|
+
if (typeof providers.fromNodeProviderChain !== "function") return void 0;
|
|
710
|
+
const credentialProvider = providers.fromNodeProviderChain();
|
|
711
|
+
const token = await gen.getToken({ credentials: credentialProvider, region });
|
|
712
|
+
cachedToken = { value: token, expiresAt: now + CACHE_TTL_MS };
|
|
713
|
+
return token;
|
|
714
|
+
} catch {
|
|
715
|
+
return void 0;
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
// src/internal/llm/bedrock-anthropic.ts
|
|
720
|
+
var BedrockAnthropicClient = class {
|
|
721
|
+
constructor(options) {
|
|
722
|
+
this.options = options;
|
|
723
|
+
this.fetchImpl = options.fetch ?? fetch;
|
|
724
|
+
}
|
|
725
|
+
options;
|
|
726
|
+
name = "bedrock";
|
|
727
|
+
fetchImpl;
|
|
728
|
+
async resolveTokenOrThrow(region) {
|
|
729
|
+
const fromOpts = this.options.apiKey;
|
|
730
|
+
if (fromOpts !== void 0 && fromOpts.length > 0) return fromOpts;
|
|
731
|
+
const fromEnv = await resolveBedrockToken(region);
|
|
732
|
+
if (fromEnv !== void 0 && fromEnv.length > 0) return fromEnv;
|
|
733
|
+
throw new ConfigurationError(
|
|
734
|
+
"Bedrock requires AWS_BEARER_TOKEN_BEDROCK env var, or install `@aws/bedrock-token-generator` for auto-refresh, or pass apiKey explicitly.",
|
|
735
|
+
{ code: "auth_failed" }
|
|
736
|
+
);
|
|
737
|
+
}
|
|
738
|
+
async *stream(request, signal) {
|
|
739
|
+
const region = inferRegionFromModelId(request.model) ?? process.env.AWS_REGION ?? "us-east-1";
|
|
740
|
+
const token = await this.resolveTokenOrThrow(region);
|
|
741
|
+
const bareModel = stripBedrockPrefix(request.model);
|
|
742
|
+
const baseUrl = this.options.baseUrl ?? resolveBedrockBaseUrl(request.model);
|
|
743
|
+
const endpoint = `/model/${encodeURIComponent(bareModel)}/invoke`;
|
|
744
|
+
const url = `${baseUrl}${endpoint}`;
|
|
745
|
+
const response = await postAnthropicRequest({
|
|
746
|
+
fetchImpl: this.fetchImpl,
|
|
747
|
+
url,
|
|
748
|
+
token,
|
|
749
|
+
body: buildBedrockBody(request),
|
|
750
|
+
signal
|
|
751
|
+
});
|
|
752
|
+
const consumed = await handleAnthropicResponse({
|
|
753
|
+
response,
|
|
754
|
+
endpoint,
|
|
755
|
+
errorMapper: mapBedrockError
|
|
756
|
+
});
|
|
757
|
+
if (consumed.text.length > 0) yield { type: "text_delta", text: consumed.text };
|
|
758
|
+
return makeLlmFinish(consumed);
|
|
759
|
+
}
|
|
760
|
+
};
|
|
761
|
+
function buildBedrockBody(request) {
|
|
762
|
+
return {
|
|
763
|
+
anthropic_version: "bedrock-2023-05-31",
|
|
764
|
+
...buildAnthropicCommonBody(request)
|
|
765
|
+
};
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
// src/internal/error-mappers/openai-compatible.ts
|
|
769
|
+
function mapOpenAICompatibleError(args) {
|
|
770
|
+
const { providerId, status, body, headers, endpoint } = args;
|
|
771
|
+
const code = mapOpenAiStatusToCode(status, body);
|
|
772
|
+
const message = formatMessage2(providerId, status, code);
|
|
773
|
+
const metadata = buildErrorMetadata({
|
|
774
|
+
provider: providerId,
|
|
775
|
+
endpoint,
|
|
776
|
+
code,
|
|
777
|
+
status,
|
|
778
|
+
headers,
|
|
779
|
+
body
|
|
780
|
+
});
|
|
781
|
+
if (status === 401 || status === 403) {
|
|
782
|
+
return new AuthenticationError(message, { code: `${providerId}_auth_failed`, metadata });
|
|
783
|
+
}
|
|
784
|
+
if (status === 429) {
|
|
785
|
+
return new RateLimitError(message, { code: `${providerId}_rate_limit`, metadata });
|
|
786
|
+
}
|
|
787
|
+
if (status === 400) {
|
|
788
|
+
return new ConfigurationError(message, { code: `${providerId}_${code}`, metadata });
|
|
789
|
+
}
|
|
790
|
+
if (status === 408) {
|
|
791
|
+
return new NetworkError(message, { code: `${providerId}_timeout`, metadata });
|
|
792
|
+
}
|
|
793
|
+
if (status >= 500 && status < 600) {
|
|
794
|
+
return new NetworkError(message, { code: `${providerId}_server_error`, metadata });
|
|
795
|
+
}
|
|
796
|
+
return new UnknownAgentError(message, { code: `${providerId}_unknown`, metadata });
|
|
797
|
+
}
|
|
798
|
+
function extractOpenAiErrorCode(body) {
|
|
799
|
+
if (body === null || typeof body !== "object") return void 0;
|
|
800
|
+
const err = body.error;
|
|
801
|
+
if (err === void 0 || err === null) return void 0;
|
|
802
|
+
if (typeof err.code === "string") return err.code;
|
|
803
|
+
if (typeof err.type === "string") return err.type;
|
|
804
|
+
return void 0;
|
|
805
|
+
}
|
|
806
|
+
function mapOpenAiStatusToCode(status, body) {
|
|
807
|
+
const rawCode = extractOpenAiErrorCode(body)?.toLowerCase() ?? "";
|
|
808
|
+
if (rawCode.includes("context_length") || rawCode.includes("context_window") || rawCode.includes("too_many_tokens") || rawCode.includes("max_tokens")) {
|
|
809
|
+
return "context_too_long";
|
|
810
|
+
}
|
|
811
|
+
if (rawCode.includes("content_filter") || rawCode.includes("content_policy") || rawCode.includes("safety")) {
|
|
812
|
+
return "content_filtered";
|
|
813
|
+
}
|
|
814
|
+
if (rawCode.includes("model_not_found") || rawCode.includes("model_unavailable") || rawCode.includes("invalid_model")) {
|
|
815
|
+
return "model_unavailable";
|
|
816
|
+
}
|
|
817
|
+
if (status === 402 || rawCode.includes("insufficient_quota") || rawCode.includes("quota_exceeded")) {
|
|
818
|
+
return "quota_exceeded";
|
|
819
|
+
}
|
|
820
|
+
if (status === 401 || status === 403) return "auth_failed";
|
|
821
|
+
if (status === 429) return "rate_limit";
|
|
822
|
+
if (status === 408) return "timeout";
|
|
823
|
+
if (status === 400) return "invalid_request";
|
|
824
|
+
if (status >= 500 && status < 600) return "server_error";
|
|
825
|
+
return "unknown";
|
|
826
|
+
}
|
|
827
|
+
function formatMessage2(providerId, status, code) {
|
|
828
|
+
return `${providerId} API error: ${code} (HTTP ${status})`;
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
// src/internal/llm/fault-injection.ts
|
|
832
|
+
var ENV_GATE_KEY = "NODE_ENV";
|
|
833
|
+
var ENV_GATE_VALUE = "test";
|
|
834
|
+
var ENV_OVERRIDE_KEY = "THEOKIT_TEST_RESPONSE_OVERRIDE";
|
|
835
|
+
function maybeWrapWithFaultInjection(client) {
|
|
836
|
+
if (!isGateOn()) return client;
|
|
837
|
+
return new FaultInjectingLlmClient(client);
|
|
838
|
+
}
|
|
839
|
+
var FaultInjectingLlmClient = class {
|
|
840
|
+
name;
|
|
841
|
+
/**
|
|
842
|
+
* Inner (real) client. Exposed so layered transport assertions (router
|
|
843
|
+
* pool-wiring tests, telemetry inspectors) can walk one level deep —
|
|
844
|
+
* same pattern PoolAwareLlmClient uses for its inner transport.
|
|
845
|
+
*/
|
|
846
|
+
inner;
|
|
847
|
+
constructor(inner) {
|
|
848
|
+
this.inner = inner;
|
|
849
|
+
this.name = inner.name;
|
|
850
|
+
}
|
|
851
|
+
async *stream(request, signal) {
|
|
852
|
+
if (!isGateOn()) {
|
|
853
|
+
return yield* this.inner.stream(request, signal);
|
|
854
|
+
}
|
|
855
|
+
const parsed = parseOverrideOrWarnOnce();
|
|
856
|
+
if (parsed === void 0) {
|
|
857
|
+
return yield* this.inner.stream(request, signal);
|
|
858
|
+
}
|
|
859
|
+
if (parsed.status >= 400) {
|
|
860
|
+
throw mapOpenAICompatibleError({
|
|
861
|
+
providerId: this.name,
|
|
862
|
+
status: parsed.status,
|
|
863
|
+
body: parsed.body,
|
|
864
|
+
headers: void 0,
|
|
865
|
+
endpoint: "/v1/chat/completions"
|
|
866
|
+
});
|
|
867
|
+
}
|
|
868
|
+
const text = extractText(parsed.body);
|
|
869
|
+
yield { type: "text_delta", text };
|
|
870
|
+
yield { type: "stop", reason: "end_turn" };
|
|
871
|
+
return {
|
|
872
|
+
stopReason: "end_turn",
|
|
873
|
+
text,
|
|
874
|
+
toolCalls: [],
|
|
875
|
+
inputTokens: 0,
|
|
876
|
+
outputTokens: 0
|
|
877
|
+
};
|
|
878
|
+
}
|
|
879
|
+
};
|
|
880
|
+
function isGateOn() {
|
|
881
|
+
return process.env[ENV_GATE_KEY] === ENV_GATE_VALUE;
|
|
882
|
+
}
|
|
883
|
+
var warnedInvalidJson = false;
|
|
884
|
+
function parseOverrideOrWarnOnce() {
|
|
885
|
+
const raw = process.env[ENV_OVERRIDE_KEY];
|
|
886
|
+
if (raw === void 0 || raw.length === 0) return void 0;
|
|
887
|
+
let candidate;
|
|
888
|
+
try {
|
|
889
|
+
candidate = JSON.parse(raw);
|
|
890
|
+
} catch (_err) {
|
|
891
|
+
warnInvalidJsonOnce();
|
|
892
|
+
return void 0;
|
|
893
|
+
}
|
|
894
|
+
if (candidate === null || typeof candidate !== "object") {
|
|
895
|
+
warnInvalidJsonOnce();
|
|
896
|
+
return void 0;
|
|
897
|
+
}
|
|
898
|
+
const obj = candidate;
|
|
899
|
+
if (typeof obj.status !== "number" || !Number.isFinite(obj.status)) {
|
|
900
|
+
warnInvalidJsonOnce();
|
|
901
|
+
return void 0;
|
|
902
|
+
}
|
|
903
|
+
return { status: obj.status, body: obj.body };
|
|
904
|
+
}
|
|
905
|
+
function warnInvalidJsonOnce(_raw) {
|
|
906
|
+
if (warnedInvalidJson) return;
|
|
907
|
+
warnedInvalidJson = true;
|
|
908
|
+
process.stderr.write(
|
|
909
|
+
`[theokit-sdk] ${ENV_OVERRIDE_KEY} is set but value is not valid JSON of shape \`{"status": number, "body": object | string}\`. Falling back to real LLM call. This warning is shown once per process.
|
|
910
|
+
`
|
|
911
|
+
);
|
|
912
|
+
}
|
|
913
|
+
function extractText(body) {
|
|
914
|
+
if (typeof body === "string") return body;
|
|
915
|
+
if (body === null || typeof body !== "object") return "";
|
|
916
|
+
const fromOpenAi = readOpenAiContent(body);
|
|
917
|
+
if (fromOpenAi !== void 0) return fromOpenAi;
|
|
918
|
+
const obj = body;
|
|
919
|
+
if (typeof obj.text === "string") return obj.text;
|
|
920
|
+
return "";
|
|
921
|
+
}
|
|
922
|
+
function readOpenAiContent(body) {
|
|
923
|
+
const obj = body;
|
|
924
|
+
if (!Array.isArray(obj.choices) || obj.choices.length === 0) return void 0;
|
|
925
|
+
const first = obj.choices[0];
|
|
926
|
+
const content = first?.message?.content;
|
|
927
|
+
if (typeof content === "string") return content;
|
|
928
|
+
return void 0;
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
// src/internal/error-mappers/ollama.ts
|
|
932
|
+
function mapOllamaTransportError(args) {
|
|
933
|
+
if (args.providerId !== "ollama") return void 0;
|
|
934
|
+
const causeCode = extractCauseCode(args.cause);
|
|
935
|
+
if (causeCode !== "ECONNREFUSED" && causeCode !== "ENOTFOUND") return void 0;
|
|
936
|
+
const metadata = buildErrorMetadata({
|
|
937
|
+
provider: "ollama",
|
|
938
|
+
endpoint: args.endpoint,
|
|
939
|
+
code: "network",
|
|
940
|
+
status: 0,
|
|
941
|
+
headers: void 0,
|
|
942
|
+
body: { cause: causeCode }
|
|
943
|
+
});
|
|
944
|
+
return new ConfigurationError(
|
|
945
|
+
"Ollama is not reachable. Run `ollama serve` to start the local runtime, or set OLLAMA_HOST to point at a remote Ollama server.",
|
|
946
|
+
{ code: "ollama_unreachable", metadata }
|
|
947
|
+
);
|
|
948
|
+
}
|
|
949
|
+
function mapOllamaHttpError(args) {
|
|
950
|
+
if (args.providerId !== "ollama") return void 0;
|
|
951
|
+
const errString = extractErrorString(args.body).toLowerCase();
|
|
952
|
+
if (args.status === 404 && errString.includes("not found") && errString.includes("pull")) {
|
|
953
|
+
const modelHint = extractModelName(errString);
|
|
954
|
+
const metadata = buildErrorMetadata({
|
|
955
|
+
provider: "ollama",
|
|
956
|
+
endpoint: args.endpoint,
|
|
957
|
+
code: "model_unavailable",
|
|
958
|
+
status: args.status,
|
|
959
|
+
headers: args.headers,
|
|
960
|
+
body: args.body
|
|
961
|
+
});
|
|
962
|
+
const pullCmd = modelHint !== void 0 ? `ollama pull ${modelHint}` : "ollama pull <model>";
|
|
963
|
+
return new ConfigurationError(
|
|
964
|
+
`Ollama model is not pulled. Run \`${pullCmd}\` to download it first.`,
|
|
965
|
+
{ code: "ollama_model_not_pulled", metadata }
|
|
966
|
+
);
|
|
967
|
+
}
|
|
968
|
+
if (args.status === 503 && errString.includes("model is loading")) {
|
|
969
|
+
const metadata = buildErrorMetadata({
|
|
970
|
+
provider: "ollama",
|
|
971
|
+
endpoint: args.endpoint,
|
|
972
|
+
code: "server_error",
|
|
973
|
+
status: args.status,
|
|
974
|
+
headers: args.headers,
|
|
975
|
+
body: args.body
|
|
976
|
+
});
|
|
977
|
+
return new NetworkError(
|
|
978
|
+
"Ollama model is loading. Retry in a few seconds \u2014 Ollama is warming up the model into memory.",
|
|
979
|
+
{ code: "ollama_model_loading", metadata }
|
|
980
|
+
);
|
|
981
|
+
}
|
|
982
|
+
return void 0;
|
|
983
|
+
}
|
|
984
|
+
function extractCauseCode(cause) {
|
|
985
|
+
if (cause === null || typeof cause !== "object") return void 0;
|
|
986
|
+
const directCode = cause.code;
|
|
987
|
+
if (typeof directCode === "string") return directCode;
|
|
988
|
+
const nested = cause.cause;
|
|
989
|
+
if (nested !== void 0) return extractCauseCode(nested);
|
|
990
|
+
return void 0;
|
|
991
|
+
}
|
|
992
|
+
function extractErrorString(body) {
|
|
993
|
+
if (typeof body === "string") return body;
|
|
994
|
+
if (body === null || typeof body !== "object") return "";
|
|
995
|
+
const err = body.error;
|
|
996
|
+
if (typeof err === "string") return err;
|
|
997
|
+
return "";
|
|
998
|
+
}
|
|
999
|
+
function extractModelName(errString) {
|
|
1000
|
+
const match = errString.match(/'([^']+)'/);
|
|
1001
|
+
return match?.[1];
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
// src/internal/llm/ollama-native.ts
|
|
1005
|
+
var OllamaNativeClient = class {
|
|
1006
|
+
constructor(options) {
|
|
1007
|
+
this.options = options;
|
|
1008
|
+
const raw = (options.baseUrl ?? "http://localhost:11434").replace(/\/+$/, "");
|
|
1009
|
+
this.baseUrl = raw.replace(/\/v1$/i, "");
|
|
1010
|
+
this.fetchImpl = options.fetch ?? fetch;
|
|
1011
|
+
}
|
|
1012
|
+
options;
|
|
1013
|
+
name = "ollama";
|
|
1014
|
+
baseUrl;
|
|
1015
|
+
fetchImpl;
|
|
1016
|
+
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: HTTP+NDJSON handshake + accumulator is intentionally one block (mirrors openai.ts)
|
|
1017
|
+
async *stream(request, signal) {
|
|
1018
|
+
const headers = { "content-type": "application/json" };
|
|
1019
|
+
if (this.options.apiKey !== void 0 && this.options.apiKey.length > 0) {
|
|
1020
|
+
headers.authorization = `Bearer ${this.options.apiKey}`;
|
|
1021
|
+
}
|
|
1022
|
+
const body = buildOllamaChatBody(request);
|
|
1023
|
+
const url = `${this.baseUrl}/api/chat`;
|
|
1024
|
+
let response;
|
|
1025
|
+
try {
|
|
1026
|
+
response = await this.fetchImpl(url, {
|
|
1027
|
+
method: "POST",
|
|
1028
|
+
signal,
|
|
1029
|
+
headers,
|
|
1030
|
+
body: JSON.stringify(body)
|
|
1031
|
+
});
|
|
1032
|
+
} catch (fetchErr) {
|
|
1033
|
+
const mapped = mapOllamaTransportError({
|
|
1034
|
+
providerId: "ollama",
|
|
1035
|
+
cause: fetchErr,
|
|
1036
|
+
endpoint: "/api/chat"
|
|
1037
|
+
});
|
|
1038
|
+
if (mapped !== void 0) throw mapped;
|
|
1039
|
+
throw fetchErr;
|
|
1040
|
+
}
|
|
1041
|
+
if (!response.ok) {
|
|
1042
|
+
const text = await response.text().catch(() => "");
|
|
1043
|
+
let bodyJson = text;
|
|
1044
|
+
try {
|
|
1045
|
+
bodyJson = JSON.parse(text);
|
|
1046
|
+
} catch {
|
|
1047
|
+
}
|
|
1048
|
+
const mapped = mapOllamaHttpError({
|
|
1049
|
+
providerId: "ollama",
|
|
1050
|
+
status: response.status,
|
|
1051
|
+
body: bodyJson,
|
|
1052
|
+
headers: response.headers,
|
|
1053
|
+
endpoint: "/api/chat"
|
|
1054
|
+
});
|
|
1055
|
+
if (mapped !== void 0) throw mapped;
|
|
1056
|
+
throw new Error(`Ollama /api/chat HTTP ${response.status}: ${text.slice(0, 200)}`);
|
|
1057
|
+
}
|
|
1058
|
+
const accumulator = new OllamaStreamAccumulator();
|
|
1059
|
+
if (response.body === null) {
|
|
1060
|
+
return accumulator.finish();
|
|
1061
|
+
}
|
|
1062
|
+
for await (const chunk of parseNdjsonStream(response.body, signal)) {
|
|
1063
|
+
const events = accumulator.consume(chunk);
|
|
1064
|
+
for (const event of events) yield event;
|
|
1065
|
+
if (chunk.done === true) break;
|
|
1066
|
+
}
|
|
1067
|
+
return accumulator.finish();
|
|
1068
|
+
}
|
|
1069
|
+
};
|
|
1070
|
+
var OllamaStreamAccumulator = class {
|
|
1071
|
+
text = "";
|
|
1072
|
+
stopReason = "end_turn";
|
|
1073
|
+
inputTokens;
|
|
1074
|
+
outputTokens;
|
|
1075
|
+
toolCalls = [];
|
|
1076
|
+
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: NDJSON chunk dispatch — text delta + tool_call append + finish-reason mapping is one cohesive accumulator (mirrors openai.ts OpenAIStreamAccumulator.consume).
|
|
1077
|
+
consume(chunk) {
|
|
1078
|
+
const events = [];
|
|
1079
|
+
if (chunk.prompt_eval_count !== void 0) this.inputTokens = chunk.prompt_eval_count;
|
|
1080
|
+
if (chunk.eval_count !== void 0) this.outputTokens = chunk.eval_count;
|
|
1081
|
+
const msg = chunk.message;
|
|
1082
|
+
if (msg?.content !== void 0 && msg.content.length > 0) {
|
|
1083
|
+
this.text += msg.content;
|
|
1084
|
+
events.push({ type: "text_delta", text: msg.content });
|
|
1085
|
+
}
|
|
1086
|
+
if (msg?.tool_calls !== void 0) {
|
|
1087
|
+
for (const call of msg.tool_calls) {
|
|
1088
|
+
const input = typeof call.function.arguments === "string" ? parseOllamaToolArguments(call.function.arguments) : call.function.arguments;
|
|
1089
|
+
this.toolCalls.push({
|
|
1090
|
+
type: "tool_use",
|
|
1091
|
+
id: `ollama_call_${this.toolCalls.length + 1}`,
|
|
1092
|
+
name: call.function.name,
|
|
1093
|
+
input
|
|
1094
|
+
});
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
if (chunk.done === true && chunk.done_reason !== void 0) {
|
|
1098
|
+
this.stopReason = mapOllamaDoneReason(chunk.done_reason, this.toolCalls.length > 0);
|
|
1099
|
+
}
|
|
1100
|
+
return events;
|
|
1101
|
+
}
|
|
1102
|
+
finish() {
|
|
1103
|
+
if (this.toolCalls.length > 0 && this.stopReason === "end_turn") {
|
|
1104
|
+
this.stopReason = "tool_use";
|
|
1105
|
+
}
|
|
1106
|
+
return makeLlmFinish({
|
|
1107
|
+
stopReason: this.stopReason,
|
|
1108
|
+
text: this.text,
|
|
1109
|
+
toolCalls: this.toolCalls,
|
|
1110
|
+
inputTokens: this.inputTokens,
|
|
1111
|
+
outputTokens: this.outputTokens
|
|
1112
|
+
});
|
|
1113
|
+
}
|
|
1114
|
+
};
|
|
1115
|
+
function parseOllamaToolArguments(raw) {
|
|
1116
|
+
try {
|
|
1117
|
+
const parsed = JSON.parse(raw);
|
|
1118
|
+
return typeof parsed === "object" && parsed !== null ? parsed : {};
|
|
1119
|
+
} catch {
|
|
1120
|
+
return {};
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
function mapOllamaDoneReason(reason, hasToolCalls) {
|
|
1124
|
+
if (hasToolCalls) return "tool_use";
|
|
1125
|
+
if (reason === "length") return "max_tokens";
|
|
1126
|
+
if (reason === "stop") return "end_turn";
|
|
1127
|
+
return "end_turn";
|
|
1128
|
+
}
|
|
1129
|
+
async function* parseNdjsonStream(body, signal) {
|
|
1130
|
+
const reader = body.getReader();
|
|
1131
|
+
const decoder = new TextDecoder("utf-8");
|
|
1132
|
+
let buffer = "";
|
|
1133
|
+
try {
|
|
1134
|
+
while (true) {
|
|
1135
|
+
if (signal.aborted) return;
|
|
1136
|
+
const { done, value } = await reader.read();
|
|
1137
|
+
if (done) break;
|
|
1138
|
+
buffer += decoder.decode(value, { stream: true });
|
|
1139
|
+
const lines = buffer.split("\n");
|
|
1140
|
+
buffer = lines.pop() ?? "";
|
|
1141
|
+
for (const line of lines) {
|
|
1142
|
+
const trimmed = line.trim();
|
|
1143
|
+
if (trimmed.length === 0) continue;
|
|
1144
|
+
try {
|
|
1145
|
+
yield JSON.parse(trimmed);
|
|
1146
|
+
} catch {
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
const tail = buffer.trim();
|
|
1151
|
+
if (tail.length > 0) {
|
|
1152
|
+
try {
|
|
1153
|
+
yield JSON.parse(tail);
|
|
1154
|
+
} catch {
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
} finally {
|
|
1158
|
+
try {
|
|
1159
|
+
await reader.cancel();
|
|
1160
|
+
} catch {
|
|
1161
|
+
}
|
|
1162
|
+
reader.releaseLock();
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
var ollamaSystemText = collapseSystemText;
|
|
1166
|
+
function buildOllamaChatBody(request) {
|
|
1167
|
+
const messages = [];
|
|
1168
|
+
const systemText = ollamaSystemText(request.system);
|
|
1169
|
+
if (systemText.length > 0) {
|
|
1170
|
+
messages.push({ role: "system", content: systemText });
|
|
1171
|
+
}
|
|
1172
|
+
for (const message of request.messages) {
|
|
1173
|
+
for (const out of toOllamaMessages(message)) messages.push(out);
|
|
1174
|
+
}
|
|
1175
|
+
const tools = toOllamaTools(request.tools);
|
|
1176
|
+
const body = {
|
|
1177
|
+
model: request.model,
|
|
1178
|
+
stream: true,
|
|
1179
|
+
messages,
|
|
1180
|
+
// ADR D192: 24h keep_alive prevents Ollama from evicting the chat model
|
|
1181
|
+
// when other models (embeddings, judges) are loaded between turns.
|
|
1182
|
+
// Cuts cold-load latency from 20-30s to single-digit ms per turn.
|
|
1183
|
+
keep_alive: process.env.OLLAMA_KEEP_ALIVE ?? "24h"
|
|
1184
|
+
};
|
|
1185
|
+
if (tools.length > 0) body.tools = tools;
|
|
1186
|
+
const opts = {};
|
|
1187
|
+
if (request.maxTokens !== void 0) opts.num_predict = request.maxTokens;
|
|
1188
|
+
if (request.temperature !== void 0) opts.temperature = request.temperature;
|
|
1189
|
+
if (Object.keys(opts).length > 0) body.options = opts;
|
|
1190
|
+
return body;
|
|
1191
|
+
}
|
|
1192
|
+
function toOllamaMessages(message) {
|
|
1193
|
+
if (message.role === "system") {
|
|
1194
|
+
return [{ role: "system", content: joinTextParts(message) }];
|
|
1195
|
+
}
|
|
1196
|
+
if (message.role === "user") {
|
|
1197
|
+
if (message.content.some((p) => p.type === "image")) {
|
|
1198
|
+
throw new ConfigurationError(
|
|
1199
|
+
"image input is not supported on the ollama-native provider; use an OpenAI/OpenRouter model for multimodal turns",
|
|
1200
|
+
{ code: "ollama_image_unsupported" }
|
|
1201
|
+
);
|
|
1202
|
+
}
|
|
1203
|
+
const out = [];
|
|
1204
|
+
for (const part of message.content) {
|
|
1205
|
+
if (part.type === "tool_result") {
|
|
1206
|
+
out.push({
|
|
1207
|
+
role: "tool",
|
|
1208
|
+
// SE7 — string-only tool role: text blocks flatten; image fails fast.
|
|
1209
|
+
content: toStringToolResultContent(part.content, "ollama"),
|
|
1210
|
+
tool_name: part.toolUseId
|
|
1211
|
+
});
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
const userText = joinTextParts(message);
|
|
1215
|
+
if (userText.length > 0) out.push({ role: "user", content: userText });
|
|
1216
|
+
return out;
|
|
1217
|
+
}
|
|
1218
|
+
const text = joinTextParts(message);
|
|
1219
|
+
const toolCalls = [];
|
|
1220
|
+
for (const part of message.content) {
|
|
1221
|
+
if (part.type === "tool_use") {
|
|
1222
|
+
toolCalls.push({
|
|
1223
|
+
function: { name: part.name, arguments: part.input }
|
|
1224
|
+
});
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
const msg = { role: "assistant", content: text };
|
|
1228
|
+
if (toolCalls.length > 0) msg.tool_calls = toolCalls;
|
|
1229
|
+
return [msg];
|
|
1230
|
+
}
|
|
1231
|
+
function joinTextParts(message) {
|
|
1232
|
+
return message.content.filter((p) => p.type === "text").map((p) => p.text).join("\n");
|
|
1233
|
+
}
|
|
1234
|
+
function toOllamaTools(tools) {
|
|
1235
|
+
if (tools === void 0 || tools.length === 0) return [];
|
|
1236
|
+
return tools.map((tool) => ({
|
|
1237
|
+
type: "function",
|
|
1238
|
+
function: {
|
|
1239
|
+
name: tool.name,
|
|
1240
|
+
description: tool.description,
|
|
1241
|
+
parameters: tool.inputSchema
|
|
1242
|
+
}
|
|
1243
|
+
}));
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
// src/internal/llm/hermes-tool-extract.ts
|
|
1247
|
+
var HERMES_BLOCK = /<function=\s*([^>\s]+)\s*>([\s\S]*?)<\/tool_call>/g;
|
|
1248
|
+
var HERMES_PARAM = /<parameter=\s*([^>\s]+)\s*>([\s\S]*?)<\/parameter>/g;
|
|
1249
|
+
function extractHermesToolCalls(content, makeId, allowedToolNames) {
|
|
1250
|
+
const isPromoted = (name) => name.length > 0 && (allowedToolNames === void 0 || allowedToolNames.has(name));
|
|
1251
|
+
const toolCalls = [];
|
|
1252
|
+
const droppedNames = [];
|
|
1253
|
+
for (const block of content.matchAll(HERMES_BLOCK)) {
|
|
1254
|
+
const name = (block[1] ?? "").trim();
|
|
1255
|
+
if (!isPromoted(name)) {
|
|
1256
|
+
if (name.length > 0 && allowedToolNames !== void 0) droppedNames.push(name);
|
|
1257
|
+
continue;
|
|
1258
|
+
}
|
|
1259
|
+
toolCalls.push({
|
|
1260
|
+
type: "tool_use",
|
|
1261
|
+
id: makeId(),
|
|
1262
|
+
name,
|
|
1263
|
+
input: parseHermesParams(block[2] ?? "")
|
|
1264
|
+
});
|
|
1265
|
+
}
|
|
1266
|
+
const residualText = toolCalls.length === 0 ? content : content.replace(
|
|
1267
|
+
HERMES_BLOCK,
|
|
1268
|
+
(full, rawName) => isPromoted((rawName ?? "").trim()) ? "" : full
|
|
1269
|
+
).trim();
|
|
1270
|
+
return { toolCalls, residualText, droppedNames };
|
|
1271
|
+
}
|
|
1272
|
+
function parseHermesParams(inner) {
|
|
1273
|
+
const input = {};
|
|
1274
|
+
for (const param of inner.matchAll(HERMES_PARAM)) {
|
|
1275
|
+
const key = param[1];
|
|
1276
|
+
const value = param[2];
|
|
1277
|
+
if (key === void 0 || value === void 0) continue;
|
|
1278
|
+
input[key.trim()] = value;
|
|
1279
|
+
}
|
|
1280
|
+
return sanitizeToolInput(input, { trim: true }).value;
|
|
1281
|
+
}
|
|
1282
|
+
var STREAM_MARKER = "<function=";
|
|
1283
|
+
var DEFAULT_STREAM_BUFFER_CAP = 8192;
|
|
1284
|
+
var isStreamWs = (c) => c === " " || c === " " || c === "\n" || c === "\r";
|
|
1285
|
+
function streamToolCallBufferState(held, allowedToolNames, cap = DEFAULT_STREAM_BUFFER_CAP) {
|
|
1286
|
+
if (allowedToolNames.size === 0) return "impossible";
|
|
1287
|
+
const t = held.trimStart();
|
|
1288
|
+
if (t.length < STREAM_MARKER.length) {
|
|
1289
|
+
return STREAM_MARKER.startsWith(t) ? "possible" : "impossible";
|
|
1290
|
+
}
|
|
1291
|
+
if (!t.startsWith(STREAM_MARKER)) return "impossible";
|
|
1292
|
+
const parsed = parseStreamMarkerName(t);
|
|
1293
|
+
if (parsed === "building") return "possible";
|
|
1294
|
+
if (parsed === "invalid") return "impossible";
|
|
1295
|
+
const nameOk = parsed.complete ? allowedToolNames.has(parsed.name) : someToolNameStartsWith(allowedToolNames, parsed.name);
|
|
1296
|
+
if (!nameOk) return "impossible";
|
|
1297
|
+
return held.length > cap ? "impossible" : "possible";
|
|
1298
|
+
}
|
|
1299
|
+
function parseStreamMarkerName(t) {
|
|
1300
|
+
let cursor = STREAM_MARKER.length;
|
|
1301
|
+
while (cursor < t.length && isStreamWs(t[cursor])) cursor += 1;
|
|
1302
|
+
const nameStart = cursor;
|
|
1303
|
+
while (cursor < t.length && t[cursor] !== ">" && !isStreamWs(t[cursor])) cursor += 1;
|
|
1304
|
+
const name = t.slice(nameStart, cursor);
|
|
1305
|
+
if (name.length === 0) return cursor >= t.length ? "building" : "invalid";
|
|
1306
|
+
return { name, complete: cursor < t.length && t[cursor] === ">" };
|
|
1307
|
+
}
|
|
1308
|
+
function someToolNameStartsWith(allowedToolNames, prefix) {
|
|
1309
|
+
for (const name of allowedToolNames) {
|
|
1310
|
+
if (name.startsWith(prefix)) return true;
|
|
1311
|
+
}
|
|
1312
|
+
return false;
|
|
1313
|
+
}
|
|
1314
|
+
function firstPossibleMarkerStart(held, allowedToolNames) {
|
|
1315
|
+
for (let i = held.indexOf("<"); i !== -1; i = held.indexOf("<", i + 1)) {
|
|
1316
|
+
if (streamToolCallBufferState(held.slice(i), allowedToolNames) === "possible") return i;
|
|
1317
|
+
}
|
|
1318
|
+
return -1;
|
|
1319
|
+
}
|
|
1320
|
+
var StreamSuppressionBuffer = class {
|
|
1321
|
+
constructor(allowedToolNames) {
|
|
1322
|
+
this.allowedToolNames = allowedToolNames;
|
|
1323
|
+
}
|
|
1324
|
+
allowedToolNames;
|
|
1325
|
+
#held = "";
|
|
1326
|
+
/** Feed a content delta; returns the text to emit as a `text_delta` now, or `undefined` to hold. */
|
|
1327
|
+
push(content) {
|
|
1328
|
+
this.#held += content;
|
|
1329
|
+
if (streamToolCallBufferState(this.#held, this.allowedToolNames) === "possible")
|
|
1330
|
+
return void 0;
|
|
1331
|
+
const holdStart = firstPossibleMarkerStart(this.#held, this.allowedToolNames);
|
|
1332
|
+
if (holdStart > 0) {
|
|
1333
|
+
const flush2 = this.#held.slice(0, holdStart);
|
|
1334
|
+
this.#held = this.#held.slice(holdStart);
|
|
1335
|
+
return flush2;
|
|
1336
|
+
}
|
|
1337
|
+
const flush = this.#held;
|
|
1338
|
+
this.#held = "";
|
|
1339
|
+
return flush;
|
|
1340
|
+
}
|
|
1341
|
+
/** Drain the held buffer at stream end. `hasNativeCalls` mirrors `finish()`'s size-guard: when
|
|
1342
|
+
* native `tool_calls` exist, `finish()` won't strip the leaked block, so stream the held text WHOLE
|
|
1343
|
+
* (keeping `accumulatedText == finish.text`); otherwise strip the recoverable blocks. Idempotent. */
|
|
1344
|
+
drain(hasNativeCalls) {
|
|
1345
|
+
if (this.#held.length === 0) return void 0;
|
|
1346
|
+
const held = this.#held;
|
|
1347
|
+
this.#held = "";
|
|
1348
|
+
if (hasNativeCalls) return held;
|
|
1349
|
+
const residual = extractHermesToolCalls(held, () => "held", this.allowedToolNames).residualText;
|
|
1350
|
+
return residual.length > 0 ? residual : void 0;
|
|
1351
|
+
}
|
|
1352
|
+
};
|
|
1353
|
+
|
|
1354
|
+
// src/internal/llm/openai-messages.ts
|
|
1355
|
+
function toOpenAIMessages(message) {
|
|
1356
|
+
if (message.role === "system") return [systemMessage(message)];
|
|
1357
|
+
if (message.role === "user") return userOrToolMessages(message);
|
|
1358
|
+
return [assistantMessage(message)];
|
|
1359
|
+
}
|
|
1360
|
+
function systemMessage(message) {
|
|
1361
|
+
return { role: "system", content: joinTextParts2(message) };
|
|
1362
|
+
}
|
|
1363
|
+
function joinTextParts2(message) {
|
|
1364
|
+
return message.content.filter((part) => part.type === "text").map((part) => part.text).join("\n");
|
|
1365
|
+
}
|
|
1366
|
+
function userOrToolMessages(message) {
|
|
1367
|
+
const out = [];
|
|
1368
|
+
for (const part of message.content) {
|
|
1369
|
+
if (part.type === "tool_result") {
|
|
1370
|
+
out.push({
|
|
1371
|
+
role: "tool",
|
|
1372
|
+
tool_call_id: part.toolUseId,
|
|
1373
|
+
// SE7 — this wire's tool role is string-only: text blocks flatten; an
|
|
1374
|
+
// image block fails fast (ConfigurationError).
|
|
1375
|
+
content: toStringToolResultContent(part.content, "openai")
|
|
1376
|
+
});
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
const userText = joinTextParts2(message);
|
|
1380
|
+
const imageParts = message.content.filter(
|
|
1381
|
+
(p) => p.type === "image"
|
|
1382
|
+
);
|
|
1383
|
+
if (imageParts.length > 0) {
|
|
1384
|
+
const content = [];
|
|
1385
|
+
if (userText.length > 0) content.push({ type: "text", text: userText });
|
|
1386
|
+
for (const img of imageParts) {
|
|
1387
|
+
const url = img.source.type === "base64" ? `data:${img.source.media_type};base64,${img.source.data}` : img.source.url;
|
|
1388
|
+
content.push({ type: "image_url", image_url: { url } });
|
|
1389
|
+
}
|
|
1390
|
+
out.push({ role: "user", content });
|
|
1391
|
+
} else if (userText.length > 0) {
|
|
1392
|
+
out.push({ role: "user", content: userText });
|
|
1393
|
+
}
|
|
1394
|
+
return out;
|
|
1395
|
+
}
|
|
1396
|
+
function assistantMessage(message) {
|
|
1397
|
+
const text = joinTextParts2(message);
|
|
1398
|
+
const toolCalls = message.content.filter((part) => part.type === "tool_use").map((part) => {
|
|
1399
|
+
const tc = part;
|
|
1400
|
+
return {
|
|
1401
|
+
id: tc.id,
|
|
1402
|
+
type: "function",
|
|
1403
|
+
function: { name: tc.name, arguments: JSON.stringify(tc.input) }
|
|
1404
|
+
};
|
|
1405
|
+
});
|
|
1406
|
+
const result = { role: "assistant", content: text };
|
|
1407
|
+
if (toolCalls.length > 0) result.tool_calls = toolCalls;
|
|
1408
|
+
return result;
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
// src/internal/llm/openai.ts
|
|
1412
|
+
function deriveChatPath(baseUrl) {
|
|
1413
|
+
try {
|
|
1414
|
+
return /\/v\d+[a-z]*(\/|$)/.test(new URL(baseUrl).pathname) ? "/chat/completions" : "/v1/chat/completions";
|
|
1415
|
+
} catch {
|
|
1416
|
+
return "/v1/chat/completions";
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
var OpenAIClient = class {
|
|
1420
|
+
constructor(options) {
|
|
1421
|
+
this.options = options;
|
|
1422
|
+
this.baseUrl = (options.baseUrl ?? "https://api.openai.com").replace(/\/+$/, "");
|
|
1423
|
+
this.chatPath = options.chatCompletionsPath ?? deriveChatPath(this.baseUrl);
|
|
1424
|
+
this.fetchImpl = options.fetch ?? fetch;
|
|
1425
|
+
}
|
|
1426
|
+
options;
|
|
1427
|
+
name = "openai";
|
|
1428
|
+
baseUrl;
|
|
1429
|
+
chatPath;
|
|
1430
|
+
fetchImpl;
|
|
1431
|
+
/**
|
|
1432
|
+
* Whether this client recovers leaked Hermes tool-call dialect from assistant
|
|
1433
|
+
* text (theokit#58 follow-up). Observability for the route→client wiring of
|
|
1434
|
+
* `extractToolCallsFromContent`. @internal
|
|
1435
|
+
*/
|
|
1436
|
+
get recoversLeakedToolCalls() {
|
|
1437
|
+
return this.options.extractToolCallsFromContent ?? false;
|
|
1438
|
+
}
|
|
1439
|
+
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: HTTP+SSE handshake + accumulator is intentionally one block
|
|
1440
|
+
async *stream(request, signal) {
|
|
1441
|
+
const headers = {
|
|
1442
|
+
"content-type": "application/json",
|
|
1443
|
+
authorization: `Bearer ${this.options.apiKey}`
|
|
1444
|
+
};
|
|
1445
|
+
if (this.options.organization !== void 0) {
|
|
1446
|
+
headers["openai-organization"] = this.options.organization;
|
|
1447
|
+
}
|
|
1448
|
+
if (this.options.extraHeaders !== void 0) Object.assign(headers, this.options.extraHeaders);
|
|
1449
|
+
const providerId = this.options.providerName ?? this.name;
|
|
1450
|
+
let response;
|
|
1451
|
+
try {
|
|
1452
|
+
response = await this.fetchImpl(`${this.baseUrl}${this.chatPath}`, {
|
|
1453
|
+
method: "POST",
|
|
1454
|
+
signal,
|
|
1455
|
+
headers,
|
|
1456
|
+
body: JSON.stringify(buildOpenAIBody(request, providerId))
|
|
1457
|
+
});
|
|
1458
|
+
} catch (fetchErr) {
|
|
1459
|
+
const mapped = mapOllamaTransportError({
|
|
1460
|
+
providerId,
|
|
1461
|
+
cause: fetchErr,
|
|
1462
|
+
endpoint: "/v1/chat/completions"
|
|
1463
|
+
});
|
|
1464
|
+
if (mapped !== void 0) throw mapped;
|
|
1465
|
+
throw fetchErr;
|
|
1466
|
+
}
|
|
1467
|
+
if (!response.ok) {
|
|
1468
|
+
const text = await response.text().catch(() => "");
|
|
1469
|
+
let body = text;
|
|
1470
|
+
try {
|
|
1471
|
+
body = JSON.parse(text);
|
|
1472
|
+
} catch {
|
|
1473
|
+
}
|
|
1474
|
+
const ollamaMapped = mapOllamaHttpError({
|
|
1475
|
+
providerId,
|
|
1476
|
+
status: response.status,
|
|
1477
|
+
body,
|
|
1478
|
+
headers: response.headers,
|
|
1479
|
+
endpoint: "/v1/chat/completions"
|
|
1480
|
+
});
|
|
1481
|
+
if (ollamaMapped !== void 0) throw ollamaMapped;
|
|
1482
|
+
throw mapOpenAICompatibleError({
|
|
1483
|
+
providerId,
|
|
1484
|
+
status: response.status,
|
|
1485
|
+
body,
|
|
1486
|
+
headers: response.headers,
|
|
1487
|
+
endpoint: "/v1/chat/completions"
|
|
1488
|
+
});
|
|
1489
|
+
}
|
|
1490
|
+
const accumulator = new OpenAIStreamAccumulator(
|
|
1491
|
+
this.options.extractToolCallsFromContent ?? false,
|
|
1492
|
+
providerId,
|
|
1493
|
+
// R5: request-scoped allowlist — leaked recovery only promotes a block whose name is a tool the
|
|
1494
|
+
// model was actually given. Empty set (no tools) recovers nothing.
|
|
1495
|
+
new Set(request.tools?.map((tool) => tool.name) ?? [])
|
|
1496
|
+
);
|
|
1497
|
+
let sawDone = false;
|
|
1498
|
+
for await (const record of parseSseStream(response.body, signal)) {
|
|
1499
|
+
if (record.data === "[DONE]") {
|
|
1500
|
+
sawDone = true;
|
|
1501
|
+
break;
|
|
1502
|
+
}
|
|
1503
|
+
let chunk;
|
|
1504
|
+
try {
|
|
1505
|
+
chunk = JSON.parse(record.data);
|
|
1506
|
+
} catch {
|
|
1507
|
+
continue;
|
|
1508
|
+
}
|
|
1509
|
+
if (chunk.error !== void 0 && chunk.error !== null) {
|
|
1510
|
+
const status = typeof chunk.error.code === "number" ? chunk.error.code : 502;
|
|
1511
|
+
throw mapOpenAICompatibleError({
|
|
1512
|
+
providerId,
|
|
1513
|
+
status,
|
|
1514
|
+
body: chunk,
|
|
1515
|
+
headers: response.headers,
|
|
1516
|
+
endpoint: "/v1/chat/completions"
|
|
1517
|
+
});
|
|
1518
|
+
}
|
|
1519
|
+
const events = accumulator.consume(chunk);
|
|
1520
|
+
for (const event of events) yield event;
|
|
1521
|
+
}
|
|
1522
|
+
if (!sawDone && !accumulator.finishReasonSeen) {
|
|
1523
|
+
throw new NetworkError("SSE stream truncated (no finish_reason / [DONE])", {
|
|
1524
|
+
code: "stream_truncated"
|
|
1525
|
+
});
|
|
1526
|
+
}
|
|
1527
|
+
const drainEvent = accumulator.finalizeHeldText();
|
|
1528
|
+
if (drainEvent !== void 0) yield drainEvent;
|
|
1529
|
+
return accumulator.finish();
|
|
1530
|
+
}
|
|
1531
|
+
};
|
|
1532
|
+
var OpenAIStreamAccumulator = class {
|
|
1533
|
+
/**
|
|
1534
|
+
* @param extractFromContent opt-in leaked-dialect safe-parse (theokit#58). Default false.
|
|
1535
|
+
* @param providerName provider id, used only to label the recovery log line.
|
|
1536
|
+
* @param allowedToolNames R5 request-scoped allowlist — built from `request.tools` at `stream()`;
|
|
1537
|
+
* leaked recovery in `finish()` only promotes a block whose name is in this set. `undefined`
|
|
1538
|
+
* (direct construction) recovers all (back-compat); an empty set recovers nothing.
|
|
1539
|
+
*/
|
|
1540
|
+
constructor(extractFromContent = false, providerName = "openai", allowedToolNames) {
|
|
1541
|
+
this.extractFromContent = extractFromContent;
|
|
1542
|
+
this.providerName = providerName;
|
|
1543
|
+
this.allowedToolNames = allowedToolNames;
|
|
1544
|
+
this.suppress = extractFromContent && allowedToolNames !== void 0 && allowedToolNames.size > 0 ? new StreamSuppressionBuffer(allowedToolNames) : void 0;
|
|
1545
|
+
}
|
|
1546
|
+
extractFromContent;
|
|
1547
|
+
providerName;
|
|
1548
|
+
allowedToolNames;
|
|
1549
|
+
text = "";
|
|
1550
|
+
stopReason = "end_turn";
|
|
1551
|
+
inputTokens;
|
|
1552
|
+
outputTokens;
|
|
1553
|
+
cacheReadTokens;
|
|
1554
|
+
cacheWriteTokens;
|
|
1555
|
+
reasoningTokens;
|
|
1556
|
+
toolCalls = /* @__PURE__ */ new Map();
|
|
1557
|
+
/** R7: present only when recovery is enabled AND the request declares tools — holds suspected
|
|
1558
|
+
* leaked-dialect content back from the `text_delta` stream. `undefined` ⇒ stream immediately. */
|
|
1559
|
+
suppress;
|
|
1560
|
+
consume(chunk) {
|
|
1561
|
+
const events = [];
|
|
1562
|
+
this.applyUsage(chunk.usage);
|
|
1563
|
+
for (const choice of chunk.choices ?? []) {
|
|
1564
|
+
events.push(...this.applyChoice(choice));
|
|
1565
|
+
}
|
|
1566
|
+
return events;
|
|
1567
|
+
}
|
|
1568
|
+
applyChoice(choice) {
|
|
1569
|
+
const events = [];
|
|
1570
|
+
const reasoningEvent = this.applyReasoningDelta(
|
|
1571
|
+
choice.delta?.reasoning ?? choice.delta?.reasoning_content
|
|
1572
|
+
);
|
|
1573
|
+
if (reasoningEvent !== void 0) events.push(reasoningEvent);
|
|
1574
|
+
const textEvent = this.applyContentDelta(choice.delta?.content);
|
|
1575
|
+
if (textEvent !== void 0) events.push(textEvent);
|
|
1576
|
+
this.mergeToolCallDeltas(choice.delta?.tool_calls);
|
|
1577
|
+
this.applyFinishReason(choice.finish_reason);
|
|
1578
|
+
if (choice.finish_reason !== void 0 && choice.finish_reason !== null) {
|
|
1579
|
+
const flushEvent = this.finalizeHeldText();
|
|
1580
|
+
if (flushEvent !== void 0) events.push(flushEvent);
|
|
1581
|
+
}
|
|
1582
|
+
return events;
|
|
1583
|
+
}
|
|
1584
|
+
applyReasoningDelta(reasoning) {
|
|
1585
|
+
if (typeof reasoning !== "string" || reasoning.length === 0) return void 0;
|
|
1586
|
+
return { type: "reasoning_delta", text: reasoning };
|
|
1587
|
+
}
|
|
1588
|
+
applyUsage(usage) {
|
|
1589
|
+
if (usage?.prompt_tokens !== void 0) this.inputTokens = usage.prompt_tokens;
|
|
1590
|
+
if (usage?.completion_tokens !== void 0) this.outputTokens = usage.completion_tokens;
|
|
1591
|
+
const cachedDetail = usage?.prompt_tokens_details?.cached_tokens;
|
|
1592
|
+
if (cachedDetail !== void 0) this.cacheReadTokens = cachedDetail;
|
|
1593
|
+
const reasoningDetail = usage?.completion_tokens_details?.reasoning_tokens;
|
|
1594
|
+
if (reasoningDetail !== void 0) this.reasoningTokens = reasoningDetail;
|
|
1595
|
+
if (usage?.cache_read_input_tokens !== void 0) {
|
|
1596
|
+
this.cacheReadTokens = usage.cache_read_input_tokens;
|
|
1597
|
+
}
|
|
1598
|
+
if (usage?.cache_creation_input_tokens !== void 0) {
|
|
1599
|
+
this.cacheWriteTokens = usage.cache_creation_input_tokens;
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
applyContentDelta(content) {
|
|
1603
|
+
if (typeof content !== "string" || content.length === 0) return void 0;
|
|
1604
|
+
this.text += content;
|
|
1605
|
+
if (this.suppress === void 0) return { type: "text_delta", text: content };
|
|
1606
|
+
const emit = this.suppress.push(content);
|
|
1607
|
+
return emit !== void 0 ? { type: "text_delta", text: emit } : void 0;
|
|
1608
|
+
}
|
|
1609
|
+
/** R7 held-buffer finalizer, called at the `finish_reason` chunk (in `applyChoice`) AND after the
|
|
1610
|
+
* SSE loop in `stream()` — so a stream that omits a `finish_reason` terminal never silently drops
|
|
1611
|
+
* held text. `toolCalls.size > 0` (native calls present) makes `finish()` skip recovery, so the
|
|
1612
|
+
* buffer streams the held text whole. Idempotent once drained. */
|
|
1613
|
+
finalizeHeldText() {
|
|
1614
|
+
const emit = this.suppress?.drain(this.toolCalls.size > 0);
|
|
1615
|
+
return emit !== void 0 ? { type: "text_delta", text: emit } : void 0;
|
|
1616
|
+
}
|
|
1617
|
+
mergeToolCallDeltas(deltas) {
|
|
1618
|
+
for (const call of deltas ?? []) {
|
|
1619
|
+
const existing = this.toolCalls.get(call.index) ?? { id: "", name: "", args: "" };
|
|
1620
|
+
if (call.id !== void 0) existing.id = call.id;
|
|
1621
|
+
if (call.function?.name !== void 0) existing.name = call.function.name;
|
|
1622
|
+
if (call.function?.arguments !== void 0) existing.args += call.function.arguments;
|
|
1623
|
+
this.toolCalls.set(call.index, existing);
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
/** M2 #61 — true once any chunk carried a non-null `finish_reason` (else a
|
|
1627
|
+
* stream ending without `[DONE]` is a truncation, not a clean end). */
|
|
1628
|
+
sawFinishReason = false;
|
|
1629
|
+
get finishReasonSeen() {
|
|
1630
|
+
return this.sawFinishReason;
|
|
1631
|
+
}
|
|
1632
|
+
applyFinishReason(reason) {
|
|
1633
|
+
if (reason === void 0 || reason === null) return;
|
|
1634
|
+
this.sawFinishReason = true;
|
|
1635
|
+
this.stopReason = mapOpenAIFinish(reason);
|
|
1636
|
+
}
|
|
1637
|
+
finish() {
|
|
1638
|
+
const toolCalls = [];
|
|
1639
|
+
for (const call of this.toolCalls.values()) {
|
|
1640
|
+
const input = parseToolArguments(call.args);
|
|
1641
|
+
toolCalls.push({ type: "tool_use", id: call.id, name: call.name, input });
|
|
1642
|
+
}
|
|
1643
|
+
let text = this.text;
|
|
1644
|
+
let stopReason = this.stopReason;
|
|
1645
|
+
if (this.extractFromContent && toolCalls.length === 0) {
|
|
1646
|
+
const recovered = extractHermesToolCalls(
|
|
1647
|
+
this.text,
|
|
1648
|
+
() => `hermes-${globalThis.crypto.randomUUID()}`,
|
|
1649
|
+
this.allowedToolNames
|
|
1650
|
+
);
|
|
1651
|
+
if (recovered.toolCalls.length > 0) {
|
|
1652
|
+
toolCalls.push(...recovered.toolCalls);
|
|
1653
|
+
text = recovered.residualText;
|
|
1654
|
+
stopReason = "tool_use";
|
|
1655
|
+
process.stderr.write(
|
|
1656
|
+
`[theokit-sdk] recovered ${recovered.toolCalls.length} leaked tool call(s) from assistant content (provider="${this.providerName}", names=${recovered.toolCalls.map((c) => c.name).join(",")})
|
|
1657
|
+
`
|
|
1658
|
+
);
|
|
1659
|
+
}
|
|
1660
|
+
if (recovered.droppedNames.length > 0) {
|
|
1661
|
+
process.stderr.write(
|
|
1662
|
+
`[theokit-sdk] dropped ${recovered.droppedNames.length} leaked block(s) whose name is not a tool in the request (provider="${this.providerName}", names=${recovered.droppedNames.join(",")})
|
|
1663
|
+
`
|
|
1664
|
+
);
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
return makeLlmFinish({
|
|
1668
|
+
stopReason,
|
|
1669
|
+
text,
|
|
1670
|
+
toolCalls,
|
|
1671
|
+
inputTokens: this.inputTokens,
|
|
1672
|
+
outputTokens: this.outputTokens,
|
|
1673
|
+
cacheReadTokens: this.cacheReadTokens,
|
|
1674
|
+
cacheWriteTokens: this.cacheWriteTokens,
|
|
1675
|
+
reasoningTokens: this.reasoningTokens
|
|
1676
|
+
});
|
|
1677
|
+
}
|
|
1678
|
+
};
|
|
1679
|
+
function applyReasoningRequest(body, effort, providerName) {
|
|
1680
|
+
if (providerName === "openai") {
|
|
1681
|
+
body.reasoning_effort = effort;
|
|
1682
|
+
return;
|
|
1683
|
+
}
|
|
1684
|
+
body.reasoning = { effort };
|
|
1685
|
+
}
|
|
1686
|
+
function applyToolsRequest(body, request) {
|
|
1687
|
+
if (request.tools === void 0 || request.tools.length === 0) return;
|
|
1688
|
+
body.tools = request.tools.map((tool) => ({
|
|
1689
|
+
type: "function",
|
|
1690
|
+
function: { name: tool.name, description: tool.description, parameters: tool.inputSchema }
|
|
1691
|
+
}));
|
|
1692
|
+
if (request.toolChoice !== void 0) body.tool_choice = request.toolChoice;
|
|
1693
|
+
}
|
|
1694
|
+
function buildOpenAIBody(request, providerName) {
|
|
1695
|
+
const messages = [];
|
|
1696
|
+
const systemText = openAISystemText(request.system);
|
|
1697
|
+
if (systemText.length > 0) {
|
|
1698
|
+
messages.push({ role: "system", content: systemText });
|
|
1699
|
+
}
|
|
1700
|
+
for (const message of request.messages) {
|
|
1701
|
+
for (const out of toOpenAIMessages(message)) messages.push(out);
|
|
1702
|
+
}
|
|
1703
|
+
const body = {
|
|
1704
|
+
model: request.model,
|
|
1705
|
+
stream: true,
|
|
1706
|
+
// D376: opt-in to OpenAI's final-chunk usage record (no-op for providers
|
|
1707
|
+
// that don't honor it; OpenRouter / OpenAI both respect it).
|
|
1708
|
+
stream_options: { include_usage: true },
|
|
1709
|
+
messages
|
|
1710
|
+
};
|
|
1711
|
+
if (request.maxTokens !== void 0) body.max_tokens = request.maxTokens;
|
|
1712
|
+
if (request.temperature !== void 0) body.temperature = request.temperature;
|
|
1713
|
+
if (request.reasoning?.effort !== void 0)
|
|
1714
|
+
applyReasoningRequest(body, request.reasoning.effort, providerName);
|
|
1715
|
+
applyToolsRequest(body, request);
|
|
1716
|
+
const responseFormat = encodeOpenAIResponseFormat(request.responseFormat);
|
|
1717
|
+
if (responseFormat !== void 0) body.response_format = responseFormat;
|
|
1718
|
+
return body;
|
|
1719
|
+
}
|
|
1720
|
+
var openAISystemText = collapseSystemText;
|
|
1721
|
+
function encodeOpenAIResponseFormat(rf) {
|
|
1722
|
+
if (rf === void 0) return void 0;
|
|
1723
|
+
if (rf.type === "json_object") return { type: "json_object" };
|
|
1724
|
+
return {
|
|
1725
|
+
type: "json_schema",
|
|
1726
|
+
json_schema: {
|
|
1727
|
+
name: rf.jsonSchema.name,
|
|
1728
|
+
schema: rf.jsonSchema.schema,
|
|
1729
|
+
strict: rf.jsonSchema.strict ?? true
|
|
1730
|
+
}
|
|
1731
|
+
};
|
|
1732
|
+
}
|
|
1733
|
+
|
|
1734
|
+
// src/internal/resilience/circuit-breaker.ts
|
|
1735
|
+
var DEFAULT_MAX_TIMEOUTS = 3;
|
|
1736
|
+
var DEFAULT_COOLDOWN_MS = 6e4;
|
|
1737
|
+
var CircuitBreaker = class {
|
|
1738
|
+
constructor(opts = {}) {
|
|
1739
|
+
this.opts = opts;
|
|
1740
|
+
}
|
|
1741
|
+
opts;
|
|
1742
|
+
states = /* @__PURE__ */ new Map();
|
|
1743
|
+
/** @returns true when the breaker is open and the call should be skipped. */
|
|
1744
|
+
shouldSkip(key) {
|
|
1745
|
+
const state = this.states.get(key);
|
|
1746
|
+
if (state === void 0) return false;
|
|
1747
|
+
if (state.cooldownUntilMs === 0) return false;
|
|
1748
|
+
if (this.now() < state.cooldownUntilMs) return true;
|
|
1749
|
+
state.cooldownUntilMs = 0;
|
|
1750
|
+
state.consecutiveTimeouts = 0;
|
|
1751
|
+
return false;
|
|
1752
|
+
}
|
|
1753
|
+
recordSuccess(key) {
|
|
1754
|
+
const state = this.states.get(key);
|
|
1755
|
+
if (state === void 0) return;
|
|
1756
|
+
state.consecutiveTimeouts = 0;
|
|
1757
|
+
state.cooldownUntilMs = 0;
|
|
1758
|
+
}
|
|
1759
|
+
recordTimeout(key) {
|
|
1760
|
+
const state = this.states.get(key) ?? { consecutiveTimeouts: 0, cooldownUntilMs: 0 };
|
|
1761
|
+
state.consecutiveTimeouts += 1;
|
|
1762
|
+
if (state.consecutiveTimeouts >= (this.opts.maxTimeouts ?? DEFAULT_MAX_TIMEOUTS)) {
|
|
1763
|
+
state.cooldownUntilMs = this.now() + (this.opts.cooldownMs ?? DEFAULT_COOLDOWN_MS);
|
|
1764
|
+
}
|
|
1765
|
+
this.states.set(key, state);
|
|
1766
|
+
}
|
|
1767
|
+
/** @internal — tests inspect counter state. */
|
|
1768
|
+
inspect(key) {
|
|
1769
|
+
return this.states.get(key) ?? { consecutiveTimeouts: 0, cooldownUntilMs: 0 };
|
|
1770
|
+
}
|
|
1771
|
+
now() {
|
|
1772
|
+
return this.opts.now?.() ?? Date.now();
|
|
1773
|
+
}
|
|
1774
|
+
};
|
|
1775
|
+
|
|
1776
|
+
// src/internal/llm/stream-relay.ts
|
|
1777
|
+
async function tryFirstEvent(client, request, signal, logFallback = false) {
|
|
1778
|
+
const generator = client.stream(request, signal);
|
|
1779
|
+
try {
|
|
1780
|
+
const firstResult = await generator.next();
|
|
1781
|
+
return { kind: "ok", generator, firstResult };
|
|
1782
|
+
} catch (cause) {
|
|
1783
|
+
if (cause instanceof NetworkError || cause instanceof RateLimitError || cause instanceof AuthenticationError) {
|
|
1784
|
+
if (logFallback) {
|
|
1785
|
+
const errCode = cause.metadata?.code ?? cause.code ?? "unknown";
|
|
1786
|
+
process.stderr.write(
|
|
1787
|
+
`[theokit-sdk] provider ${client.name} failed (${errCode}): falling back
|
|
1788
|
+
`
|
|
1789
|
+
);
|
|
1790
|
+
}
|
|
1791
|
+
return { kind: "handshake_error", error: cause };
|
|
1792
|
+
}
|
|
1793
|
+
throw cause;
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
async function* relayStream(generator, firstResult) {
|
|
1797
|
+
if (firstResult.done === true) return firstResult.value;
|
|
1798
|
+
yield firstResult.value;
|
|
1799
|
+
while (true) {
|
|
1800
|
+
const next = await generator.next();
|
|
1801
|
+
if (next.done === true) return next.value;
|
|
1802
|
+
yield next.value;
|
|
1803
|
+
}
|
|
1804
|
+
}
|
|
1805
|
+
|
|
1806
|
+
// src/internal/llm/pool-aware-client.ts
|
|
1807
|
+
var PoolAwareLlmClient = class {
|
|
1808
|
+
constructor(pool, buildClient2, waitForAvailableMs = 3e4, resilience = {}) {
|
|
1809
|
+
this.pool = pool;
|
|
1810
|
+
this.buildClient = buildClient2;
|
|
1811
|
+
this.waitForAvailableMs = waitForAvailableMs;
|
|
1812
|
+
this.name = `pool-aware:${pool.provider}`;
|
|
1813
|
+
this.breaker = resilience.breaker ?? new CircuitBreaker();
|
|
1814
|
+
this.backoffBaseMs = resilience.backoffBaseMs;
|
|
1815
|
+
this.rng = resilience.rng;
|
|
1816
|
+
this.onRateLimit = resilience.onRateLimit;
|
|
1817
|
+
}
|
|
1818
|
+
pool;
|
|
1819
|
+
buildClient;
|
|
1820
|
+
waitForAvailableMs;
|
|
1821
|
+
name;
|
|
1822
|
+
onRateLimit;
|
|
1823
|
+
/** M2 #60 — provider-level circuit breaker (consecutive-failure). */
|
|
1824
|
+
breaker;
|
|
1825
|
+
backoffBaseMs;
|
|
1826
|
+
rng;
|
|
1827
|
+
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: stream() must serialize pool-select → build client → first-event probe → classify → retry/rotate/propagate. Extracting helpers fragments the linear narrative; the comments above each branch keep it readable.
|
|
1828
|
+
async *stream(request, signal) {
|
|
1829
|
+
if (this.breaker.shouldSkip(this.pool.provider)) {
|
|
1830
|
+
throw new NetworkError(`${this.pool.provider} circuit open \u2014 failing fast`, {
|
|
1831
|
+
code: "circuit_open"
|
|
1832
|
+
});
|
|
1833
|
+
}
|
|
1834
|
+
let hasRetried429 = false;
|
|
1835
|
+
while (true) {
|
|
1836
|
+
if (signal.aborted) throw abortError(signal);
|
|
1837
|
+
let entry = await this.pool.select();
|
|
1838
|
+
if (entry === null) {
|
|
1839
|
+
if (this.waitForAvailableMs > 0) {
|
|
1840
|
+
const available = await this.pool.waitForAvailable(signal, {
|
|
1841
|
+
maxWaitMs: this.waitForAvailableMs
|
|
1842
|
+
});
|
|
1843
|
+
if (available) {
|
|
1844
|
+
entry = await this.pool.select();
|
|
1845
|
+
}
|
|
1846
|
+
}
|
|
1847
|
+
if (entry === null) {
|
|
1848
|
+
this.breaker.recordTimeout(this.pool.provider);
|
|
1849
|
+
throw new CredentialPoolExhaustedError(
|
|
1850
|
+
`All ${this.pool.provider} credentials exhausted; next retry available at ${this.nextRetryHint() ?? "unknown"}`,
|
|
1851
|
+
{ provider: this.pool.provider, nextRetryAt: this.nextRetryHint() }
|
|
1852
|
+
);
|
|
1853
|
+
}
|
|
1854
|
+
}
|
|
1855
|
+
const realClient = this.buildClient(entry.accessToken);
|
|
1856
|
+
const attempt = await tryFirstEvent(realClient, request, signal);
|
|
1857
|
+
if (attempt.kind === "ok") {
|
|
1858
|
+
this.breaker.recordSuccess(this.pool.provider);
|
|
1859
|
+
return yield* relayStream(attempt.generator, attempt.firstResult);
|
|
1860
|
+
}
|
|
1861
|
+
const decision = classifyAndDecide(attempt.error, hasRetried429);
|
|
1862
|
+
if (decision === "retry") {
|
|
1863
|
+
const backoffMs = computeBackoffMs({
|
|
1864
|
+
attempt: 0,
|
|
1865
|
+
...this.backoffBaseMs !== void 0 ? { baseMs: this.backoffBaseMs } : {},
|
|
1866
|
+
...this.rng !== void 0 ? { rng: this.rng } : {}
|
|
1867
|
+
});
|
|
1868
|
+
this.onRateLimit?.({ attempt: 1, retryAfterMs: backoffMs });
|
|
1869
|
+
await sleepWithAbort(backoffMs, signal);
|
|
1870
|
+
hasRetried429 = true;
|
|
1871
|
+
continue;
|
|
1872
|
+
}
|
|
1873
|
+
if (decision === "rotate") {
|
|
1874
|
+
try {
|
|
1875
|
+
await this.pool.markExhaustedAndRotate({
|
|
1876
|
+
entryId: entry.id,
|
|
1877
|
+
statusCode: attempt.error.metadata?.statusCode ?? inferStatusCode(attempt.error),
|
|
1878
|
+
...parseRetryAfterMs(attempt.error) !== void 0 ? { resetAtMs: parseRetryAfterMs(attempt.error) } : {}
|
|
1879
|
+
});
|
|
1880
|
+
} catch (persistErr) {
|
|
1881
|
+
process.stderr.write(
|
|
1882
|
+
`[theokit-sdk] credential-pool: persist failed during rotate; continuing in-memory: ${persistErr instanceof Error ? persistErr.message : String(persistErr)}
|
|
1883
|
+
`
|
|
1884
|
+
);
|
|
1885
|
+
}
|
|
1886
|
+
hasRetried429 = false;
|
|
1887
|
+
continue;
|
|
1888
|
+
}
|
|
1889
|
+
this.breaker.recordTimeout(this.pool.provider);
|
|
1890
|
+
throw attempt.error;
|
|
1891
|
+
}
|
|
1892
|
+
}
|
|
1893
|
+
/**
|
|
1894
|
+
* Earliest epoch ms among entries' `lastErrorResetAt` — best estimate
|
|
1895
|
+
* for the caller's `CredentialPoolExhaustedError.nextRetryAt`.
|
|
1896
|
+
*/
|
|
1897
|
+
nextRetryHint() {
|
|
1898
|
+
const resets = this.pool.list().map((e) => e.lastErrorResetAt).filter((v) => v !== void 0);
|
|
1899
|
+
return resets.length === 0 ? void 0 : Math.min(...resets);
|
|
1900
|
+
}
|
|
1901
|
+
};
|
|
1902
|
+
function classifyAndDecide(error, hasRetried429) {
|
|
1903
|
+
if (error instanceof NetworkError) return "propagate";
|
|
1904
|
+
if (error instanceof AuthenticationError) return "rotate";
|
|
1905
|
+
const status = error.metadata?.statusCode ?? 429;
|
|
1906
|
+
if (status === 402) return "rotate";
|
|
1907
|
+
return hasRetried429 ? "rotate" : "retry";
|
|
1908
|
+
}
|
|
1909
|
+
function parseRetryAfterMs(error) {
|
|
1910
|
+
const seconds = error.metadata?.retryAfter;
|
|
1911
|
+
if (typeof seconds === "number" && seconds > 0) {
|
|
1912
|
+
return Date.now() + seconds * 1e3;
|
|
1913
|
+
}
|
|
1914
|
+
return void 0;
|
|
1915
|
+
}
|
|
1916
|
+
function inferStatusCode(error) {
|
|
1917
|
+
if (error instanceof AuthenticationError) return 401;
|
|
1918
|
+
if (error instanceof RateLimitError) return 429;
|
|
1919
|
+
return 0;
|
|
1920
|
+
}
|
|
1921
|
+
function abortError(signal) {
|
|
1922
|
+
if (signal.reason instanceof Error) return signal.reason;
|
|
1923
|
+
return new Error("AbortError");
|
|
1924
|
+
}
|
|
1925
|
+
|
|
1926
|
+
// src/internal/llm/responses.ts
|
|
1927
|
+
function messageToInputItems(message) {
|
|
1928
|
+
const items = [];
|
|
1929
|
+
if (message.role === "user") {
|
|
1930
|
+
const content = [];
|
|
1931
|
+
for (const part of message.content) {
|
|
1932
|
+
if (part.type === "text") {
|
|
1933
|
+
content.push({ type: "input_text", text: part.text });
|
|
1934
|
+
} else if (part.type === "image") {
|
|
1935
|
+
const url = part.source.type === "base64" ? `data:${part.source.media_type};base64,${part.source.data}` : part.source.url;
|
|
1936
|
+
content.push({ type: "input_image", image_url: url });
|
|
1937
|
+
} else if (part.type === "tool_result") {
|
|
1938
|
+
items.push({
|
|
1939
|
+
type: "function_call_output",
|
|
1940
|
+
call_id: part.toolUseId,
|
|
1941
|
+
output: toStringToolResultContent(part.content, "openai-responses")
|
|
1942
|
+
});
|
|
1943
|
+
}
|
|
1944
|
+
}
|
|
1945
|
+
if (content.length > 0) items.push({ role: "user", content });
|
|
1946
|
+
return items;
|
|
1947
|
+
}
|
|
1948
|
+
if (message.role === "assistant") {
|
|
1949
|
+
const content = [];
|
|
1950
|
+
for (const part of message.content) {
|
|
1951
|
+
if (part.type === "text") {
|
|
1952
|
+
content.push({ type: "output_text", text: part.text });
|
|
1953
|
+
} else if (part.type === "tool_use") {
|
|
1954
|
+
items.push({
|
|
1955
|
+
type: "function_call",
|
|
1956
|
+
call_id: part.id,
|
|
1957
|
+
name: part.name,
|
|
1958
|
+
arguments: JSON.stringify(part.input)
|
|
1959
|
+
});
|
|
1960
|
+
}
|
|
1961
|
+
}
|
|
1962
|
+
if (content.length > 0) items.push({ role: "assistant", content });
|
|
1963
|
+
return items;
|
|
1964
|
+
}
|
|
1965
|
+
const text = message.content.filter((p) => p.type === "text").map((p) => p.text).join("\n");
|
|
1966
|
+
if (text.length > 0) items.push({ role: "system", content: text });
|
|
1967
|
+
return items;
|
|
1968
|
+
}
|
|
1969
|
+
function buildResponsesBody(request) {
|
|
1970
|
+
const input = [];
|
|
1971
|
+
for (const message of request.messages) {
|
|
1972
|
+
for (const item of messageToInputItems(message)) input.push(item);
|
|
1973
|
+
}
|
|
1974
|
+
const slash = request.model.lastIndexOf("/");
|
|
1975
|
+
const model = slash >= 0 ? request.model.slice(slash + 1) : request.model;
|
|
1976
|
+
const body = { model, input, stream: true, store: false };
|
|
1977
|
+
const instructions = collapseSystemText(request.system);
|
|
1978
|
+
if (instructions.length > 0) body.instructions = instructions;
|
|
1979
|
+
if (request.maxTokens !== void 0) body.max_output_tokens = request.maxTokens;
|
|
1980
|
+
if (request.temperature !== void 0) body.temperature = request.temperature;
|
|
1981
|
+
const tools = (request.tools ?? []).map((tool) => ({
|
|
1982
|
+
type: "function",
|
|
1983
|
+
name: tool.name,
|
|
1984
|
+
description: tool.description,
|
|
1985
|
+
parameters: tool.inputSchema,
|
|
1986
|
+
strict: false
|
|
1987
|
+
}));
|
|
1988
|
+
if (tools.length > 0) body.tools = tools;
|
|
1989
|
+
if (request.reasoning !== void 0) body.reasoning = { effort: request.reasoning.effort };
|
|
1990
|
+
return body;
|
|
1991
|
+
}
|
|
1992
|
+
var ResponsesApiClient = class {
|
|
1993
|
+
constructor(options) {
|
|
1994
|
+
this.options = options;
|
|
1995
|
+
this.name = options.providerName ?? "openai-responses";
|
|
1996
|
+
this.baseUrl = (options.baseUrl ?? "https://api.openai.com/v1").replace(/\/+$/, "");
|
|
1997
|
+
this.fetchImpl = options.fetch ?? fetch;
|
|
1998
|
+
}
|
|
1999
|
+
options;
|
|
2000
|
+
name;
|
|
2001
|
+
baseUrl;
|
|
2002
|
+
fetchImpl;
|
|
2003
|
+
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: the SSE dispatch (text / reasoning / tool-call add+delta+done / terminal+usage / error) is one cohesive state machine, mirroring OpenAIStreamAccumulator.consume.
|
|
2004
|
+
async *stream(request, signal) {
|
|
2005
|
+
const providerId = this.options.providerName ?? "openai";
|
|
2006
|
+
const headers = {
|
|
2007
|
+
"content-type": "application/json",
|
|
2008
|
+
accept: "text/event-stream",
|
|
2009
|
+
authorization: `Bearer ${this.options.apiKey}`,
|
|
2010
|
+
...this.options.extraHeaders ?? {}
|
|
2011
|
+
};
|
|
2012
|
+
const url = `${this.baseUrl}/responses`;
|
|
2013
|
+
const response = await this.fetchImpl(url, {
|
|
2014
|
+
method: "POST",
|
|
2015
|
+
signal,
|
|
2016
|
+
headers,
|
|
2017
|
+
body: JSON.stringify(buildResponsesBody(request))
|
|
2018
|
+
});
|
|
2019
|
+
if (!response.ok) {
|
|
2020
|
+
const text2 = await response.text().catch(() => "");
|
|
2021
|
+
let body = text2;
|
|
2022
|
+
try {
|
|
2023
|
+
body = JSON.parse(text2);
|
|
2024
|
+
} catch {
|
|
2025
|
+
}
|
|
2026
|
+
throw mapOpenAICompatibleError({
|
|
2027
|
+
providerId,
|
|
2028
|
+
status: response.status,
|
|
2029
|
+
body,
|
|
2030
|
+
headers: response.headers,
|
|
2031
|
+
endpoint: "/responses"
|
|
2032
|
+
});
|
|
2033
|
+
}
|
|
2034
|
+
let text = "";
|
|
2035
|
+
const toolCalls = [];
|
|
2036
|
+
let stopReason = "end_turn";
|
|
2037
|
+
let inputTokens;
|
|
2038
|
+
let outputTokens;
|
|
2039
|
+
let reasoningTokens;
|
|
2040
|
+
const pending = {};
|
|
2041
|
+
if (response.body !== null) {
|
|
2042
|
+
for await (const record of parseSseStream(response.body, signal)) {
|
|
2043
|
+
if (record.data === "[DONE]") break;
|
|
2044
|
+
let event;
|
|
2045
|
+
try {
|
|
2046
|
+
event = JSON.parse(record.data);
|
|
2047
|
+
} catch {
|
|
2048
|
+
continue;
|
|
2049
|
+
}
|
|
2050
|
+
const t = event.type;
|
|
2051
|
+
if (t === "response.output_text.delta") {
|
|
2052
|
+
const d = event.delta ?? "";
|
|
2053
|
+
if (d.length > 0) {
|
|
2054
|
+
text += d;
|
|
2055
|
+
yield { type: "text_delta", text: d };
|
|
2056
|
+
}
|
|
2057
|
+
} else if (t === "response.reasoning_summary_text.delta" || t === "response.reasoning_text.delta") {
|
|
2058
|
+
const d = event.delta ?? "";
|
|
2059
|
+
if (d.length > 0) yield { type: "reasoning_delta", text: d };
|
|
2060
|
+
} else if (t === "response.output_item.added" && event.item?.type === "function_call") {
|
|
2061
|
+
const id = event.item.id ?? event.item.call_id ?? "call-0";
|
|
2062
|
+
pending[id] = {
|
|
2063
|
+
callId: event.item.call_id ?? id,
|
|
2064
|
+
name: event.item.name ?? "",
|
|
2065
|
+
args: event.item.arguments ?? ""
|
|
2066
|
+
};
|
|
2067
|
+
} else if (t === "response.function_call_arguments.delta") {
|
|
2068
|
+
const c = event.item_id !== void 0 ? pending[event.item_id] : void 0;
|
|
2069
|
+
if (c !== void 0) c.args += event.delta ?? "";
|
|
2070
|
+
} else if (t === "response.output_item.done" && event.item?.type === "function_call") {
|
|
2071
|
+
const id = event.item.id ?? event.item.call_id ?? "call-0";
|
|
2072
|
+
const c = pending[id] ?? {
|
|
2073
|
+
callId: event.item.call_id ?? id,
|
|
2074
|
+
name: event.item.name ?? "",
|
|
2075
|
+
args: event.item.arguments ?? ""
|
|
2076
|
+
};
|
|
2077
|
+
const rawArgs = event.item.arguments ?? c.args;
|
|
2078
|
+
const call = {
|
|
2079
|
+
type: "tool_use",
|
|
2080
|
+
id: c.callId,
|
|
2081
|
+
name: c.name.length > 0 ? c.name : event.item.name ?? "",
|
|
2082
|
+
input: parseToolArguments(rawArgs)
|
|
2083
|
+
};
|
|
2084
|
+
toolCalls.push(call);
|
|
2085
|
+
delete pending[id];
|
|
2086
|
+
yield { type: "tool_use", id: call.id, name: call.name, input: call.input };
|
|
2087
|
+
} else if (t === "response.completed" || t === "response.incomplete") {
|
|
2088
|
+
const usage = event.response?.usage;
|
|
2089
|
+
if (usage !== void 0) {
|
|
2090
|
+
inputTokens = usage.input_tokens;
|
|
2091
|
+
outputTokens = usage.output_tokens;
|
|
2092
|
+
reasoningTokens = usage.output_tokens_details?.reasoning_tokens;
|
|
2093
|
+
}
|
|
2094
|
+
stopReason = t === "response.incomplete" ? "max_tokens" : "end_turn";
|
|
2095
|
+
} else if (t === "response.failed" || t === "error") {
|
|
2096
|
+
const msg = event.response?.error?.message ?? event.message ?? "responses stream failed";
|
|
2097
|
+
yield { type: "error", message: msg };
|
|
2098
|
+
throw mapOpenAICompatibleError({
|
|
2099
|
+
providerId,
|
|
2100
|
+
status: 502,
|
|
2101
|
+
body: { error: { message: msg } },
|
|
2102
|
+
headers: response.headers,
|
|
2103
|
+
endpoint: "/responses"
|
|
2104
|
+
});
|
|
2105
|
+
}
|
|
2106
|
+
}
|
|
2107
|
+
}
|
|
2108
|
+
if (toolCalls.length > 0 && stopReason === "end_turn") stopReason = "tool_use";
|
|
2109
|
+
yield { type: "stop", reason: stopReason };
|
|
2110
|
+
return makeLlmFinish({
|
|
2111
|
+
stopReason,
|
|
2112
|
+
text,
|
|
2113
|
+
toolCalls,
|
|
2114
|
+
inputTokens,
|
|
2115
|
+
outputTokens,
|
|
2116
|
+
reasoningTokens
|
|
2117
|
+
});
|
|
2118
|
+
}
|
|
2119
|
+
};
|
|
2120
|
+
|
|
2121
|
+
// src/internal/error-mappers/vertex.ts
|
|
2122
|
+
var VERTEX_RULES = [
|
|
2123
|
+
{ test: (s, e) => s === 429 || e === "RESOURCE_EXHAUSTED", code: "rate_limit" },
|
|
2124
|
+
{ test: (s, e) => s === 401 || e === "UNAUTHENTICATED", code: "auth_unauthenticated" },
|
|
2125
|
+
{ test: (s, e) => s === 403 || e === "PERMISSION_DENIED", code: "auth_permission" },
|
|
2126
|
+
{
|
|
2127
|
+
test: (s, e) => s === 400 || e === "INVALID_ARGUMENT" || e === "FAILED_PRECONDITION" || e === "NOT_FOUND",
|
|
2128
|
+
code: "invalid_request"
|
|
2129
|
+
},
|
|
2130
|
+
{ test: (s, e) => s === 408 || e === "DEADLINE_EXCEEDED", code: "timeout" },
|
|
2131
|
+
{ test: (s) => s >= 500, code: "server_error" }
|
|
2132
|
+
];
|
|
2133
|
+
function classifyVertexError(args, errStatus) {
|
|
2134
|
+
for (const rule of VERTEX_RULES) {
|
|
2135
|
+
if (rule.test(args.status, errStatus)) return rule.code;
|
|
2136
|
+
}
|
|
2137
|
+
return "unknown";
|
|
2138
|
+
}
|
|
2139
|
+
function vertexMetadata(args, code) {
|
|
2140
|
+
return buildErrorMetadata({
|
|
2141
|
+
provider: "vertex",
|
|
2142
|
+
endpoint: args.endpoint,
|
|
2143
|
+
code,
|
|
2144
|
+
status: args.status,
|
|
2145
|
+
headers: args.headers,
|
|
2146
|
+
body: args.body
|
|
2147
|
+
});
|
|
2148
|
+
}
|
|
2149
|
+
var VERTEX_ERROR_BUILDERS = {
|
|
2150
|
+
rate_limit: (args, msg) => new RateLimitError(`Vertex quota exhausted: ${msg}`, {
|
|
2151
|
+
metadata: vertexMetadata(args, "rate_limit")
|
|
2152
|
+
}),
|
|
2153
|
+
auth_unauthenticated: (args, msg) => new AuthenticationError(`Vertex unauthenticated: ${msg}`, {
|
|
2154
|
+
metadata: vertexMetadata(args, "auth_failed")
|
|
2155
|
+
}),
|
|
2156
|
+
auth_permission: (args, msg) => new AuthenticationError(`Vertex permission denied: ${msg}`, {
|
|
2157
|
+
metadata: vertexMetadata(args, "auth_failed")
|
|
2158
|
+
}),
|
|
2159
|
+
invalid_request: (args, msg) => new ConfigurationError(`Vertex validation: ${msg}`, {
|
|
2160
|
+
metadata: vertexMetadata(args, "invalid_request")
|
|
2161
|
+
}),
|
|
2162
|
+
timeout: (args, msg) => new NetworkError(`Vertex timeout: ${msg}`, { metadata: vertexMetadata(args, "timeout") }),
|
|
2163
|
+
server_error: (args, msg) => new NetworkError(`Vertex server error: ${msg}`, {
|
|
2164
|
+
metadata: vertexMetadata(args, "server_error")
|
|
2165
|
+
}),
|
|
2166
|
+
unknown: (args, msg) => new UnknownAgentError(`Vertex unknown: ${msg}`, { metadata: vertexMetadata(args, "unknown") })
|
|
2167
|
+
};
|
|
2168
|
+
function mapVertexError(args) {
|
|
2169
|
+
const parsed = parseBody2(args.body);
|
|
2170
|
+
const errStatus = parsed.error?.status ?? "";
|
|
2171
|
+
const message = parsed.error?.message ?? "Vertex request failed";
|
|
2172
|
+
const code = classifyVertexError(args, errStatus);
|
|
2173
|
+
return VERTEX_ERROR_BUILDERS[code](args, message);
|
|
2174
|
+
}
|
|
2175
|
+
function parseBody2(body) {
|
|
2176
|
+
if (body !== null && typeof body === "object") return body;
|
|
2177
|
+
if (typeof body === "string") {
|
|
2178
|
+
try {
|
|
2179
|
+
return JSON.parse(body);
|
|
2180
|
+
} catch {
|
|
2181
|
+
return {};
|
|
2182
|
+
}
|
|
2183
|
+
}
|
|
2184
|
+
return {};
|
|
2185
|
+
}
|
|
2186
|
+
var cachedAuthClient;
|
|
2187
|
+
var SCOPE_CLOUD_PLATFORM = "https://www.googleapis.com/auth/cloud-platform";
|
|
2188
|
+
async function resolveVertexAccessToken() {
|
|
2189
|
+
if (cachedAuthClient === void 0) {
|
|
2190
|
+
cachedAuthClient = await getAuthClientOrThrow();
|
|
2191
|
+
}
|
|
2192
|
+
try {
|
|
2193
|
+
const result = await cachedAuthClient.getAccessToken();
|
|
2194
|
+
return result.token ?? void 0;
|
|
2195
|
+
} catch {
|
|
2196
|
+
return void 0;
|
|
2197
|
+
}
|
|
2198
|
+
}
|
|
2199
|
+
async function getAuthClientOrThrow() {
|
|
2200
|
+
let mod;
|
|
2201
|
+
try {
|
|
2202
|
+
const r = createRequire(import.meta.url);
|
|
2203
|
+
mod = r("google-auth-library");
|
|
2204
|
+
} catch (err) {
|
|
2205
|
+
const isModuleMissing = err instanceof Error && (err.message.includes("Cannot find module") || err.code === "MODULE_NOT_FOUND");
|
|
2206
|
+
if (isModuleMissing) {
|
|
2207
|
+
throw new ConfigurationError(
|
|
2208
|
+
"Vertex provider requires `google-auth-library`. Install: `pnpm add google-auth-library`.",
|
|
2209
|
+
{ code: "auth_failed", cause: err }
|
|
2210
|
+
);
|
|
2211
|
+
}
|
|
2212
|
+
throw err;
|
|
2213
|
+
}
|
|
2214
|
+
try {
|
|
2215
|
+
const auth = new mod.GoogleAuth({ scopes: [SCOPE_CLOUD_PLATFORM] });
|
|
2216
|
+
return await auth.getClient();
|
|
2217
|
+
} catch (err) {
|
|
2218
|
+
process.stderr.write(
|
|
2219
|
+
`[theokit-sdk] Vertex ADC initialization failed: ${err instanceof Error ? err.message : String(err)}
|
|
2220
|
+
`
|
|
2221
|
+
);
|
|
2222
|
+
return { getAccessToken: async () => ({ token: null }) };
|
|
2223
|
+
}
|
|
2224
|
+
}
|
|
2225
|
+
function resolveVertexProjectId() {
|
|
2226
|
+
return process.env.GOOGLE_CLOUD_PROJECT ?? process.env.GOOGLE_CLOUD_PROJECT_ID ?? process.env.GCLOUD_PROJECT;
|
|
2227
|
+
}
|
|
2228
|
+
function resolveVertexLocation() {
|
|
2229
|
+
return process.env.GOOGLE_CLOUD_LOCATION ?? process.env.CLOUD_ML_REGION ?? "us-central1";
|
|
2230
|
+
}
|
|
2231
|
+
|
|
2232
|
+
// src/internal/llm/vertex-anthropic.ts
|
|
2233
|
+
var VertexAnthropicClient = class {
|
|
2234
|
+
constructor(options) {
|
|
2235
|
+
this.options = options;
|
|
2236
|
+
this.fetchImpl = options.fetch ?? fetch;
|
|
2237
|
+
}
|
|
2238
|
+
options;
|
|
2239
|
+
name = "vertex_anthropic";
|
|
2240
|
+
fetchImpl;
|
|
2241
|
+
resolveProjectOrThrow() {
|
|
2242
|
+
const projectId = this.options.projectId ?? resolveVertexProjectId();
|
|
2243
|
+
if (projectId !== void 0) return projectId;
|
|
2244
|
+
throw new ConfigurationError(
|
|
2245
|
+
"Vertex requires a project id. Set GOOGLE_CLOUD_PROJECT env var or run `gcloud config set project <id>`.",
|
|
2246
|
+
{ code: "auth_failed" }
|
|
2247
|
+
);
|
|
2248
|
+
}
|
|
2249
|
+
async resolveTokenOrThrow() {
|
|
2250
|
+
const fromOpts = this.options.apiKey;
|
|
2251
|
+
if (fromOpts !== void 0 && fromOpts.length > 0) return fromOpts;
|
|
2252
|
+
const fromAdc = await resolveVertexAccessToken();
|
|
2253
|
+
if (fromAdc !== void 0 && fromAdc.length > 0) return fromAdc;
|
|
2254
|
+
throw new ConfigurationError(
|
|
2255
|
+
"Vertex could not resolve an access token. Run `gcloud auth application-default login` or set GOOGLE_APPLICATION_CREDENTIALS to a service-account JSON path.",
|
|
2256
|
+
{ code: "auth_failed" }
|
|
2257
|
+
);
|
|
2258
|
+
}
|
|
2259
|
+
async *stream(request, signal) {
|
|
2260
|
+
const projectId = this.resolveProjectOrThrow();
|
|
2261
|
+
const location = this.options.location ?? resolveVertexLocation();
|
|
2262
|
+
const accessToken = await this.resolveTokenOrThrow();
|
|
2263
|
+
const baseUrl = resolveVertexBaseUrl({ projectId, location, modelDialect: "anthropic" });
|
|
2264
|
+
const bareModel = stripVertexPrefix(request.model);
|
|
2265
|
+
const endpoint = `/models/${encodeURIComponent(bareModel)}:rawPredict`;
|
|
2266
|
+
const url = `${baseUrl}${endpoint}`;
|
|
2267
|
+
const response = await postAnthropicRequest({
|
|
2268
|
+
fetchImpl: this.fetchImpl,
|
|
2269
|
+
url,
|
|
2270
|
+
token: accessToken,
|
|
2271
|
+
body: buildVertexAnthropicBody(request),
|
|
2272
|
+
signal
|
|
2273
|
+
});
|
|
2274
|
+
const consumed = await handleAnthropicResponse({
|
|
2275
|
+
response,
|
|
2276
|
+
endpoint,
|
|
2277
|
+
errorMapper: mapVertexError
|
|
2278
|
+
});
|
|
2279
|
+
if (consumed.text.length > 0) yield { type: "text_delta", text: consumed.text };
|
|
2280
|
+
return makeLlmFinish(consumed);
|
|
2281
|
+
}
|
|
2282
|
+
};
|
|
2283
|
+
function buildVertexAnthropicBody(request) {
|
|
2284
|
+
return {
|
|
2285
|
+
anthropic_version: "vertex-2023-10-16",
|
|
2286
|
+
...buildAnthropicCommonBody(request)
|
|
2287
|
+
};
|
|
2288
|
+
}
|
|
2289
|
+
|
|
2290
|
+
// src/internal/llm/vertex-gemini.ts
|
|
2291
|
+
var VertexGeminiClient = class {
|
|
2292
|
+
constructor(options) {
|
|
2293
|
+
this.options = options;
|
|
2294
|
+
}
|
|
2295
|
+
options;
|
|
2296
|
+
name = "vertex_gemini";
|
|
2297
|
+
async *stream(request, signal) {
|
|
2298
|
+
const projectId = this.options.projectId ?? resolveVertexProjectId();
|
|
2299
|
+
if (projectId === void 0) {
|
|
2300
|
+
throw new ConfigurationError(
|
|
2301
|
+
"Vertex requires a project id. Set GOOGLE_CLOUD_PROJECT env var or run `gcloud config set project <id>`.",
|
|
2302
|
+
{ code: "auth_failed" }
|
|
2303
|
+
);
|
|
2304
|
+
}
|
|
2305
|
+
const location = this.options.location ?? resolveVertexLocation();
|
|
2306
|
+
const accessToken = this.options.apiKey !== void 0 && this.options.apiKey.length > 0 ? this.options.apiKey : await resolveVertexAccessToken();
|
|
2307
|
+
if (accessToken === void 0 || accessToken.length === 0) {
|
|
2308
|
+
throw new ConfigurationError(
|
|
2309
|
+
"Vertex could not resolve an access token. Run `gcloud auth application-default login` or set GOOGLE_APPLICATION_CREDENTIALS to a service-account JSON path.",
|
|
2310
|
+
{ code: "auth_failed" }
|
|
2311
|
+
);
|
|
2312
|
+
}
|
|
2313
|
+
const baseUrl = resolveVertexBaseUrl({ projectId, location, modelDialect: "gemini" });
|
|
2314
|
+
const url = `${baseUrl}/chat/completions`;
|
|
2315
|
+
const innerFetch = this.options.fetch ?? fetch;
|
|
2316
|
+
const rewriteFetch = (input, init) => {
|
|
2317
|
+
const requested = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
|
|
2318
|
+
const targetUrl = requested.endsWith("/v1/chat/completions") ? url : requested;
|
|
2319
|
+
return innerFetch(targetUrl, init);
|
|
2320
|
+
};
|
|
2321
|
+
const inner = new OpenAIClient({
|
|
2322
|
+
apiKey: accessToken,
|
|
2323
|
+
baseUrl: "https://_vertex_placeholder",
|
|
2324
|
+
providerName: "vertex",
|
|
2325
|
+
fetch: rewriteFetch
|
|
2326
|
+
});
|
|
2327
|
+
return yield* inner.stream(request, signal);
|
|
2328
|
+
}
|
|
2329
|
+
};
|
|
2330
|
+
|
|
2331
|
+
// src/internal/llm/vertex-router.ts
|
|
2332
|
+
var VertexRouterClient = class {
|
|
2333
|
+
constructor(options) {
|
|
2334
|
+
this.options = options;
|
|
2335
|
+
}
|
|
2336
|
+
options;
|
|
2337
|
+
name = "vertex";
|
|
2338
|
+
async *stream(request, signal) {
|
|
2339
|
+
const dialect = inferModelDialect(request.model);
|
|
2340
|
+
if (dialect === "anthropic") {
|
|
2341
|
+
const inner2 = new VertexAnthropicClient(this.options);
|
|
2342
|
+
return yield* inner2.stream(request, signal);
|
|
2343
|
+
}
|
|
2344
|
+
const inner = new VertexGeminiClient(this.options);
|
|
2345
|
+
return yield* inner.stream(request, signal);
|
|
2346
|
+
}
|
|
2347
|
+
};
|
|
2348
|
+
|
|
2349
|
+
// src/internal/llm/router.ts
|
|
2350
|
+
function resolveProviderChain(options) {
|
|
2351
|
+
registerBuiltins();
|
|
2352
|
+
return buildChain(options);
|
|
2353
|
+
}
|
|
2354
|
+
function buildChain(options) {
|
|
2355
|
+
warnUnknownProvidersInApiKeys(options.apiKeys);
|
|
2356
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2357
|
+
const clients = [];
|
|
2358
|
+
const addClient = (name) => {
|
|
2359
|
+
const lowered = name.toLowerCase();
|
|
2360
|
+
if (seen.has(lowered)) return;
|
|
2361
|
+
seen.add(lowered);
|
|
2362
|
+
const client = buildClient(lowered, options);
|
|
2363
|
+
if (client !== void 0) clients.push(maybeWrapWithFaultInjection(client));
|
|
2364
|
+
};
|
|
2365
|
+
addClient(options.primary);
|
|
2366
|
+
for (const fallback of options.fallback ?? []) addClient(fallback);
|
|
2367
|
+
if (clients.length === 0) {
|
|
2368
|
+
throw new ConfigurationError(
|
|
2369
|
+
`No provider client could be resolved (primary=${options.primary}). Set ANTHROPIC_API_KEY or OPENAI_API_KEY / OPENROUTER_API_KEY.`,
|
|
2370
|
+
{ code: "provider_unresolved" }
|
|
2371
|
+
);
|
|
2372
|
+
}
|
|
2373
|
+
return clients;
|
|
2374
|
+
}
|
|
2375
|
+
function buildClient(name, routerOptions) {
|
|
2376
|
+
const baseProfile = getProviderProfile(name);
|
|
2377
|
+
if (baseProfile === void 0) return void 0;
|
|
2378
|
+
const profile = routerOptions.extractToolCallsFromContent === true && baseProfile.extractToolCallsFromContent !== true ? { ...baseProfile, extractToolCallsFromContent: true } : baseProfile;
|
|
2379
|
+
const resilience = routerOptions.onRateLimit !== void 0 ? { onRateLimit: routerOptions.onRateLimit } : {};
|
|
2380
|
+
const ambient = currentCredentialPool(name);
|
|
2381
|
+
if (ambient !== void 0) {
|
|
2382
|
+
return new PoolAwareLlmClient(
|
|
2383
|
+
ambient,
|
|
2384
|
+
(apiKey) => selectTransport(profile, apiKey),
|
|
2385
|
+
void 0,
|
|
2386
|
+
resilience
|
|
2387
|
+
);
|
|
2388
|
+
}
|
|
2389
|
+
const poolKeys = filterPoolKeys(routerOptions.apiKeys?.[name]);
|
|
2390
|
+
const noAuthOverride = maybeBuildNoAuthTransport(profile, poolKeys);
|
|
2391
|
+
if (noAuthOverride !== void 0) return noAuthOverride;
|
|
2392
|
+
return buildPoolOrSingle({ name, profile, poolKeys, routerOptions });
|
|
2393
|
+
}
|
|
2394
|
+
function maybeBuildNoAuthTransport(profile, poolKeys) {
|
|
2395
|
+
if (profile.authType !== "none" || poolKeys === void 0 || poolKeys.length === 0) {
|
|
2396
|
+
return void 0;
|
|
2397
|
+
}
|
|
2398
|
+
warnNoAuthApiKeysIgnoredOnce(profile.name);
|
|
2399
|
+
const sentinel = sentinelForNoAuth(profile);
|
|
2400
|
+
if (sentinel === void 0) return void 0;
|
|
2401
|
+
return selectTransport(profile, sentinel);
|
|
2402
|
+
}
|
|
2403
|
+
function buildPoolOrSingle(args) {
|
|
2404
|
+
const { name, profile, poolKeys, routerOptions } = args;
|
|
2405
|
+
if (poolKeys !== void 0 && poolKeys.length >= 2) {
|
|
2406
|
+
const strategy = routerOptions.credentialPoolStrategy?.[name] ?? "fill_first";
|
|
2407
|
+
const entries = poolKeys.map(
|
|
2408
|
+
(accessToken, priority) => newPooledCredential({ provider: name, accessToken, priority, source: "manual" })
|
|
2409
|
+
);
|
|
2410
|
+
const pool = new CredentialPool(name, entries, strategy);
|
|
2411
|
+
const resilience = routerOptions.onRateLimit !== void 0 ? { onRateLimit: routerOptions.onRateLimit } : {};
|
|
2412
|
+
return new PoolAwareLlmClient(
|
|
2413
|
+
pool,
|
|
2414
|
+
(apiKey2) => selectTransport(profile, apiKey2),
|
|
2415
|
+
void 0,
|
|
2416
|
+
resilience
|
|
2417
|
+
);
|
|
2418
|
+
}
|
|
2419
|
+
const apiKey = poolKeys?.[0] ?? resolveApiKey(profile.envVars) ?? sentinelForNoAuth(profile) ?? sentinelForLazyAuth(profile);
|
|
2420
|
+
if (apiKey === void 0) return void 0;
|
|
2421
|
+
return selectTransport(profile, apiKey);
|
|
2422
|
+
}
|
|
2423
|
+
function resolveBaseUrlEnvOverride(providerName) {
|
|
2424
|
+
switch (providerName) {
|
|
2425
|
+
case "openai":
|
|
2426
|
+
return process.env.OPENAI_API_BASE_URL;
|
|
2427
|
+
case "openrouter":
|
|
2428
|
+
return process.env.OPENROUTER_API_BASE_URL;
|
|
2429
|
+
case "ollama":
|
|
2430
|
+
return process.env.OLLAMA_HOST;
|
|
2431
|
+
case "lmstudio":
|
|
2432
|
+
return process.env.LMSTUDIO_HOST;
|
|
2433
|
+
case "llamacpp":
|
|
2434
|
+
return process.env.LLAMACPP_HOST;
|
|
2435
|
+
default:
|
|
2436
|
+
return void 0;
|
|
2437
|
+
}
|
|
2438
|
+
}
|
|
2439
|
+
var warnedNoAuthApiKeys = /* @__PURE__ */ new Set();
|
|
2440
|
+
function warnNoAuthApiKeysIgnoredOnce(provider) {
|
|
2441
|
+
if (warnedNoAuthApiKeys.has(provider)) return;
|
|
2442
|
+
warnedNoAuthApiKeys.add(provider);
|
|
2443
|
+
process.stderr.write(
|
|
2444
|
+
`[theokit-sdk] provider "${provider}" has authType: "none" \u2014 apiKeys ignored (no auth required for local runtime).
|
|
2445
|
+
`
|
|
2446
|
+
);
|
|
2447
|
+
}
|
|
2448
|
+
function sentinelForNoAuth(profile) {
|
|
2449
|
+
return profile.authType === "none" ? profile.name : void 0;
|
|
2450
|
+
}
|
|
2451
|
+
function sentinelForLazyAuth(profile) {
|
|
2452
|
+
if (profile.authType === "aws_bearer") return "__bedrock_lazy_token__";
|
|
2453
|
+
if (profile.authType === "gcp_oauth") return "__vertex_lazy_token__";
|
|
2454
|
+
if (profile.authType === "oauth_device_code" || profile.authType === "oauth_external") {
|
|
2455
|
+
return "__oauth_lazy_token__";
|
|
2456
|
+
}
|
|
2457
|
+
return void 0;
|
|
2458
|
+
}
|
|
2459
|
+
function filterPoolKeys(keys) {
|
|
2460
|
+
if (keys === void 0) return void 0;
|
|
2461
|
+
const cleaned = keys.filter((k) => typeof k === "string" && k.length > 0);
|
|
2462
|
+
return cleaned.length === 0 ? void 0 : cleaned;
|
|
2463
|
+
}
|
|
2464
|
+
var warnedProviders = /* @__PURE__ */ new Set();
|
|
2465
|
+
function warnUnknownProvidersInApiKeys(apiKeys) {
|
|
2466
|
+
if (apiKeys === void 0) return;
|
|
2467
|
+
for (const name of Object.keys(apiKeys)) {
|
|
2468
|
+
if (warnedProviders.has(name)) continue;
|
|
2469
|
+
const profile = getProviderProfile(name);
|
|
2470
|
+
if (profile === void 0) {
|
|
2471
|
+
warnedProviders.add(name);
|
|
2472
|
+
process.stderr.write(
|
|
2473
|
+
`[theokit-sdk] credential-pool: unknown provider "${name}" in apiKeys (no profile registered)
|
|
2474
|
+
`
|
|
2475
|
+
);
|
|
2476
|
+
}
|
|
2477
|
+
}
|
|
2478
|
+
}
|
|
2479
|
+
function resolveApiKey(envVars) {
|
|
2480
|
+
for (const v of envVars) {
|
|
2481
|
+
const value = process.env[v];
|
|
2482
|
+
if (value !== void 0 && value.length > 0) return value;
|
|
2483
|
+
}
|
|
2484
|
+
return void 0;
|
|
2485
|
+
}
|
|
2486
|
+
function selectTransport(profile, apiKey) {
|
|
2487
|
+
const applyTransform = () => {
|
|
2488
|
+
if (profile.transform === void 0) return {};
|
|
2489
|
+
const ctx = { apiKey };
|
|
2490
|
+
return { fetch: profile.transform.fetch?.(ctx), headers: profile.transform.headers?.(ctx) };
|
|
2491
|
+
};
|
|
2492
|
+
const comTransform = (opts, criar) => {
|
|
2493
|
+
const t = applyTransform();
|
|
2494
|
+
assertOAuthResolved(t);
|
|
2495
|
+
if (t.fetch !== void 0) opts.fetch = t.fetch;
|
|
2496
|
+
const merged = profile.extraHeaders !== void 0 || t.headers !== void 0 ? { ...profile.extraHeaders, ...t.headers } : void 0;
|
|
2497
|
+
if (merged !== void 0) opts.extraHeaders = merged;
|
|
2498
|
+
return criar(opts);
|
|
2499
|
+
};
|
|
2500
|
+
const assertOAuthResolved = (t) => {
|
|
2501
|
+
if (apiKey !== "__oauth_lazy_token__") return;
|
|
2502
|
+
const auth = t.headers?.authorization ?? t.headers?.Authorization;
|
|
2503
|
+
if (t.fetch === void 0 && auth === void 0) {
|
|
2504
|
+
throw new ConfigurationError(
|
|
2505
|
+
`provider "${profile.name}" uses OAuth (authType: ${profile.authType}) but no credential was resolved. Register a ProviderProfile.transform whose fetch (or headers.authorization) supplies the bearer \u2014 typically via resolveCredential() from "@theokit/sdk/auth".`
|
|
2506
|
+
);
|
|
2507
|
+
}
|
|
2508
|
+
};
|
|
2509
|
+
if (profile.apiMode === "chat_completions") {
|
|
2510
|
+
if (profile.name === "ollama") {
|
|
2511
|
+
const ollamaBase = process.env.OLLAMA_HOST ?? profile.baseUrl;
|
|
2512
|
+
return new OllamaNativeClient({ apiKey, baseUrl: ollamaBase });
|
|
2513
|
+
}
|
|
2514
|
+
const opts = { apiKey };
|
|
2515
|
+
opts.baseUrl = profile.baseUrl;
|
|
2516
|
+
opts.providerName = profile.name;
|
|
2517
|
+
if (profile.extractToolCallsFromContent === true) {
|
|
2518
|
+
opts.extractToolCallsFromContent = true;
|
|
2519
|
+
}
|
|
2520
|
+
if (profile.chatCompletionsPath !== void 0) {
|
|
2521
|
+
opts.chatCompletionsPath = profile.chatCompletionsPath;
|
|
2522
|
+
}
|
|
2523
|
+
if (profile.name === "openai" && process.env.OPENAI_ORGANIZATION !== void 0) {
|
|
2524
|
+
opts.organization = process.env.OPENAI_ORGANIZATION;
|
|
2525
|
+
}
|
|
2526
|
+
const envOverride = resolveBaseUrlEnvOverride(profile.name);
|
|
2527
|
+
if (envOverride !== void 0) opts.baseUrl = envOverride;
|
|
2528
|
+
return comTransform(opts, (o) => new OpenAIClient(o));
|
|
2529
|
+
}
|
|
2530
|
+
if (profile.apiMode === "anthropic_messages") {
|
|
2531
|
+
if (profile.name === "vertex") {
|
|
2532
|
+
const realKey = apiKey === "__vertex_lazy_token__" ? void 0 : apiKey;
|
|
2533
|
+
return new VertexRouterClient(realKey !== void 0 ? { apiKey: realKey } : {});
|
|
2534
|
+
}
|
|
2535
|
+
const opts = { apiKey };
|
|
2536
|
+
opts.baseUrl = process.env.ANTHROPIC_API_BASE_URL ?? profile.baseUrl;
|
|
2537
|
+
return comTransform(opts, (o) => new AnthropicClient(o));
|
|
2538
|
+
}
|
|
2539
|
+
if (profile.apiMode === "bedrock_anthropic") {
|
|
2540
|
+
const realKey = apiKey === "__bedrock_lazy_token__" ? void 0 : apiKey;
|
|
2541
|
+
return new BedrockAnthropicClient(realKey !== void 0 ? { apiKey: realKey } : {});
|
|
2542
|
+
}
|
|
2543
|
+
if (profile.apiMode === "responses_api") {
|
|
2544
|
+
const t = applyTransform();
|
|
2545
|
+
assertOAuthResolved(t);
|
|
2546
|
+
const mergedHeaders = profile.extraHeaders !== void 0 || t.headers !== void 0 ? { ...profile.extraHeaders, ...t.headers } : void 0;
|
|
2547
|
+
return new ResponsesApiClient({
|
|
2548
|
+
apiKey,
|
|
2549
|
+
...profile.baseUrl !== void 0 ? { baseUrl: profile.baseUrl } : {},
|
|
2550
|
+
...mergedHeaders !== void 0 ? { extraHeaders: mergedHeaders } : {},
|
|
2551
|
+
...t.fetch !== void 0 ? { fetch: t.fetch } : {},
|
|
2552
|
+
providerName: profile.name
|
|
2553
|
+
});
|
|
2554
|
+
}
|
|
2555
|
+
throw new ConfigurationError(
|
|
2556
|
+
`Provider "${profile.name}" requires apiMode "${profile.apiMode}" but no transport is registered. Install a third-party transport plugin (@theokit-transport-${profile.apiMode}) or use a provider with apiMode "chat_completions" or "anthropic_messages".`,
|
|
2557
|
+
{ code: "transport_unavailable" }
|
|
2558
|
+
);
|
|
2559
|
+
}
|
|
2560
|
+
|
|
2561
|
+
// src/internal/local-agent/real-local-run-provider.ts
|
|
2562
|
+
function inferProviderFromApiKey(apiKey) {
|
|
2563
|
+
if (apiKey === void 0 || apiKey.length === 0) return void 0;
|
|
2564
|
+
const byPrefix = [
|
|
2565
|
+
{ provider: "openrouter", prefix: "sk-or-" },
|
|
2566
|
+
{ provider: "anthropic", prefix: "sk-ant-" },
|
|
2567
|
+
{ provider: "openai", prefix: "sk-" }
|
|
2568
|
+
];
|
|
2569
|
+
for (const { provider, prefix } of byPrefix) {
|
|
2570
|
+
if (apiKey.startsWith(prefix) && getProviderProfile(provider) !== void 0) {
|
|
2571
|
+
return provider;
|
|
2572
|
+
}
|
|
2573
|
+
}
|
|
2574
|
+
return void 0;
|
|
2575
|
+
}
|
|
2576
|
+
function detectPrimaryProvider() {
|
|
2577
|
+
if (process.env.ANTHROPIC_API_KEY !== void 0 && process.env.ANTHROPIC_API_KEY.length > 0) {
|
|
2578
|
+
return "anthropic";
|
|
2579
|
+
}
|
|
2580
|
+
if (process.env.OPENAI_API_KEY !== void 0 && process.env.OPENAI_API_KEY.length > 0) {
|
|
2581
|
+
return "openai";
|
|
2582
|
+
}
|
|
2583
|
+
if (process.env.OPENROUTER_API_KEY !== void 0 && process.env.OPENROUTER_API_KEY.length > 0) {
|
|
2584
|
+
return "openrouter";
|
|
2585
|
+
}
|
|
2586
|
+
return "openai";
|
|
2587
|
+
}
|
|
2588
|
+
|
|
2589
|
+
// src/internal/runtime/compression/compression-model-registry.ts
|
|
2590
|
+
var EXACT_REGISTRY = /* @__PURE__ */ new Map([
|
|
2591
|
+
// OpenAI family
|
|
2592
|
+
["openai/gpt-4o", "openai/gpt-4o-mini"],
|
|
2593
|
+
["openai/gpt-4-turbo", "openai/gpt-4o-mini"],
|
|
2594
|
+
["openai/gpt-4", "openai/gpt-4o-mini"],
|
|
2595
|
+
["openai/o1-preview", "openai/gpt-4o-mini"],
|
|
2596
|
+
["openai/o1", "openai/gpt-4o-mini"],
|
|
2597
|
+
["openai/o3", "openai/gpt-4o-mini"],
|
|
2598
|
+
["openai/o3-mini", "openai/gpt-4o-mini"],
|
|
2599
|
+
// Anthropic family
|
|
2600
|
+
["anthropic/claude-opus-4", "anthropic/claude-3-5-haiku-latest"],
|
|
2601
|
+
["anthropic/claude-sonnet-4", "anthropic/claude-3-5-haiku-latest"],
|
|
2602
|
+
["anthropic/claude-3-5-sonnet", "anthropic/claude-3-5-haiku-latest"],
|
|
2603
|
+
["anthropic/claude-3-5-sonnet-latest", "anthropic/claude-3-5-haiku-latest"],
|
|
2604
|
+
["anthropic/claude-3-opus", "anthropic/claude-3-haiku"],
|
|
2605
|
+
["anthropic/claude-3-sonnet", "anthropic/claude-3-haiku"],
|
|
2606
|
+
// Vertex (Gemini + Anthropic-on-Vertex)
|
|
2607
|
+
["vertex/gemini-1.5-pro", "vertex/gemini-1.5-flash"],
|
|
2608
|
+
["vertex/gemini-2.0-pro", "vertex/gemini-1.5-flash"],
|
|
2609
|
+
["vertex/claude-3-5-sonnet", "vertex/claude-3-5-haiku"],
|
|
2610
|
+
["vertex/claude-3-opus", "vertex/claude-3-haiku"],
|
|
2611
|
+
// OpenRouter (preserve openrouter prefix, swap tier within same vendor)
|
|
2612
|
+
["openrouter/openai/gpt-4o", "openrouter/openai/gpt-4o-mini"],
|
|
2613
|
+
["openrouter/openai/gpt-4-turbo", "openrouter/openai/gpt-4o-mini"],
|
|
2614
|
+
["openrouter/anthropic/claude-3-5-sonnet", "openrouter/anthropic/claude-3-5-haiku"],
|
|
2615
|
+
["openrouter/anthropic/claude-opus-4", "openrouter/anthropic/claude-3-5-haiku"]
|
|
2616
|
+
]);
|
|
2617
|
+
var WILDCARD_REGISTRY = [
|
|
2618
|
+
// Bedrock Anthropic: us.anthropic.claude-sonnet-* → us.anthropic.claude-3-haiku-*
|
|
2619
|
+
["bedrock/anthropic.claude-sonnet*", "bedrock/anthropic.claude-3-haiku*"],
|
|
2620
|
+
["bedrock/anthropic.claude-opus*", "bedrock/anthropic.claude-3-haiku*"],
|
|
2621
|
+
["bedrock/anthropic.claude-3-5-sonnet*", "bedrock/anthropic.claude-3-5-haiku*"]
|
|
2622
|
+
];
|
|
2623
|
+
var NO_AUTH_PROVIDERS = /* @__PURE__ */ new Set(["ollama", "lmstudio", "llamacpp"]);
|
|
2624
|
+
var CompressionModelUnresolvedError = class extends Error {
|
|
2625
|
+
name = "CompressionModelUnresolvedError";
|
|
2626
|
+
agentModel;
|
|
2627
|
+
constructor(agentModel) {
|
|
2628
|
+
super(
|
|
2629
|
+
`Could not resolve a same-family-cheaper-tier compression model for "${agentModel}". Provide Agent.create({compression: {model: "<your-cheaper-model>"}}) OR add "${agentModel}" to the compression-model-registry (see ADR D440).`
|
|
2630
|
+
);
|
|
2631
|
+
this.agentModel = agentModel;
|
|
2632
|
+
}
|
|
2633
|
+
};
|
|
2634
|
+
function resolveCompressionModel(agentModel) {
|
|
2635
|
+
const exact = EXACT_REGISTRY.get(agentModel);
|
|
2636
|
+
if (exact !== void 0) return exact;
|
|
2637
|
+
const wildcard = matchWildcard(agentModel);
|
|
2638
|
+
if (wildcard !== null) return wildcard;
|
|
2639
|
+
if (isNoAuthProvider(agentModel)) return agentModel;
|
|
2640
|
+
throw new CompressionModelUnresolvedError(agentModel);
|
|
2641
|
+
}
|
|
2642
|
+
function matchWildcard(agentModel) {
|
|
2643
|
+
for (const [pattern, replacement] of WILDCARD_REGISTRY) {
|
|
2644
|
+
const prefix = pattern.endsWith("*") ? pattern.slice(0, -1) : pattern;
|
|
2645
|
+
if (!agentModel.startsWith(prefix)) continue;
|
|
2646
|
+
const suffix = agentModel.slice(prefix.length);
|
|
2647
|
+
const replPrefix = replacement.endsWith("*") ? replacement.slice(0, -1) : replacement;
|
|
2648
|
+
return `${replPrefix}${suffix}`;
|
|
2649
|
+
}
|
|
2650
|
+
return null;
|
|
2651
|
+
}
|
|
2652
|
+
function isNoAuthProvider(agentModel) {
|
|
2653
|
+
const slashIdx = agentModel.indexOf("/");
|
|
2654
|
+
if (slashIdx <= 0) return false;
|
|
2655
|
+
return NO_AUTH_PROVIDERS.has(agentModel.slice(0, slashIdx));
|
|
2656
|
+
}
|
|
2657
|
+
|
|
2658
|
+
// src/internal/runtime/compression/compression-summarizer.ts
|
|
2659
|
+
var CompressionFailedError = class extends Error {
|
|
2660
|
+
name = "CompressionFailedError";
|
|
2661
|
+
};
|
|
2662
|
+
function buildCompressionPrompt(messages) {
|
|
2663
|
+
const formatted = messages.map((m) => `[${m.role}]: ${m.content}`).join("\n\n");
|
|
2664
|
+
return `Summarize the following ${messages.length} conversation messages into a concise summary that preserves ALL facts, decisions, user preferences, and context needed for the conversation to continue naturally. The summary will replace these messages in the context window. Be thorough but concise.
|
|
2665
|
+
|
|
2666
|
+
--- CONVERSATION TO SUMMARIZE ---
|
|
2667
|
+
${formatted}
|
|
2668
|
+
--- END ---`;
|
|
2669
|
+
}
|
|
2670
|
+
var COMPRESSION_SYSTEM = "You are a conversation summarizer. Produce a concise factual summary. Preserve all decisions, preferences, code snippets, and action items. Do not add commentary or opinions. Output ONLY the summary text.";
|
|
2671
|
+
async function compressConversationWindow(opts) {
|
|
2672
|
+
const userPrompt = buildCompressionPrompt(opts.messages);
|
|
2673
|
+
let summary;
|
|
2674
|
+
try {
|
|
2675
|
+
summary = await opts.callLlm(opts.model, COMPRESSION_SYSTEM, userPrompt);
|
|
2676
|
+
} catch (cause) {
|
|
2677
|
+
throw new CompressionFailedError(
|
|
2678
|
+
`Compression LLM call failed: ${cause instanceof Error ? cause.message : String(cause)}`,
|
|
2679
|
+
{ cause: cause instanceof Error ? cause : void 0 }
|
|
2680
|
+
);
|
|
2681
|
+
}
|
|
2682
|
+
if (!summary || summary.trim().length === 0) {
|
|
2683
|
+
throw new CompressionFailedError(
|
|
2684
|
+
"Compression LLM returned empty summary \u2014 reduction ineffective."
|
|
2685
|
+
);
|
|
2686
|
+
}
|
|
2687
|
+
return {
|
|
2688
|
+
role: "system",
|
|
2689
|
+
content: `[Compressed conversation summary]: ${summary.trim()}`
|
|
2690
|
+
};
|
|
2691
|
+
}
|
|
2692
|
+
|
|
2693
|
+
// src/internal/session/compact-session.ts
|
|
2694
|
+
var COMPACT_SUMMARY_MARKER = "[[theokit:compact-summary]]";
|
|
2695
|
+
var COMPACT_USER_MESSAGE_MAX_TOKENS = 2e4;
|
|
2696
|
+
function isCompactSummary(content) {
|
|
2697
|
+
return content.startsWith(COMPACT_SUMMARY_MARKER) || content.startsWith("[[theokit:goal-continuation]]");
|
|
2698
|
+
}
|
|
2699
|
+
function plainText(content) {
|
|
2700
|
+
if (typeof content === "string") return content;
|
|
2701
|
+
if (!Array.isArray(content)) return void 0;
|
|
2702
|
+
const texts = content.filter(
|
|
2703
|
+
(p) => p !== null && typeof p === "object" && p.type === "text" && typeof p.text === "string"
|
|
2704
|
+
).map((p) => p.text);
|
|
2705
|
+
return texts.length > 0 ? texts.join("\n") : void 0;
|
|
2706
|
+
}
|
|
2707
|
+
async function compactSessionTranscript(opts) {
|
|
2708
|
+
const prior = await opts.store.readRecords(opts.loc.agentId);
|
|
2709
|
+
const history = reconstructMessages(prior);
|
|
2710
|
+
const compressible = [];
|
|
2711
|
+
for (const m of history) {
|
|
2712
|
+
const text = plainText(m.content);
|
|
2713
|
+
if (text !== void 0 && (m.role === "user" || m.role === "assistant" || m.role === "system")) {
|
|
2714
|
+
compressible.push({ role: m.role, content: text });
|
|
2715
|
+
}
|
|
2716
|
+
}
|
|
2717
|
+
const preTokens = estimateTokens(compressible.map((m) => m.content).join("\n"));
|
|
2718
|
+
const summaryBody = await opts.summarize(compressible);
|
|
2719
|
+
const preserved = [];
|
|
2720
|
+
let budget = 0;
|
|
2721
|
+
for (let i = compressible.length - 1; i >= 0; i--) {
|
|
2722
|
+
const m = compressible[i];
|
|
2723
|
+
if (m === void 0 || m.role !== "user" || isCompactSummary(m.content)) continue;
|
|
2724
|
+
const cost = estimateTokens(m.content);
|
|
2725
|
+
if (budget + cost > COMPACT_USER_MESSAGE_MAX_TOKENS) {
|
|
2726
|
+
const remaining = COMPACT_USER_MESSAGE_MAX_TOKENS - budget;
|
|
2727
|
+
if (remaining > 50) {
|
|
2728
|
+
preserved.unshift(`${m.content.slice(0, remaining * 4)}
|
|
2729
|
+
[...truncated for compaction...]`);
|
|
2730
|
+
}
|
|
2731
|
+
break;
|
|
2732
|
+
}
|
|
2733
|
+
budget += cost;
|
|
2734
|
+
preserved.unshift(m.content);
|
|
2735
|
+
}
|
|
2736
|
+
const summary = `${COMPACT_SUMMARY_MARKER}
|
|
2737
|
+
${summaryBody}`;
|
|
2738
|
+
const transcript = SessionTranscript.fromRecords(prior, {
|
|
2739
|
+
cwd: opts.loc.cwd,
|
|
2740
|
+
sessionId: opts.sessionId,
|
|
2741
|
+
model: opts.loc.model ?? "unknown"
|
|
2742
|
+
});
|
|
2743
|
+
transcript.appendCompactBoundary({ preTokens, trigger: opts.trigger });
|
|
2744
|
+
for (const text of preserved) transcript.appendUserTurn(text);
|
|
2745
|
+
transcript.appendUserTurn(summary);
|
|
2746
|
+
const delta = transcript.records().slice(prior.length);
|
|
2747
|
+
await opts.store.appendRecords(opts.loc.agentId, delta);
|
|
2748
|
+
invalidateSessionCache(opts.loc.cwd, opts.loc.agentId);
|
|
2749
|
+
const postTokens = estimateTokens([...preserved, summary].join("\n"));
|
|
2750
|
+
return { preTokens, postTokens };
|
|
2751
|
+
}
|
|
2752
|
+
function resolveSummarizerRoute(opts) {
|
|
2753
|
+
const provider = opts.keyProvider ?? (opts.prefixHasProfile && opts.modelPrefix !== void 0 ? opts.modelPrefix : opts.envProvider);
|
|
2754
|
+
return { provider, fullSlug: provider !== opts.modelPrefix };
|
|
2755
|
+
}
|
|
2756
|
+
function buildDefaultSummarizer(opts) {
|
|
2757
|
+
return async (messages) => {
|
|
2758
|
+
registerBuiltins();
|
|
2759
|
+
const modelPrefix = opts.agentModel.includes("/") ? opts.agentModel.slice(0, opts.agentModel.indexOf("/")) : void 0;
|
|
2760
|
+
const prefixProfile = modelPrefix !== void 0 ? getProviderProfile(modelPrefix) : void 0;
|
|
2761
|
+
const prefixUsable = prefixProfile !== void 0 && (prefixProfile.authType !== "api_key" || prefixProfile.envVars.some((v) => (process.env[v] ?? "").length > 0));
|
|
2762
|
+
const route = resolveSummarizerRoute({
|
|
2763
|
+
keyProvider: inferProviderFromApiKey(opts.apiKey),
|
|
2764
|
+
modelPrefix,
|
|
2765
|
+
prefixHasProfile: prefixUsable,
|
|
2766
|
+
envProvider: detectPrimaryProvider()
|
|
2767
|
+
});
|
|
2768
|
+
const provider = route.provider;
|
|
2769
|
+
let model;
|
|
2770
|
+
if (route.fullSlug) {
|
|
2771
|
+
model = opts.agentModel;
|
|
2772
|
+
} else {
|
|
2773
|
+
try {
|
|
2774
|
+
model = resolveCompressionModel(opts.agentModel);
|
|
2775
|
+
} catch {
|
|
2776
|
+
model = opts.agentModel;
|
|
2777
|
+
}
|
|
2778
|
+
}
|
|
2779
|
+
const callLlm = async (m, system, user) => {
|
|
2780
|
+
const chain = resolveProviderChain({
|
|
2781
|
+
primary: provider,
|
|
2782
|
+
...opts.apiKey !== void 0 ? { apiKeys: { [provider]: [opts.apiKey] } } : {}
|
|
2783
|
+
});
|
|
2784
|
+
const client = chain[0];
|
|
2785
|
+
if (client === void 0) throw new Error(`no LLM client for provider "${provider}"`);
|
|
2786
|
+
const controller = new AbortController();
|
|
2787
|
+
let text = "";
|
|
2788
|
+
const stream = client.stream(
|
|
2789
|
+
{ model: m, system, messages: [{ role: "user", content: [{ type: "text", text: user }] }] },
|
|
2790
|
+
controller.signal
|
|
2791
|
+
);
|
|
2792
|
+
for (; ; ) {
|
|
2793
|
+
const step = await stream.next();
|
|
2794
|
+
if (step.done === true) break;
|
|
2795
|
+
if (step.value.type === "text_delta") text += step.value.text;
|
|
2796
|
+
if (step.value.type === "error") throw new Error(step.value.message);
|
|
2797
|
+
}
|
|
2798
|
+
return text;
|
|
2799
|
+
};
|
|
2800
|
+
const summary = await compressConversationWindow({ messages: [...messages], model, callLlm });
|
|
2801
|
+
return summary.content;
|
|
2802
|
+
};
|
|
2803
|
+
}
|
|
2804
|
+
function shouldAutoCompact(opts) {
|
|
2805
|
+
if (opts.usageTotal === void 0 || opts.contextWindow === void 0) return false;
|
|
2806
|
+
if (opts.usageTotal <= 0 || opts.contextWindow <= 0) return false;
|
|
2807
|
+
return opts.usageTotal >= Math.floor(opts.contextWindow * 9 / 10);
|
|
2808
|
+
}
|
|
2809
|
+
var autoCompactAttempts = (() => {
|
|
2810
|
+
const g = globalThis;
|
|
2811
|
+
const sym = /* @__PURE__ */ Symbol.for("theokit-sdk.session.auto-compact-attempts");
|
|
2812
|
+
if (g[sym] === void 0) g[sym] = /* @__PURE__ */ new Map();
|
|
2813
|
+
return g[sym];
|
|
2814
|
+
})();
|
|
2815
|
+
async function autoCompactIfNeeded(opts) {
|
|
2816
|
+
if (!shouldAutoCompact(opts)) return false;
|
|
2817
|
+
const key = `${opts.loc.cwd}::${opts.loc.agentId}`;
|
|
2818
|
+
if (autoCompactAttempts.get(key) === opts.turnCount) return false;
|
|
2819
|
+
autoCompactAttempts.set(key, opts.turnCount);
|
|
2820
|
+
try {
|
|
2821
|
+
await compactSessionTranscript({
|
|
2822
|
+
store: opts.store,
|
|
2823
|
+
loc: opts.loc,
|
|
2824
|
+
sessionId: opts.sessionId,
|
|
2825
|
+
trigger: "auto",
|
|
2826
|
+
summarize: opts.summarize
|
|
2827
|
+
});
|
|
2828
|
+
return true;
|
|
2829
|
+
} catch (cause) {
|
|
2830
|
+
const msg = cause instanceof Error ? cause.message : String(cause);
|
|
2831
|
+
process.stderr.write(
|
|
2832
|
+
`[theokit-sdk] auto-compaction failed (left transcript untouched): ${msg}
|
|
2833
|
+
`
|
|
2834
|
+
);
|
|
2835
|
+
return false;
|
|
2836
|
+
}
|
|
2837
|
+
}
|
|
2838
|
+
|
|
2839
|
+
export { COMPACT_SUMMARY_MARKER, COMPACT_USER_MESSAGE_MAX_TOKENS, CircuitBreaker, autoCompactIfNeeded, buildDefaultSummarizer, compactSessionTranscript, detectPrimaryProvider, discoverProviderPlugins, inferProviderFromApiKey, isCompactSummary, mapOllamaHttpError, mapOllamaTransportError, mapOpenAICompatibleError, parseSseStream, relayStream, renderToolResultContentText, resolveProviderChain, resolveSummarizerRoute, shouldAutoCompact, tryFirstEvent };
|
|
2840
|
+
//# sourceMappingURL=chunk-NRSH7RJY.js.map
|
|
2841
|
+
//# sourceMappingURL=chunk-NRSH7RJY.js.map
|