@xopcai/xopc 0.0.371 → 0.0.373

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (206) hide show
  1. package/dist/browser-ext/manifest.json +1 -1
  2. package/dist/extensions/telegram/xopc.extension.json +1 -1
  3. package/dist/gateway/static/root/assets/{activity-store-BQ3PMp2q.js → activity-store-BeYLqqr8.js} +1 -1
  4. package/dist/gateway/static/root/assets/{agent-avatar-dicebear-C8MqXy6W.js → agent-avatar-dicebear--kDkSTqC.js} +1 -1
  5. package/dist/gateway/static/root/assets/agent-defaults-settings-panel-z4_2-Kn2.js +1 -0
  6. package/dist/gateway/static/root/assets/{agent-plugin-dialog-BrysdA6F.js → agent-plugin-dialog-BYndK83K.js} +1 -1
  7. package/dist/gateway/static/root/assets/{agents-BJRyMNx2.js → agents-CVo96cx1.js} +3 -3
  8. package/dist/gateway/static/root/assets/{agents-admin-api-HBZvVyzF.js → agents-admin-api-B4tgTbWG.js} +1 -1
  9. package/dist/gateway/static/root/assets/{app-management-settings-panel-Bi02swpZ.js → app-management-settings-panel-D9oOJKiH.js} +1 -1
  10. package/dist/gateway/static/root/assets/{appearance-settings-rr0qirDm.js → appearance-settings-B0-8Cdyv.js} +1 -1
  11. package/dist/gateway/static/root/assets/{apps-page-Sz3wl3lV.js → apps-page-DasVc6i4.js} +1 -1
  12. package/dist/gateway/static/root/assets/{archive-plugin-BO3I9zRP.js → archive-plugin-bI9OLnXj.js} +1 -1
  13. package/dist/gateway/static/root/assets/{automations-page-DqdbP0hD.js → automations-page-BlCNqy2i.js} +1 -1
  14. package/dist/gateway/static/root/assets/{automations-workspace-CxgKlA5d.js → automations-workspace-C8ipDP05.js} +1 -1
  15. package/dist/gateway/static/root/assets/{binary-plugins-gFD7vH9R.js → binary-plugins-dOwLzjeH.js} +1 -1
  16. package/dist/gateway/static/root/assets/{block-editor-CfVR1zGa.js → block-editor-Cktlez6g.js} +1 -1
  17. package/dist/gateway/static/root/assets/{browser-automation-inputs-DxRoKFun.js → browser-automation-inputs-i7h1Svpf.js} +1 -1
  18. package/dist/gateway/static/root/assets/{browser-automations-page-DeYnh3mY.js → browser-automations-page-KRfDl8ej.js} +1 -1
  19. package/dist/gateway/static/root/assets/{browser-settings-page-DDZvlGCD.js → browser-settings-page-BraQ4wnh.js} +1 -1
  20. package/dist/gateway/static/root/assets/{capabilities-CY4SiHcP.js → capabilities-CquUV0DP.js} +1 -1
  21. package/dist/gateway/static/root/assets/{capabilities-gjQtMhKu.js → capabilities-D4KFLtqM.js} +1 -1
  22. package/dist/gateway/static/root/assets/{capabilities-page-Bl6PZqx3.js → capabilities-page-B3m8xGzd.js} +2 -2
  23. package/dist/gateway/static/root/assets/{capabilities-settings-panel-B-MM4LJA.js → capabilities-settings-panel-VQ4fHuBV.js} +6 -6
  24. package/dist/gateway/static/root/assets/{capability-header-actions-CAiY7HK1.js → capability-header-actions-f5Ido4Eg.js} +1 -1
  25. package/dist/gateway/static/root/assets/{channels-D1_fDkhu.js → channels-N4Vc2nEG.js} +1 -1
  26. package/dist/gateway/static/root/assets/{chat-terminal-dock-D6slpF2a.js → chat-terminal-dock-BwvTKwsn.js} +1 -1
  27. package/dist/gateway/static/root/assets/cloud-credits-BMYMiiNn.js +1 -0
  28. package/dist/gateway/static/root/assets/{computer-settings-page-D1n_uQHN.js → computer-settings-page-CAwUsKpw.js} +1 -1
  29. package/dist/gateway/static/root/assets/{connector-service-page-FvSCyJzn.js → connector-service-page-C0HuAMuH.js} +1 -1
  30. package/dist/gateway/static/root/assets/{connectors-page-DroD-gQn.js → connectors-page-BcBmW8JT.js} +1 -1
  31. package/dist/gateway/static/root/assets/{date-picker-Bl-HqvGU.js → date-picker-DiQxRpe-.js} +1 -1
  32. package/dist/gateway/static/root/assets/{dependency-picker-DY4nU-HL.js → dependency-picker-Ds0_BJbN.js} +1 -1
  33. package/dist/gateway/static/root/assets/{desktop-pet-_A89B2xw.js → desktop-pet-ZMEOYwrR.js} +1 -1
  34. package/dist/gateway/static/root/assets/{desktop-pet-settings-BJWkGynu.js → desktop-pet-settings-DkyTqD07.js} +1 -1
  35. package/dist/gateway/static/root/assets/{device-pairing-wizard-DJ_ITvkI.js → device-pairing-wizard-CvaI5ZJZ.js} +1 -1
  36. package/dist/gateway/static/root/assets/{directory-picker-path-field-Bj2Cpmxs.js → directory-picker-path-field-Bfv93eK1.js} +1 -1
  37. package/dist/gateway/static/root/assets/{extension-debug-page-BfAtjhoK.js → extension-debug-page-CbgEonkK.js} +1 -1
  38. package/dist/gateway/static/root/assets/{extension-page-BFjc1zDp.js → extension-page-JXKGtIdM.js} +1 -1
  39. package/dist/gateway/static/root/assets/{extension-provider-Br6LXyIj.js → extension-provider-DxlAZ2iy.js} +1 -1
  40. package/dist/gateway/static/root/assets/{extension-settings-page-oAwllsMb.js → extension-settings-page-lsQ2xvbr.js} +1 -1
  41. package/dist/gateway/static/root/assets/{fallback-models-KHd6tw_t.js → fallback-models-7bG0AND1.js} +1 -1
  42. package/dist/gateway/static/root/assets/{gateway-config-swr-7RKqgU_U.js → gateway-config-swr-BoRkBnsw.js} +1 -1
  43. package/dist/gateway/static/root/assets/gateway-realtime-C3WQQY10.js +1 -0
  44. package/dist/gateway/static/root/assets/gateway-realtime-store-cLCDEe4D.js +1 -0
  45. package/dist/gateway/static/root/assets/{gateway-settings-BIyhyiOq.js → gateway-settings-BpbcoPx9.js} +1 -1
  46. package/dist/gateway/static/root/assets/{gateway-startup-retry-jiV5sbEX.js → gateway-startup-retry-TYSQEQcG.js} +1 -1
  47. package/dist/gateway/static/root/assets/{home-page-CEjaIUTF.js → home-page-BxctC0aJ.js} +1 -1
  48. package/dist/gateway/static/root/assets/{image-generation-api-DO4mXb6I.js → image-generation-api-_pKiNfSq.js} +1 -1
  49. package/dist/gateway/static/root/assets/{imports-page-ClFqRiGQ.js → imports-page-Bq8CazCb.js} +1 -1
  50. package/dist/gateway/static/root/assets/{index-GBFmI8im.js → index-B40-KDN8.js} +7 -7
  51. package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-Ca9_GJSQ.js → keyboard-shortcuts-settings-kkAevkl2.js} +1 -1
  52. package/dist/gateway/static/root/assets/{local-app-workbench-page-BaK9zX1-.js → local-app-workbench-page-abLaf1hW.js} +1 -1
  53. package/dist/gateway/static/root/assets/{local-apps-page-1CjSftZ3.js → local-apps-page-nwsnwYjK.js} +1 -1
  54. package/dist/gateway/static/root/assets/{local-session-drafts-pox8evDV.js → local-session-drafts-Cg9RdsNN.js} +1 -1
  55. package/dist/gateway/static/root/assets/{locale-store-COVscAqC.js → locale-store-rWFLl-p4.js} +1 -1
  56. package/dist/gateway/static/root/assets/{logs-page-6joZBbcx.js → logs-page-BEgYS-Xg.js} +1 -1
  57. package/dist/gateway/static/root/assets/{management-settings-Cnubkklb.js → management-settings-D_JQ6_X0.js} +1 -1
  58. package/dist/gateway/static/root/assets/{markdown-view-B_qnAcW2.js → markdown-view-BHFFusQs.js} +1 -1
  59. package/dist/gateway/static/root/assets/{media-plugins-C0Pc-wSY.js → media-plugins-BVPTMWJI.js} +1 -1
  60. package/dist/gateway/static/root/assets/messages-C1SUo4gG.js +3 -0
  61. package/dist/gateway/static/root/assets/models-hub-cache-Jj8dvYcv.js +1 -0
  62. package/dist/gateway/static/root/assets/{note-detail-panel-B6DHbMzT.js → note-detail-panel-CCnfurBR.js} +3 -3
  63. package/dist/gateway/static/root/assets/{notes-api-sBgBcyLW.js → notes-api-CSLVCZBn.js} +1 -1
  64. package/dist/gateway/static/root/assets/{notes-page-CGZkIKdi.js → notes-page-D9u_znxM.js} +1 -1
  65. package/dist/gateway/static/root/assets/{page-context-capture-button-y3QmhTu2.js → page-context-capture-button-BO3FoRej.js} +1 -1
  66. package/dist/gateway/static/root/assets/{personal-page-C_roXCJB.js → personal-page-BJn6UHAF.js} +1 -1
  67. package/dist/gateway/static/root/assets/{product-open-page-CSRYWt0f.js → product-open-page-Dso2_hyl.js} +1 -1
  68. package/dist/gateway/static/root/assets/{product-preview-content-DuOML6gs.js → product-preview-content-CkvVb3EA.js} +1 -1
  69. package/dist/gateway/static/root/assets/{project-detail-page-v7xtnvha.js → project-detail-page-UjrrzE3H.js} +1 -1
  70. package/dist/gateway/static/root/assets/{projects-page-BuJCBT6n.js → projects-page-C1h63P2M.js} +1 -1
  71. package/dist/gateway/static/root/assets/{registry-api-Bh9UBb-f.js → registry-api-CoMBYjHD.js} +1 -1
  72. package/dist/gateway/static/root/assets/{remote-access-hub-DpHnyBnG.js → remote-access-hub-BJifH8AB.js} +1 -1
  73. package/dist/gateway/static/root/assets/{runtime-tools-settings-panel-DTMTL0hy.js → runtime-tools-settings-panel-vG1wqcEb.js} +1 -1
  74. package/dist/gateway/static/root/assets/{scenes-page-B0JqO_fM.js → scenes-page-CwoAoYMX.js} +1 -1
  75. package/dist/gateway/static/root/assets/{schema-form-g4z7GH4W.js → schema-form-BIsHdyz7.js} +1 -1
  76. package/dist/gateway/static/root/assets/{send-session-input-BW40VHiL.js → send-session-input-CLhR1uEp.js} +1 -1
  77. package/dist/gateway/static/root/assets/{session-api-yjpEz4pP.js → session-api-u_4_kFQt.js} +1 -1
  78. package/dist/gateway/static/root/assets/{session-manager-BqlvrjPE.js → session-manager-B06ZCie_.js} +1 -1
  79. package/dist/gateway/static/root/assets/{sessions-page-CftImbH7.js → sessions-page-BcCLp3yU.js} +1 -1
  80. package/dist/gateway/static/root/assets/settings-advanced-gate-D114UdRI.js +1 -0
  81. package/dist/gateway/static/root/assets/{settings-page-YD-U8BVw.js → settings-page-z9Jlk0BL.js} +1 -1
  82. package/dist/gateway/static/root/assets/{setup-status-panel-q_X3kxw2.js → setup-status-panel-CrpXECqe.js} +1 -1
  83. package/dist/gateway/static/root/assets/{share-link-dialog-HCHM8qnf.js → share-link-dialog-XhiuPKF4.js} +1 -1
  84. package/dist/gateway/static/root/assets/{share-preview-page-Ce6FNWt1.js → share-preview-page-Vc0TQuZ8.js} +1 -1
  85. package/dist/gateway/static/root/assets/{shares-api-c0dfWz6h.js → shares-api-BVSb1vh4.js} +1 -1
  86. package/dist/gateway/static/root/assets/{shares-settings-KHRg_-03.js → shares-settings-D9XpTJXP.js} +1 -1
  87. package/dist/gateway/static/root/assets/{skill-api-BpqDMkNB.js → skill-api-DtPOf1aW.js} +1 -1
  88. package/dist/gateway/static/root/assets/{skill-reload-api-Ge5XymHa.js → skill-reload-api-BsLx_FXd.js} +1 -1
  89. package/dist/gateway/static/root/assets/{skills-page-Bnfn51ks.js → skills-page-Bq9-2b9U.js} +1 -1
  90. package/dist/gateway/static/root/assets/{system-settings-panel-CncvC2CI.js → system-settings-panel-P7T729uD.js} +1 -1
  91. package/dist/gateway/static/root/assets/{task-detail-page-BVJKdnMC.js → task-detail-page-CsB9AFx6.js} +1 -1
  92. package/dist/gateway/static/root/assets/{task-expandable-content-hYcgCEE3.js → task-expandable-content-DxeBTJvh.js} +1 -1
  93. package/dist/gateway/static/root/assets/{theme-store-BOfCKgl6.js → theme-store-KcQhCg1-.js} +1 -1
  94. package/dist/gateway/static/root/assets/{tracing-settings-page-DDWFXcST.js → tracing-settings-page-DJRAP3nb.js} +1 -1
  95. package/dist/gateway/static/root/assets/{tunnel-api-g7etuoaT.js → tunnel-api-CuowqIZ7.js} +1 -1
  96. package/dist/gateway/static/root/assets/usage-page-avX3msoZ.js +1 -0
  97. package/dist/gateway/static/root/assets/{use-autosave-CSckcjFx.js → use-autosave-C-IWxbzm.js} +1 -1
  98. package/dist/gateway/static/root/assets/{use-composer-attachments-DJya1hIL.js → use-composer-attachments-Dxphaxk-.js} +1 -1
  99. package/dist/gateway/static/root/assets/{use-file-preview-expanded-C5ONO7sF.js → use-file-preview-expanded-Du37Gofn.js} +2 -2
  100. package/dist/gateway/static/root/assets/{user-model-page-jr986ucl.js → user-model-page-DGDBWw7P.js} +1 -1
  101. package/dist/gateway/static/root/assets/{voice-api-key-field-GEw5x2fU.js → voice-api-key-field-7BImy8Jc.js} +1 -1
  102. package/dist/gateway/static/root/assets/{voice-config-api-3yyVwXln.js → voice-config-api-6MINytQl.js} +1 -1
  103. package/dist/gateway/static/root/assets/{work-discovery-overlay-CC3mr7tX.js → work-discovery-overlay-BgJNz2tH.js} +1 -1
  104. package/dist/gateway/static/root/assets/{workflow-run-setup-panel-CqpXZJxs.js → workflow-run-setup-panel-1I2q2J_U.js} +1 -1
  105. package/dist/gateway/static/root/assets/{workflows-page-DOk6tBbK.js → workflows-page-DhJl9pnO.js} +1 -1
  106. package/dist/gateway/static/root/assets/{workspace-api-WhbMvdBE.js → workspace-api-CXiyhTwv.js} +2 -2
  107. package/dist/gateway/static/root/assets/xopc-cloud-account-card-CnPBP1V_.js +1 -0
  108. package/dist/gateway/static/root/index.html +26 -26
  109. package/dist/package.js +1 -1
  110. package/dist/packages/endpoint-tools-protocol/src/index.js +4 -110
  111. package/dist/packages/endpoint-tools-protocol/src/personal.js +2 -2
  112. package/dist/packages/endpoint-tools-protocol/src/shared-schemas.js +116 -0
  113. package/dist/packages/gateway-contract/src/cloud-credits.js +80 -0
  114. package/dist/packages/gateway-contract/src/index.js +2 -0
  115. package/dist/src/agent/agent-manager.js +4 -4
  116. package/dist/src/agent/background-review/run-background-review.js +1 -1
  117. package/dist/src/agent/client-error-format.d.ts +1 -1
  118. package/dist/src/agent/client-error-format.js +7 -0
  119. package/dist/src/agent/embedded/model-runtime.js +1 -1
  120. package/dist/src/agent/embedded/run-for-session.js +2 -2
  121. package/dist/src/agent/embedded/run-turn.js +40 -4
  122. package/dist/src/agent/embedded/xopc-stream-bridge.js +18 -0
  123. package/dist/src/agent/image/generation/openai-images-provider.d.ts +2 -1
  124. package/dist/src/agent/image/generation/openai-images-provider.js +13 -7
  125. package/dist/src/agent/image/generation/providers/xopc-cloud.js +14 -2
  126. package/dist/src/agent/image/tool-model-config.js +1 -1
  127. package/dist/src/agent/service.js +3 -3
  128. package/dist/src/agent/session/session-inspector.js +1 -1
  129. package/dist/src/agent/tasks/task-judge-service.js +3 -3
  130. package/dist/src/agent-config/schema.d.ts +4 -0
  131. package/dist/src/agent-config/schema.js +1 -0
  132. package/dist/src/ai-assist/text-assist.js +1 -1
  133. package/dist/src/auth/credentials.js +2 -2
  134. package/dist/src/auth/model-credential-store.js +1 -1
  135. package/dist/src/auth/sync-provider-auth.js +1 -1
  136. package/dist/src/chat-commands/builtins/config.js +1 -1
  137. package/dist/src/cli/commands/doctor/checks/database-schema.js +1 -1
  138. package/dist/src/cli/commands/doctor/checks/image-providers.js +1 -1
  139. package/dist/src/cli/commands/doctor/checks/provider-auth.js +1 -1
  140. package/dist/src/cli/commands/models.js +42 -1
  141. package/dist/src/cli/commands/onboard/model.js +3 -3
  142. package/dist/src/computer/runtime.js +4 -1
  143. package/dist/src/config/index.js +3 -3
  144. package/dist/src/config/loader.js +1 -1
  145. package/dist/src/config/models-json.js +2 -2
  146. package/dist/src/endpoint-tools/policy.js +2 -1
  147. package/dist/src/extensions/activation-context.js +1 -1
  148. package/dist/src/gateway/hono/lib/config-payload.d.ts +1 -0
  149. package/dist/src/gateway/hono/lib/config-payload.js +2 -2
  150. package/dist/src/gateway/hono/routes/config-patch/misc.js +1 -1
  151. package/dist/src/gateway/hono/routes/models.js +4 -4
  152. package/dist/src/gateway/hono/routes/observability.js +1 -1
  153. package/dist/src/gateway/hono/routes/usage.js +15 -5
  154. package/dist/src/gateway/hono/routes/voice.js +1 -1
  155. package/dist/src/gateway/service/config-coordinator.js +1 -1
  156. package/dist/src/gateway/service/session-command-service.js +1 -1
  157. package/dist/src/gateway/service.js +4 -4
  158. package/dist/src/home-intelligence/generator.js +1 -1
  159. package/dist/src/notes/service.js +1 -1
  160. package/dist/src/observability/runtime.js +1 -1
  161. package/dist/src/personal-agent/proactivity/service.js +2 -2
  162. package/dist/src/personal-agent/reply-composer.js +2 -2
  163. package/dist/src/platform/client.d.ts +1 -1
  164. package/dist/src/platform/contracts.d.ts +1 -1
  165. package/dist/src/platform/index.js +1 -1
  166. package/dist/src/providers/index.js +4 -4
  167. package/dist/src/providers/model-call.js +15 -1
  168. package/dist/src/providers/model-registry.js +2 -2
  169. package/dist/src/providers/provider-auth-service.js +1 -1
  170. package/dist/src/providers/provider-disconnect-core.js +1 -1
  171. package/dist/src/providers/register-runtime-providers.js +1 -1
  172. package/dist/src/providers/xopc-cloud-account-service.d.ts +7 -23
  173. package/dist/src/providers/xopc-cloud-account-service.js +49 -29
  174. package/dist/src/providers/xopc-cloud-catalog-coordinator.js +1 -1
  175. package/dist/src/providers/xopc-cloud-credit-budget.d.ts +14 -0
  176. package/dist/src/providers/xopc-cloud-credit-budget.js +61 -0
  177. package/dist/src/storage/sqlite/ai-usage-repository.d.ts +9 -0
  178. package/dist/src/storage/sqlite/ai-usage-repository.js +94 -5
  179. package/dist/src/storage/sqlite/migrations/242_cloud_usage_receipts.sql +5 -0
  180. package/dist/src/storage/sqlite/migrations/runner.d.ts +1 -1
  181. package/dist/src/storage/sqlite/migrations/runner.js +3 -3
  182. package/dist/src/storage/sqlite/schema.js +1 -1
  183. package/dist/src/tasks/task-main-update-decision-service.js +2 -2
  184. package/dist/src/tui/backends/embedded-backend.js +1 -1
  185. package/dist/src/tui/tui.js +1 -1
  186. package/dist/src/usage/cloud-capability.d.ts +2 -0
  187. package/dist/src/usage/cloud-capability.js +34 -0
  188. package/dist/src/usage/cloud-receipts.d.ts +10 -0
  189. package/dist/src/usage/cloud-receipts.js +55 -0
  190. package/dist/src/usage/cost.js +1 -0
  191. package/dist/src/usage/recorder.js +3 -1
  192. package/dist/src/usage/scenario-registry.js +15 -0
  193. package/dist/src/voice/realtime/runtime.js +27 -22
  194. package/dist/src/voice/stt/providers/xopc-cloud-transcription.js +28 -14
  195. package/dist/src/voice/tts/providers/xopc-cloud-speech.js +39 -29
  196. package/dist/src/voice/tts/speech-provider-types.d.ts +1 -0
  197. package/dist/src/work-discovery/service.js +2 -2
  198. package/dist/src/workflows/service/workflow-run-service.js +1 -1
  199. package/package.json +4 -4
  200. package/dist/gateway/static/root/assets/agent-defaults-settings-panel-BLaL4law.js +0 -1
  201. package/dist/gateway/static/root/assets/gateway-realtime-F6D4A7N5.js +0 -1
  202. package/dist/gateway/static/root/assets/gateway-realtime-store-BheKaO6d.js +0 -1
  203. package/dist/gateway/static/root/assets/messages-Bb2GK-Nj.js +0 -3
  204. package/dist/gateway/static/root/assets/models-hub-cache-DyPPksMP.js +0 -1
  205. package/dist/gateway/static/root/assets/settings-advanced-gate-BkvbSieZ.js +0 -1
  206. package/dist/gateway/static/root/assets/usage-page-Dao1I-5F.js +0 -1
@@ -1,5 +1,8 @@
1
1
  import { getSqliteDatabase, init_transaction, runSqliteWriteTransaction } from "./transaction.js";
2
+ import { creditMicroSchema } from "../../../packages/gateway-contract/src/cloud-credits.js";
3
+ import { init_src } from "../../../packages/gateway-contract/src/index.js";
2
4
  //#region src/storage/sqlite/ai-usage-repository.ts
5
+ init_src();
3
6
  init_transaction();
4
7
  const ERROR_SUMMARY_LIMIT = 512;
5
8
  const AI_USAGE_RETENTION_MS = 4320 * 60 * 60 * 1e3;
@@ -43,6 +46,41 @@ function pruneAiUsageEvents(cutoffMs) {
43
46
  function getAiUsageEvent(id) {
44
47
  return getSqliteDatabase().prepare("SELECT * FROM ai_usage_events WHERE id = ?").get(id);
45
48
  }
49
+ function setAiUsageCloudRequest(id, requestId) {
50
+ if (!/^[A-Za-z0-9_.:-]{1,128}$/.test(requestId)) return;
51
+ runSqliteWriteTransaction((db) => {
52
+ db.prepare(`UPDATE ai_usage_events SET cloud_request_id = ?, updated_at = ?
53
+ WHERE id = ? AND provider = 'xopc-cloud' AND cloud_billing_json IS NULL`).run(requestId, Date.now(), id);
54
+ });
55
+ }
56
+ function setAiUsageCloudReceipt(id, receipt) {
57
+ if (![
58
+ "settled",
59
+ "released",
60
+ "expired",
61
+ "reserved",
62
+ "pending"
63
+ ].includes(receipt.state)) return;
64
+ const state = receipt.state === "settled" ? "settled" : ["released", "expired"].includes(receipt.state) ? "released" : "pending";
65
+ const charged = state === "settled" ? receipt.chargedCreditMicro : state === "released" ? "0" : null;
66
+ if (state === "settled" && charged === null) return;
67
+ creditMicroSchema.parse(receipt.reservedCreditMicro);
68
+ creditMicroSchema.parse(receipt.releasedCreditMicro);
69
+ if (charged !== null) creditMicroSchema.parse(charged);
70
+ const billing = {
71
+ source: "xopc_cloud",
72
+ state,
73
+ requestId: receipt.requestId,
74
+ reservedCreditMicro: state === "pending" ? receipt.reservedCreditMicro : "0",
75
+ chargedCreditMicro: charged,
76
+ releasedCreditMicro: receipt.releasedCreditMicro
77
+ };
78
+ runSqliteWriteTransaction((db) => {
79
+ db.prepare(`UPDATE ai_usage_events SET cloud_billing_json = ?, updated_at = ?
80
+ WHERE id = ? AND provider = 'xopc-cloud' AND cloud_request_id = ?
81
+ AND COALESCE(json_extract(cloud_billing_json, '$.state'), '') NOT IN ('settled', 'released')`).run(JSON.stringify(billing), Date.now(), id, receipt.requestId);
82
+ });
83
+ }
46
84
  function queryWhere(query) {
47
85
  const clauses = ["started_at >= ?", "started_at < ?"];
48
86
  const values = [query.from, query.to];
@@ -64,6 +102,18 @@ function queryWhere(query) {
64
102
  values
65
103
  };
66
104
  }
105
+ function pendingAiUsageCloudReceiptIds(query) {
106
+ const { sql, values } = queryWhere(query);
107
+ return getSqliteDatabase().prepare(`SELECT id FROM ai_usage_events WHERE ${sql}
108
+ AND provider = 'xopc-cloud' AND cloud_request_id IS NOT NULL
109
+ AND COALESCE(json_extract(cloud_billing_json, '$.state'), '') NOT IN ('settled', 'released')
110
+ ORDER BY updated_at ASC, id ASC LIMIT 4`).all(...values).map((row) => row.id);
111
+ }
112
+ function markAiUsageCloudReceiptAttempt(id) {
113
+ runSqliteWriteTransaction((db) => {
114
+ db.prepare(`UPDATE ai_usage_events SET updated_at = ? WHERE id = ? AND provider = 'xopc-cloud'`).run(Date.now(), id);
115
+ });
116
+ }
67
117
  function usd(microusd) {
68
118
  return (microusd / 1e6).toFixed(6).replace(/0+$/, "").replace(/\.$/, "") || "0";
69
119
  }
@@ -81,19 +131,44 @@ function totalsFromRow(row) {
81
131
  cacheWriteTokens: Number(row.cache_write_tokens ?? 0),
82
132
  totalTokens: Number(row.total_tokens ?? 0),
83
133
  knownCostUsd: usd(Number(row.known_cost_microusd ?? 0)),
84
- costCompleteness: calls === 0 || unknownCostCalls === calls ? "unknown" : unknownCostCalls > 0 ? "partial" : "complete"
134
+ costCompleteness: Number(row.external_calls ?? 0) === 0 || unknownCostCalls === Number(row.external_calls) ? "unknown" : unknownCostCalls > 0 ? "partial" : "complete",
135
+ billing: {
136
+ external: {
137
+ calls: Number(row.external_calls ?? 0),
138
+ estimatedCostUsd: usd(Number(row.known_cost_microusd ?? 0)),
139
+ unknownCostCalls
140
+ },
141
+ localCalls: Number(row.local_calls ?? 0),
142
+ cloud: {
143
+ calls: Number(row.cloud_calls ?? 0),
144
+ settledCalls: Number(row.cloud_final_calls ?? 0),
145
+ pendingCalls: Number(row.cloud_pending_calls ?? 0),
146
+ unavailableCalls: Number(row.cloud_unavailable_calls ?? 0),
147
+ chargedCreditMicro: Number(row.cloud_final_calls ?? 0) > 0 ? String(row.cloud_charged_micro ?? "0") : null,
148
+ reservedCreditMicro: Number(row.cloud_known_reservations ?? 0) > 0 ? String(row.cloud_reserved_micro ?? "0") : null
149
+ }
150
+ }
85
151
  };
86
152
  }
87
153
  const TOTALS_SQL = `COUNT(*) AS calls,
88
154
  SUM(CASE WHEN status = 'succeeded' THEN 1 ELSE 0 END) AS succeeded_calls,
89
155
  SUM(CASE WHEN status IN ('failed', 'aborted', 'unknown') THEN 1 ELSE 0 END) AS failed_calls,
90
- SUM(CASE WHEN cost_source <> 'local' AND estimated_cost_microusd IS NULL THEN 1 ELSE 0 END) AS unknown_cost_calls,
156
+ SUM(CASE WHEN provider <> 'xopc-cloud' AND cost_source <> 'local' AND estimated_cost_microusd IS NULL THEN 1 ELSE 0 END) AS unknown_cost_calls,
157
+ SUM(CASE WHEN provider <> 'xopc-cloud' AND cost_source <> 'local' THEN 1 ELSE 0 END) AS external_calls,
158
+ SUM(CASE WHEN provider <> 'xopc-cloud' AND cost_source = 'local' THEN 1 ELSE 0 END) AS local_calls,
159
+ SUM(CASE WHEN provider = 'xopc-cloud' THEN 1 ELSE 0 END) AS cloud_calls,
160
+ SUM(CASE WHEN provider = 'xopc-cloud' AND json_extract(cloud_billing_json, '$.state') IN ('settled', 'released') THEN 1 ELSE 0 END) AS cloud_final_calls,
161
+ SUM(CASE WHEN provider = 'xopc-cloud' AND json_extract(cloud_billing_json, '$.state') = 'pending' THEN 1 ELSE 0 END) AS cloud_pending_calls,
162
+ SUM(CASE WHEN provider = 'xopc-cloud' AND cloud_billing_json IS NULL THEN 1 ELSE 0 END) AS cloud_unavailable_calls,
163
+ SUM(CASE WHEN provider = 'xopc-cloud' AND cloud_billing_json IS NOT NULL THEN 1 ELSE 0 END) AS cloud_known_reservations,
164
+ CAST(COALESCE(SUM(CASE WHEN provider = 'xopc-cloud' THEN CAST(json_extract(cloud_billing_json, '$.chargedCreditMicro') AS INTEGER) ELSE 0 END), 0) AS TEXT) AS cloud_charged_micro,
165
+ CAST(COALESCE(SUM(CASE WHEN provider = 'xopc-cloud' THEN CAST(json_extract(cloud_billing_json, '$.reservedCreditMicro') AS INTEGER) ELSE 0 END), 0) AS TEXT) AS cloud_reserved_micro,
91
166
  COALESCE(SUM(input_tokens), 0) AS input_tokens,
92
167
  COALESCE(SUM(output_tokens), 0) AS output_tokens,
93
168
  COALESCE(SUM(cache_read_tokens), 0) AS cache_read_tokens,
94
169
  COALESCE(SUM(cache_write_tokens), 0) AS cache_write_tokens,
95
170
  COALESCE(SUM(total_tokens), 0) AS total_tokens,
96
- COALESCE(SUM(estimated_cost_microusd), 0) AS known_cost_microusd`;
171
+ COALESCE(SUM(CASE WHEN provider <> 'xopc-cloud' AND cost_source <> 'local' THEN estimated_cost_microusd ELSE 0 END), 0) AS known_cost_microusd`;
97
172
  /** Aggregate all retained AI usage records for the requested conversations. */
98
173
  function summarizeAiUsageByConversations(conversationIds) {
99
174
  const uniqueIds = [...new Set(conversationIds.filter(Boolean))];
@@ -140,6 +215,19 @@ function summarizeAiUsage(query) {
140
215
  }
141
216
  function eventFromRow(row) {
142
217
  const pricingSnapshot = typeof row.pricing_snapshot_json === "string" ? JSON.parse(row.pricing_snapshot_json) : void 0;
218
+ const estimatedCostUsd = row.provider === "xopc-cloud" || row.cost_source === "local" || row.estimated_cost_microusd === null ? void 0 : usd(Number(row.estimated_cost_microusd));
219
+ const billing = row.provider === "xopc-cloud" ? row.cloud_billing_json ? JSON.parse(String(row.cloud_billing_json)) : {
220
+ source: "xopc_cloud",
221
+ state: "unavailable",
222
+ requestId: row.cloud_request_id ?? void 0,
223
+ reservedCreditMicro: null,
224
+ chargedCreditMicro: null,
225
+ releasedCreditMicro: null
226
+ } : row.cost_source === "local" ? { source: "local" } : {
227
+ source: "external_provider",
228
+ currency: "USD",
229
+ estimatedCostUsd
230
+ };
143
231
  return {
144
232
  id: String(row.id),
145
233
  traceId: String(row.trace_id),
@@ -165,7 +253,8 @@ function eventFromRow(row) {
165
253
  cacheWriteTokens: row.cache_write_tokens === null ? void 0 : Number(row.cache_write_tokens),
166
254
  reasoningTokens: row.reasoning_tokens === null ? void 0 : Number(row.reasoning_tokens),
167
255
  totalTokens: row.total_tokens === null ? void 0 : Number(row.total_tokens),
168
- estimatedCostUsd: row.estimated_cost_microusd === null ? void 0 : usd(Number(row.estimated_cost_microusd)),
256
+ estimatedCostUsd,
257
+ billing,
169
258
  costSource: String(row.cost_source),
170
259
  pricingSnapshot,
171
260
  errorSummary: row.error_summary ? String(row.error_summary) : void 0
@@ -198,4 +287,4 @@ function listAiUsageTrace(traceId) {
198
287
  ORDER BY started_at ASC, id ASC`).all(traceId).map(eventFromRow);
199
288
  }
200
289
  //#endregion
201
- export { AI_USAGE_RETENTION_MS, finishAiUsageEvent, getAiUsageEvent, getAiUsageEventView, insertAiUsageEvent, listAiUsageEvents, listAiUsageTrace, markStaleAiUsageEventsUnknown, pruneAiUsageEvents, summarizeAiUsage, summarizeAiUsageByConversations };
290
+ export { AI_USAGE_RETENTION_MS, finishAiUsageEvent, getAiUsageEvent, getAiUsageEventView, insertAiUsageEvent, listAiUsageEvents, listAiUsageTrace, markAiUsageCloudReceiptAttempt, markStaleAiUsageEventsUnknown, pendingAiUsageCloudReceiptIds, pruneAiUsageEvents, setAiUsageCloudReceipt, setAiUsageCloudRequest, summarizeAiUsage, summarizeAiUsageByConversations };
@@ -0,0 +1,5 @@
1
+ ALTER TABLE ai_usage_events ADD COLUMN cloud_request_id TEXT;
2
+ ALTER TABLE ai_usage_events ADD COLUMN cloud_billing_json TEXT
3
+ CHECK(cloud_billing_json IS NULL OR json_valid(cloud_billing_json));
4
+ CREATE UNIQUE INDEX idx_ai_usage_cloud_request
5
+ ON ai_usage_events(cloud_request_id) WHERE cloud_request_id IS NOT NULL;
@@ -3,7 +3,7 @@ import type { ApplyMigrationsOptions } from './types.js';
3
3
  /** Baseline schema version applied from schema.sql on first open. */
4
4
  export declare const XOPC_DB_BASELINE_SCHEMA_VERSION = 165;
5
5
  /** Latest schema version this release supports (increment when adding migrations). */
6
- export declare const XOPC_DB_SCHEMA_VERSION = 241;
6
+ export declare const XOPC_DB_SCHEMA_VERSION = 242;
7
7
  export declare function resolveMigrationsDir(override?: string): string;
8
8
  /**
9
9
  * Apply sequential SQL migrations from (currentVersion + 1) through targetVersion.
@@ -57,7 +57,7 @@ function applySingleMigration(db, migration, discardOldScenes = false) {
57
57
  */
58
58
  function applyPendingMigrations(db, options = {}) {
59
59
  db.function("xopc_operation_id", () => currentOperationId() ?? null);
60
- const targetVersion = options.targetVersion ?? 241;
60
+ const targetVersion = options.targetVersion ?? 242;
61
61
  let currentVersion = readSchemaVersion(db);
62
62
  if (currentVersion > targetVersion) throw new DatabaseSchemaTooNewError(currentVersion, targetVersion);
63
63
  if (!options.migrationsDir && currentVersion < 165) throw new DatabaseSchemaTooOldError(currentVersion, 165);
@@ -95,7 +95,7 @@ function applyPendingMigrations(db, options = {}) {
95
95
  }
96
96
  /** Inspect schema version without mutating the database. */
97
97
  function inspectSchemaMigrationStatus(db, options = {}) {
98
- const appVersion = options.targetVersion ?? 241;
98
+ const appVersion = options.targetVersion ?? 242;
99
99
  const dbVersion = readSchemaVersion(db);
100
100
  if (dbVersion > appVersion) return {
101
101
  dbVersion,
@@ -137,7 +137,7 @@ var init_runner = __esmMin((() => {
137
137
  init_errors();
138
138
  log = createLogger("Sqlite:Migrations");
139
139
  XOPC_DB_BASELINE_SCHEMA_VERSION = 165;
140
- XOPC_DB_SCHEMA_VERSION = 241;
140
+ XOPC_DB_SCHEMA_VERSION = 242;
141
141
  }));
142
142
  //#endregion
143
143
  init_runner();
@@ -28,7 +28,7 @@ function ensureXopcDatabaseSchema(db, options = {}) {
28
28
  currentVersion = 165;
29
29
  }
30
30
  applyPendingMigrations(db, {
31
- targetVersion: 241,
31
+ targetVersion: 242,
32
32
  databasePath: fresh ? void 0 : options.databasePath
33
33
  });
34
34
  }
@@ -1,9 +1,9 @@
1
1
  import { TaskRepository, init_task_repository } from "./task-repository.js";
2
2
  import { TaskRunRepository, init_task_run_repository } from "./task-run-repository.js";
3
+ import { extractAssistantText, getAssistantMessageErrorReason, stripCodeFences } from "../providers/model-response.js";
3
4
  import { init_providers, resolveModel } from "../providers/index.js";
4
- import { getPersonalAgentByConversation } from "../personal-agent/repository.js";
5
5
  import { completeWithResolvedCredentials } from "../providers/model-call.js";
6
- import { extractAssistantText, getAssistantMessageErrorReason, stripCodeFences } from "../providers/model-response.js";
6
+ import { getPersonalAgentByConversation } from "../personal-agent/repository.js";
7
7
  import { TaskCollaborationRepository } from "./task-collaboration-repository.js";
8
8
  //#region src/tasks/task-main-update-decision-service.ts
9
9
  init_providers();
@@ -11,8 +11,8 @@ import { init_sqlite } from "../../storage/sqlite/index.js";
11
11
  import { normalizeAgentId } from "../../agent-catalog/id.js";
12
12
  import { init_agent_scope, listAgentEntries } from "../../agent/agent-scope.js";
13
13
  import { AgentCatalogService } from "../../agent-catalog/service.js";
14
- import { init_paths } from "../../config/paths.js";
15
14
  import { getAgentDefaultModelRef, init_schema } from "../../config/schema.js";
15
+ import { init_paths } from "../../config/paths.js";
16
16
  import { init_agent_session_key, resolveAgentIdFromConversationId } from "../../routing/agent-session-key.js";
17
17
  import { resolveEffectiveAgentProfile } from "../../config/agent-profile.js";
18
18
  import { effectiveWorkspacePathForSession } from "../../session/session-workspace.js";
@@ -2,8 +2,8 @@ import { version } from "../../package.js";
2
2
  import { resolveStateDir, resolveXopcDatabasePath } from "../config/paths-state.js";
3
3
  import { init_paths } from "../config/paths.js";
4
4
  import { init_agent_session_key, normalizeAgentId } from "../routing/agent-session-key.js";
5
- import { resolveEffectiveAgentProfile } from "../config/agent-profile.js";
6
5
  import { getAllModels, getAllProviders, getApiKey, init_providers, isProviderConfiguredSync } from "../providers/index.js";
6
+ import { resolveEffectiveAgentProfile } from "../config/agent-profile.js";
7
7
  import { isTerminalWorkflowRunStatus } from "../workflows/domain/run.js";
8
8
  import "../workflows/domain/index.js";
9
9
  import { ProjectTrustStore, hasTrustRequiringProjectResources } from "../project-trust/trust-store.js";
@@ -0,0 +1,2 @@
1
+ /** Cloud tools receive independent receipts even when the parent chat uses BYOK. */
2
+ export declare function trackCloudCapabilityUsage<T>(model: string, operation: string, action: () => Promise<T>): Promise<T>;
@@ -0,0 +1,34 @@
1
+ import { startAiUsageCall } from "./recorder.js";
2
+ import { getEmbeddedExecutionRunId, getEmbeddedExecutionSession } from "../agent/embedded/execution-context.js";
3
+ //#region src/usage/cloud-capability.ts
4
+ /** Cloud tools receive independent receipts even when the parent chat uses BYOK. */
5
+ async function trackCloudCapabilityUsage(model, operation, action) {
6
+ const call = startAiUsageCall({
7
+ provider: "xopc-cloud",
8
+ id: model,
9
+ baseUrl: void 0,
10
+ cost: {
11
+ input: 0,
12
+ output: 0,
13
+ cacheRead: 0,
14
+ cacheWrite: 0
15
+ }
16
+ }, {
17
+ operation,
18
+ conversationId: getEmbeddedExecutionSession(),
19
+ runId: getEmbeddedExecutionRunId()
20
+ });
21
+ try {
22
+ const result = await call.run(action);
23
+ call.finish({ status: "succeeded" });
24
+ return result;
25
+ } catch (error) {
26
+ call.finish({
27
+ status: "failed",
28
+ errorSummary: error instanceof Error ? error.message : String(error)
29
+ });
30
+ throw error;
31
+ }
32
+ }
33
+ //#endregion
34
+ export { trackCloudCapabilityUsage };
@@ -0,0 +1,10 @@
1
+ import type { SimpleStreamOptions } from '@earendil-works/pi-ai';
2
+ export declare function withUsageReceiptContext<T>(id: string, action: () => T): T;
3
+ export declare function captureCloudUsageResponse(response: {
4
+ status: number;
5
+ headers: Headers | Record<string, string>;
6
+ }): void;
7
+ /** Preserve existing response hooks while linking the physical Cloud request to its usage row. */
8
+ export declare function withCloudUsageResponse(options?: SimpleStreamOptions): SimpleStreamOptions;
9
+ /** Bounded, read-only synchronization. Missing authorization or receipt never becomes a zero charge. */
10
+ export declare function reconcileCloudUsageReceipt(id: string): Promise<void>;
@@ -0,0 +1,55 @@
1
+ import { init_connection, isXopcDatabaseOpen } from "../storage/sqlite/connection.js";
2
+ import { getAiUsageEvent, markAiUsageCloudReceiptAttempt, setAiUsageCloudReceipt, setAiUsageCloudRequest } from "../storage/sqlite/ai-usage-repository.js";
3
+ import { XopcCloudAccountService } from "../providers/xopc-cloud-account-service.js";
4
+ import { AsyncLocalStorage } from "node:async_hooks";
5
+ //#region src/usage/cloud-receipts.ts
6
+ init_connection();
7
+ const calls = new AsyncLocalStorage();
8
+ const active = /* @__PURE__ */ new Map();
9
+ function withUsageReceiptContext(id, action) {
10
+ return calls.run({ id }, action);
11
+ }
12
+ function captureCloudUsageResponse(response) {
13
+ if (response.status < 200 || response.status >= 300) return;
14
+ const id = calls.getStore()?.id;
15
+ const requestId = new Headers(response.headers).get("x-xopc-request-id");
16
+ if (id && requestId && isXopcDatabaseOpen()) try {
17
+ setAiUsageCloudRequest(id, requestId);
18
+ } catch {}
19
+ }
20
+ /** Preserve existing response hooks while linking the physical Cloud request to its usage row. */
21
+ function withCloudUsageResponse(options) {
22
+ return {
23
+ ...options,
24
+ onResponse: async (response, model) => {
25
+ if (model.provider === "xopc-cloud") captureCloudUsageResponse(response);
26
+ await options?.onResponse?.(response, model);
27
+ }
28
+ };
29
+ }
30
+ /** Bounded, read-only synchronization. Missing authorization or receipt never becomes a zero charge. */
31
+ function reconcileCloudUsageReceipt(id) {
32
+ const running = active.get(id);
33
+ if (running) return running;
34
+ if (!isXopcDatabaseOpen() || active.size >= 4) return Promise.resolve();
35
+ const row = getAiUsageEvent(id);
36
+ if (row?.provider !== "xopc-cloud" || typeof row.cloud_request_id !== "string") return Promise.resolve();
37
+ const state = row.cloud_billing_json ? JSON.parse(String(row.cloud_billing_json)).state : void 0;
38
+ if (state === "settled" || state === "released") return Promise.resolve();
39
+ const requestId = row.cloud_request_id;
40
+ markAiUsageCloudReceiptAttempt(id);
41
+ const service = new XopcCloudAccountService({ fetchImpl: (url, init) => fetch(url, {
42
+ ...init,
43
+ signal: AbortSignal.timeout(5e3)
44
+ }) });
45
+ const sync = (async () => {
46
+ try {
47
+ const receipt = await service.getReceipt(requestId);
48
+ if (receipt?.requestId === requestId && isXopcDatabaseOpen()) setAiUsageCloudReceipt(id, receipt);
49
+ } catch {}
50
+ })().finally(() => active.delete(id));
51
+ active.set(id, sync);
52
+ return sync;
53
+ }
54
+ //#endregion
55
+ export { captureCloudUsageResponse, reconcileCloudUsageReceipt, withCloudUsageResponse, withUsageReceiptContext };
@@ -17,6 +17,7 @@ function pricingSnapshot(model) {
17
17
  };
18
18
  }
19
19
  function classifyCostSource(model) {
20
+ if (model.provider === "xopc-cloud") return "unknown";
20
21
  if (isLocalBaseUrl(model.baseUrl)) return "local";
21
22
  return Object.values(pricingSnapshot(model)).some((price) => price > 0) ? "model_catalog" : "unknown";
22
23
  }
@@ -5,6 +5,7 @@ import { startObservation } from "../observability/runtime.js";
5
5
  import { finishAiUsageEvent, insertAiUsageEvent } from "../storage/sqlite/ai-usage-repository.js";
6
6
  import { classifyCostSource, dollarsToMicrousd, pricingSnapshot } from "./cost.js";
7
7
  import { resolveAiUsageScenario } from "./scenario-registry.js";
8
+ import { reconcileCloudUsageReceipt, withUsageReceiptContext } from "./cloud-receipts.js";
8
9
  import { randomUUID } from "node:crypto";
9
10
  //#region src/usage/recorder.ts
10
11
  init_connection();
@@ -67,7 +68,7 @@ function startAiUsageCall(model, context, input) {
67
68
  let completed = false;
68
69
  return {
69
70
  id,
70
- run: observation.run,
71
+ run: (fn) => observation.run(() => withUsageReceiptContext(id, fn)),
71
72
  finish(result) {
72
73
  if (completed) return;
73
74
  completed = true;
@@ -90,6 +91,7 @@ function startAiUsageCall(model, context, input) {
90
91
  ...result,
91
92
  estimatedCostMicrousd: costSource === "local" ? 0 : costSource === "unknown" ? void 0 : dollarsToMicrousd(result.usage?.cost?.total)
92
93
  });
94
+ if (model.provider === "xopc-cloud") reconcileCloudUsageReceipt(id);
93
95
  } catch (err) {
94
96
  log.error({
95
97
  err,
@@ -1,5 +1,20 @@
1
1
  //#region src/usage/scenario-registry.ts
2
2
  const SCENARIOS = {
3
+ "images.generate": {
4
+ category: "other",
5
+ reasonKey: "usage.reason.imageGeneration",
6
+ trigger: "agent"
7
+ },
8
+ "audio.transcribe": {
9
+ category: "other",
10
+ reasonKey: "usage.reason.audioTranscription",
11
+ trigger: "user"
12
+ },
13
+ "audio.speech": {
14
+ category: "other",
15
+ reasonKey: "usage.reason.audioSpeech",
16
+ trigger: "agent"
17
+ },
3
18
  "agent.answer": {
4
19
  category: "chat",
5
20
  reasonKey: "usage.reason.agentAnswer",
@@ -418,29 +418,34 @@ var VoiceRealtimeRuntime = class {
418
418
  sockets?.delete(socket);
419
419
  if (sockets?.size === 0) this.socketsByPrincipal.delete(claim.principalId);
420
420
  };
421
- const shutdown = async (reason, notify) => {
422
- if (closed) return;
421
+ let shutdownCompletion;
422
+ const shutdown = (reason, notify) => {
423
+ if (shutdownCompletion) return shutdownCompletion;
424
+ if (closed) return Promise.resolve();
423
425
  closed = true;
424
- if (claim && this.activeSessions.get(claim.sessionId)?.socket === socket) this.activeSessions.delete(claim.sessionId);
425
- unsubscribeMemory?.();
426
- unsubscribeSession?.();
427
- clearTimeout(startTimer);
428
- clearInterval(lifecycleTimer);
429
- try {
430
- await engine?.close();
431
- } catch (err) {
432
- log.error({
433
- err,
434
- sessionId: claim?.sessionId
435
- }, "Voice cleanup failed");
436
- } finally {
437
- abortController.abort(reason);
438
- }
439
- if (notify) send("session.closed", { reason });
440
- detachPrincipal();
441
- if (claim) this.releaseConversationReservation(claim);
442
- this.preauthBudget.release(clientIp);
443
- if (socket.readyState === WebSocketState.OPEN) socket.close(1e3, reason.slice(0, 120));
426
+ shutdownCompletion = (async () => {
427
+ unsubscribeMemory?.();
428
+ unsubscribeSession?.();
429
+ clearTimeout(startTimer);
430
+ clearInterval(lifecycleTimer);
431
+ try {
432
+ await engine?.close();
433
+ } catch (err) {
434
+ log.error({
435
+ err,
436
+ sessionId: claim?.sessionId
437
+ }, "Voice cleanup failed");
438
+ } finally {
439
+ abortController.abort(reason);
440
+ }
441
+ if (notify) send("session.closed", { reason });
442
+ detachPrincipal();
443
+ if (claim && this.activeSessions.get(claim.sessionId)?.socket === socket) this.activeSessions.delete(claim.sessionId);
444
+ if (claim) this.releaseConversationReservation(claim);
445
+ this.preauthBudget.release(clientIp);
446
+ if (socket.readyState === WebSocketState.OPEN) socket.close(1e3, reason.slice(0, 120));
447
+ })();
448
+ return shutdownCompletion;
444
449
  };
445
450
  const startTimer = setTimeout(() => socket.close(4401, "Voice session start timeout"), VOICE_REALTIME_START_TIMEOUT_MS);
446
451
  const lifecycleTimer = setInterval(() => {
@@ -1,8 +1,11 @@
1
1
  import { getModelCatalogStore, init_model_catalog_store } from "../../../providers/model-catalog-store.js";
2
2
  import { init_xopc_cloud_config, resolveXopcModelRouterUrl } from "../../../providers/xopc-cloud-config.js";
3
3
  import { getProviderAuthService, init_provider_auth_service } from "../../../providers/provider-auth-service.js";
4
+ import { captureCloudUsageResponse } from "../../../usage/cloud-receipts.js";
5
+ import { cloudCreditBudgetHeaders, rejectUnbudgetedCloudTransport, reportCloudCreditError } from "../../../providers/xopc-cloud-credit-budget.js";
4
6
  import { compareCatalogModels } from "../../../providers/model-catalog-ranking.js";
5
7
  import { registerMediaUnderstandingProvider } from "../../../media-understanding/registry.js";
8
+ import { trackCloudCapabilityUsage } from "../../../usage/cloud-capability.js";
6
9
  import { openPlatformStt } from "../../platform-streams.js";
7
10
  import { platformVoiceModels, requirePlatformVoiceModel } from "../../platform-catalog.js";
8
11
  //#region src/voice/stt/providers/xopc-cloud-transcription.ts
@@ -32,21 +35,31 @@ const xopcCloudTranscriptionProvider = {
32
35
  form.append("file", new File([Uint8Array.from(request.buffer)], request.fileName, { type: request.mime ?? "application/octet-stream" }));
33
36
  if (request.language) form.append("language", request.language);
34
37
  if (request.prompt) form.append("prompt", request.prompt);
35
- const response = await fetch(`${baseUrl}/audio/transcriptions`, {
36
- method: "POST",
37
- headers: { authorization: `Bearer ${accessToken}` },
38
- body: form,
39
- signal: request.signal ?? AbortSignal.timeout(request.timeoutMs),
40
- redirect: "error"
38
+ return trackCloudCapabilityUsage(model, "audio.transcribe", async () => {
39
+ const response = await fetch(`${baseUrl}/audio/transcriptions`, {
40
+ method: "POST",
41
+ headers: {
42
+ authorization: `Bearer ${accessToken}`,
43
+ ...await cloudCreditBudgetHeaders()
44
+ },
45
+ body: form,
46
+ signal: request.signal ?? AbortSignal.timeout(request.timeoutMs),
47
+ redirect: "error"
48
+ });
49
+ captureCloudUsageResponse(response);
50
+ const body = await response.json().catch(() => null);
51
+ if (!response.ok) {
52
+ const message = `${body?.error?.code ?? ""}: ${typeof body?.error?.message === "string" ? body.error.message : `XOPC Cloud STT failed (${response.status})`}`;
53
+ reportCloudCreditError(message);
54
+ throw new Error(message);
55
+ }
56
+ if (typeof body?.text !== "string") throw new Error("XOPC Cloud STT returned an invalid response");
57
+ return {
58
+ text: body.text,
59
+ model,
60
+ ...request.language ? { language: request.language } : {}
61
+ };
41
62
  });
42
- const body = await response.json().catch(() => null);
43
- if (!response.ok) throw new Error(typeof body?.error?.message === "string" ? body.error.message : `XOPC Cloud STT failed (${response.status})`);
44
- if (typeof body?.text !== "string") throw new Error("XOPC Cloud STT returned an invalid response");
45
- return {
46
- text: body.text,
47
- model,
48
- ...request.language ? { language: request.language } : {}
49
- };
50
63
  },
51
64
  streamingAudio: {
52
65
  inputSampleRates: [16e3],
@@ -59,6 +72,7 @@ const xopcCloudTranscriptionProvider = {
59
72
  }
60
73
  },
61
74
  openAudioStream: async (request) => {
75
+ rejectUnbudgetedCloudTransport();
62
76
  const catalogModel = requirePlatformVoiceModel(request.model, "transcription.stream");
63
77
  const accessToken = await getProviderAuthService().resolveApiKey("xopc-cloud", request.signal);
64
78
  if (!accessToken) throw new Error("XOPC Cloud authorization is unavailable");
@@ -1,7 +1,10 @@
1
1
  import { getModelCatalogStore, init_model_catalog_store } from "../../../providers/model-catalog-store.js";
2
2
  import { init_xopc_cloud_config, resolveXopcModelRouterUrl } from "../../../providers/xopc-cloud-config.js";
3
3
  import { getProviderAuthService, init_provider_auth_service } from "../../../providers/provider-auth-service.js";
4
+ import { captureCloudUsageResponse } from "../../../usage/cloud-receipts.js";
5
+ import { cloudCreditBudgetHeaders, rejectUnbudgetedCloudTransport, reportCloudCreditError } from "../../../providers/xopc-cloud-credit-budget.js";
4
6
  import { compareCatalogModels } from "../../../providers/model-catalog-ranking.js";
7
+ import { trackCloudCapabilityUsage } from "../../../usage/cloud-capability.js";
5
8
  import { openPlatformTts } from "../../platform-streams.js";
6
9
  import { requirePlatformVoiceModel } from "../../platform-catalog.js";
7
10
  import { registerSpeechProvider } from "../speech-registry.js";
@@ -74,38 +77,45 @@ const xopcCloudSpeechProvider = {
74
77
  const outputFormat = request.target === "voice-note" && catalogModel.tts?.outputFormats.includes("opus") ? "opus" : catalogModel.tts?.outputFormats.includes("mp3") ? "mp3" : catalogModel.tts?.outputFormats[0] ?? "wav";
75
78
  const token = await getProviderAuthService().resolveApiKey("xopc-cloud");
76
79
  if (!token) throw new Error("XOPC Cloud authorization is unavailable");
77
- const response = await fetch(`${config.baseUrl.replace(/\/+$/, "")}/audio/speech`, {
78
- method: "POST",
79
- headers: {
80
- authorization: `Bearer ${token}`,
81
- "content-type": "application/json"
82
- },
83
- body: JSON.stringify({
84
- model,
85
- input: request.text,
86
- voice,
87
- response_format: outputFormat,
88
- ...speed !== void 0 && catalogModel.tts?.speed ? { speed } : {},
89
- ...instructions && catalogModel.tts?.instructions ? { instructions } : {}
90
- }),
91
- signal: request.signal,
92
- redirect: "error"
80
+ return trackCloudCapabilityUsage(model, "audio.speech", async () => {
81
+ const response = await fetch(`${config.baseUrl.replace(/\/+$/, "")}/audio/speech`, {
82
+ method: "POST",
83
+ headers: {
84
+ authorization: `Bearer ${token}`,
85
+ "content-type": "application/json",
86
+ ...await cloudCreditBudgetHeaders()
87
+ },
88
+ body: JSON.stringify({
89
+ model,
90
+ input: request.text,
91
+ voice,
92
+ response_format: outputFormat,
93
+ ...speed !== void 0 && catalogModel.tts?.speed ? { speed } : {},
94
+ ...instructions && catalogModel.tts?.instructions ? { instructions } : {}
95
+ }),
96
+ signal: request.signal,
97
+ redirect: "error"
98
+ });
99
+ captureCloudUsageResponse(response);
100
+ if (!response.ok) {
101
+ const body = await response.json().catch(() => null);
102
+ const message = `${body?.error?.code ?? ""}: ${typeof body?.error?.message === "string" ? body.error.message : `XOPC Cloud TTS failed (${response.status})`}`;
103
+ reportCloudCreditError(message);
104
+ throw new Error(message);
105
+ }
106
+ const audioBuffer = Buffer.from(await response.arrayBuffer());
107
+ if (audioBuffer.length === 0) throw new Error("XOPC Cloud TTS returned empty audio");
108
+ const fileExtension = extension(response.headers.get("content-type"), outputFormat);
109
+ return {
110
+ audioBuffer,
111
+ outputFormat: fileExtension,
112
+ fileExtension,
113
+ voiceCompatible: fileExtension === "opus" || fileExtension === "ogg"
114
+ };
93
115
  });
94
- if (!response.ok) {
95
- const body = await response.json().catch(() => null);
96
- throw new Error(typeof body?.error?.message === "string" ? body.error.message : `XOPC Cloud TTS failed (${response.status})`);
97
- }
98
- const audioBuffer = Buffer.from(await response.arrayBuffer());
99
- if (audioBuffer.length === 0) throw new Error("XOPC Cloud TTS returned empty audio");
100
- const fileExtension = extension(response.headers.get("content-type"), outputFormat);
101
- return {
102
- audioBuffer,
103
- outputFormat: fileExtension,
104
- fileExtension,
105
- voiceCompatible: fileExtension === "opus" || fileExtension === "ogg"
106
- };
107
116
  },
108
117
  synthesizeStream: async (request) => {
118
+ rejectUnbudgetedCloudTransport();
109
119
  const config = readConfig(request.providerConfig);
110
120
  const model = typeof request.providerOverrides?.model === "string" ? request.providerOverrides.model : config.model;
111
121
  const voice = typeof request.providerOverrides?.voice === "string" ? request.providerOverrides.voice : config.voice;
@@ -97,6 +97,7 @@ export interface SpeechStreamSession {
97
97
  export type SpeechStreamSessionRequest = Omit<SpeechSynthesisStreamRequest, 'text'>;
98
98
  /** Voice catalog entry returned by `listVoices`. */
99
99
  export interface SpeechVoiceOption {
100
+ displayNames?: Partial<Record<'zh-CN' | 'en', string>>;
100
101
  id: string;
101
102
  name?: string;
102
103
  category?: string;