@xopcai/xopc 0.0.202 → 0.0.204

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 (179) 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-DOhnAQOQ.js → activity-store-DJBadfSX.js} +1 -1
  4. package/dist/gateway/static/root/assets/{agent-avatar-dicebear-CAjfa48Q.js → agent-avatar-dicebear-BaH7Poqk.js} +1 -1
  5. package/dist/gateway/static/root/assets/agents-HctDDhtH.js +68 -0
  6. package/dist/gateway/static/root/assets/{api-E9Bk-Idp.js → api-B5O7fKXa.js} +1 -1
  7. package/dist/gateway/static/root/assets/{app-management-settings-panel-GNT3iv0V.js → app-management-settings-panel-CCLK9wdi.js} +1 -1
  8. package/dist/gateway/static/root/assets/{appearance-settings-BFond2ll.js → appearance-settings-DYSHvGJP.js} +1 -1
  9. package/dist/gateway/static/root/assets/apps-page-kCUkEIoM.js +1 -0
  10. package/dist/gateway/static/root/assets/{archive-plugin-C6qpchzp.js → archive-plugin-CZX6pfRb.js} +1 -1
  11. package/dist/gateway/static/root/assets/{attachment-load-B4CcSDdV.js → attachment-load-CGYpcBXN.js} +1 -1
  12. package/dist/gateway/static/root/assets/automations-page-D2Ao4Dwi.js +6 -0
  13. package/dist/gateway/static/root/assets/{binary-plugins-xYL_41XP.js → binary-plugins-BJ1AQfTa.js} +1 -1
  14. package/dist/gateway/static/root/assets/{block-editor-nYiJQItv.js → block-editor-DR6LCljI.js} +46 -46
  15. package/dist/gateway/static/root/assets/browser-settings-page-B_IjOjtT.js +8 -0
  16. package/dist/gateway/static/root/assets/{browser-workflow-inputs-D_jnndyz.js → browser-workflow-inputs-Bd68yluo.js} +1 -1
  17. package/dist/gateway/static/root/assets/{browser-workflows-page-pT7Hvwwh.js → browser-workflows-page-BG-hEoGn.js} +2 -2
  18. package/dist/gateway/static/root/assets/capabilities-settings-panel-DDC5-wqk.js +7 -0
  19. package/dist/gateway/static/root/assets/{capability-presets-api--tz0mGLY.js → capability-presets-api-BerPMp6f.js} +1 -1
  20. package/dist/gateway/static/root/assets/capability-presets-settings-panel-CxpKaTzc.js +3 -0
  21. package/dist/gateway/static/root/assets/{channel-recipient-api-CkOKF9nc.js → channel-recipient-api-D2d21_XA.js} +1 -1
  22. package/dist/gateway/static/root/assets/channels-settings-D7pmMIbZ.js +1 -0
  23. package/dist/gateway/static/root/assets/channels-status-swr-CZ5P9jbB.js +1 -0
  24. package/dist/gateway/static/root/assets/{connectors-api-X5ukNasZ.js → connectors-api-Sw0BrUmJ.js} +1 -1
  25. package/dist/gateway/static/root/assets/connectors-page-DTyM5bMS.js +2 -0
  26. package/dist/gateway/static/root/assets/cron-expression-DfZsNMAq.js +2 -0
  27. package/dist/gateway/static/root/assets/{date-picker-CGm8EH8G.js → date-picker-DjHSmgq9.js} +1 -1
  28. package/dist/gateway/static/root/assets/dependency-picker-CRajhh0O.js +1 -0
  29. package/dist/gateway/static/root/assets/desktop-pet-a3DXD4IR.js +1 -0
  30. package/dist/gateway/static/root/assets/{desktop-pet-settings-s4kmnf-W.js → desktop-pet-settings-BWx0Duz-.js} +1 -1
  31. package/dist/gateway/static/root/assets/{directory-picker-path-field-DmfFg4Gq.js → directory-picker-path-field-CEZrPGZx.js} +1 -1
  32. package/dist/gateway/static/root/assets/download-Dk-ZjtO6.js +1 -0
  33. package/dist/gateway/static/root/assets/{extension-debug-page-DrLEK8GU.js → extension-debug-page-CwJ-R0DC.js} +1 -1
  34. package/dist/gateway/static/root/assets/{extension-page-DtyFPtMR.js → extension-page-BpXWrZFh.js} +1 -1
  35. package/dist/gateway/static/root/assets/{extension-settings-page-Xok6T2KD.js → extension-settings-page-D0c4RgxT.js} +1 -1
  36. package/dist/gateway/static/root/assets/{gateway-config-api-DwSpEYiw.js → gateway-config-api-Dn2FSNBa.js} +1 -1
  37. package/dist/gateway/static/root/assets/{gateway-config-swr-Cv2ywpdb.js → gateway-config-swr-VVcfd3sG.js} +1 -1
  38. package/dist/gateway/static/root/assets/{gateway-settings-B5Zh2rx9.js → gateway-settings-BNamAC3x.js} +1 -1
  39. package/dist/gateway/static/root/assets/{gateway-startup-retry-undf-k5g.js → gateway-startup-retry-qAX_T2t2.js} +1 -1
  40. package/dist/gateway/static/root/assets/{heartbeat-settings-C2RxqeK0.js → heartbeat-settings-BagVvGe6.js} +1 -1
  41. package/dist/gateway/static/root/assets/home-page-Ci0h-uKG.js +3 -0
  42. package/dist/gateway/static/root/assets/index-DHGnTdgl.js +85 -0
  43. package/dist/gateway/static/root/assets/index-Dm-cvIpl.css +2 -0
  44. package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-BN3p-1wI.js → keyboard-shortcuts-settings-BOoz5DNd.js} +1 -1
  45. package/dist/gateway/static/root/assets/local-app-workbench-page-B1mLlQT6.js +2 -0
  46. package/dist/gateway/static/root/assets/local-apps-page-B2mV4Olf.js +1 -0
  47. package/dist/gateway/static/root/assets/{locale-store-CnamlR07.js → locale-store-CbPVxonP.js} +1 -1
  48. package/dist/gateway/static/root/assets/logs-page-DLFwiLa6.js +2 -0
  49. package/dist/gateway/static/root/assets/{management-settings-CTIaHfgU.js → management-settings-BEduNvMc.js} +1 -1
  50. package/dist/gateway/static/root/assets/{markdown-split-C0iKadAm.js → markdown-split-BgE9kKG6.js} +1 -1
  51. package/dist/gateway/static/root/assets/markdown-view-B12xeflb.css +1 -0
  52. package/dist/gateway/static/root/assets/markdown-view-Dl-0_9JR.js +69 -0
  53. package/dist/gateway/static/root/assets/{media-plugins-DcM72GSJ.js → media-plugins-DNESLGDB.js} +1 -1
  54. package/dist/gateway/static/root/assets/messages-9zCa8KkW.js +3 -0
  55. package/dist/gateway/static/root/assets/{notes-page-By_EzRhP.js → notes-page-COgmlorx.js} +1 -1
  56. package/dist/gateway/static/root/assets/notes-workbench-Ce5ORQus.js +4 -0
  57. package/dist/gateway/static/root/assets/plus-Q2yW6OE7.js +1 -0
  58. package/dist/gateway/static/root/assets/preview-open-alternatives-BV_58lf7.js +1 -0
  59. package/dist/gateway/static/root/assets/{product-open-page-dQ2_5PLu.js → product-open-page-Bh8WERvg.js} +1 -1
  60. package/dist/gateway/static/root/assets/project-detail-page-C0vjVpKC.js +1 -0
  61. package/dist/gateway/static/root/assets/projects-page-RGaztvxc.js +1 -0
  62. package/dist/gateway/static/root/assets/remote-access-hub-C7EfVrqr.js +1 -0
  63. package/dist/gateway/static/root/assets/{runtime-tools-settings-panel-DiPj9FLN.js → runtime-tools-settings-panel-HZYyqA5T.js} +2 -2
  64. package/dist/gateway/static/root/assets/{schema-form-BDLYItBP.js → schema-form-C6EnKgW2.js} +1 -1
  65. package/dist/gateway/static/root/assets/sessions-page-BTpLFg2e.js +1 -0
  66. package/dist/gateway/static/root/assets/settings-advanced-gate-RV6nphHJ.js +1 -0
  67. package/dist/gateway/static/root/assets/{settings-form-section-Dr4Uf_CY.js → settings-form-section-sAjYWtCZ.js} +1 -1
  68. package/dist/gateway/static/root/assets/{settings-loading-skeleton-Bqi7tsXj.js → settings-loading-skeleton-Bw56H64J.js} +1 -1
  69. package/dist/gateway/static/root/assets/{settings-page-BbookgxJ.js → settings-page-aduxT-Nu.js} +1 -1
  70. package/dist/gateway/static/root/assets/setup-status-panel-CVg8oOTw.js +1 -0
  71. package/dist/gateway/static/root/assets/share-preview-page-SmtgZyVf.js +2 -0
  72. package/dist/gateway/static/root/assets/shares-settings-DNXQwu2U.js +1 -0
  73. package/dist/gateway/static/root/assets/skill-reload-api-DhgLGYw8.js +1 -0
  74. package/dist/gateway/static/root/assets/skills-page-BHBDl1pN.js +2 -0
  75. package/dist/gateway/static/root/assets/{skills-page.utils-b9byH-KL.js → skills-page.utils-D5SnlPVg.js} +1 -1
  76. package/dist/gateway/static/root/assets/{system-settings-panel-CL_AXjw1.js → system-settings-panel-wiXKgbG9.js} +1 -1
  77. package/dist/gateway/static/root/assets/task-detail-page-DBu-p3Iv.js +1 -0
  78. package/dist/gateway/static/root/assets/{theme-store-BDyUTObE.js → theme-store-Bc_eRUev.js} +1 -1
  79. package/dist/gateway/static/root/assets/{use-autosave-CUI3q_I-.js → use-autosave-CiuRY85U.js} +1 -1
  80. package/dist/gateway/static/root/assets/user-context-page-B-uZKYIg.js +1 -0
  81. package/dist/gateway/static/root/assets/{voice-api-key-field-DLXOB1jw.js → voice-api-key-field-CX9xuc7h.js} +1 -1
  82. package/dist/gateway/static/root/assets/work-discovery-overlay-DrTD6gtS.js +1 -0
  83. package/dist/gateway/static/root/assets/work-discovery-page-C4Ivt1_C.js +1 -0
  84. package/dist/gateway/static/root/assets/{workflow-run-setup-panel-DKpUxB-i.js → workflow-run-setup-panel-CG0fSFFv.js} +2 -2
  85. package/dist/gateway/static/root/assets/workflows-page-CT5g5who.js +5 -0
  86. package/dist/gateway/static/root/assets/{working-directory-picker-modal-BcyiDXU3.js → working-directory-picker-modal-CYWdZUJ5.js} +1 -1
  87. package/dist/gateway/static/root/assets/x-B_N4OPjy.js +1 -0
  88. package/dist/gateway/static/root/index.html +19 -14
  89. package/dist/package.js +1 -1
  90. package/dist/packages/realtime-client/src/index.js +11 -7
  91. package/dist/src/agent/agent-manager.js +3 -2
  92. package/dist/src/agent/embedded/run-for-session.js +3 -2
  93. package/dist/src/agent/embedded/run-turn.js +29 -16
  94. package/dist/src/agent/embedded/types.d.ts +2 -2
  95. package/dist/src/agent/embedded/xopc-stream-bridge.d.ts +2 -1
  96. package/dist/src/agent/embedded/xopc-stream-bridge.js +2 -2
  97. package/dist/src/agent/memory/compaction-planner.js +20 -4
  98. package/dist/src/agent/memory/compaction.js +22 -4
  99. package/dist/src/agent/memory/context-budget.d.ts +6 -1
  100. package/dist/src/agent/memory/context-budget.js +94 -2
  101. package/dist/src/agent/memory/create-memory-manager.js +1 -1
  102. package/dist/src/agent/prompt/cache-boundary.d.ts +2 -0
  103. package/dist/src/agent/prompt/cache-boundary.js +10 -1
  104. package/dist/src/agent/prompt/sections/project-context.d.ts +1 -4
  105. package/dist/src/agent/prompt/sections/project-context.js +6 -13
  106. package/dist/src/agent/prompt/service-prompt-builder.js +2 -1
  107. package/dist/src/agent/prompt/system-prompt.d.ts +1 -3
  108. package/dist/src/agent/prompt/system-prompt.js +6 -15
  109. package/dist/src/agent/tools/cache-stability.d.ts +3 -0
  110. package/dist/src/agent/tools/cache-stability.js +19 -0
  111. package/dist/src/agent/tools/factory.js +2 -1
  112. package/dist/src/agent-manifest/schema.d.ts +40 -20
  113. package/dist/src/agent-manifest/schema.js +4 -5
  114. package/dist/src/agent-runtime/index.js +1 -1
  115. package/dist/src/agent-runtime/runtime-profile.js +1 -1
  116. package/dist/src/cli/commands/gateway/lifecycle.js +1 -1
  117. package/dist/src/config/schema.d.ts +40 -20
  118. package/dist/src/extensions/types/providers.d.ts +0 -2
  119. package/dist/src/gateway/chat-stream/mapper.js +44 -4
  120. package/dist/src/gateway/heartbeat/service.js +1 -1
  121. package/dist/src/gateway/hono/lib/config-payload.d.ts +4 -1
  122. package/dist/src/heartbeat/index.js +1 -1
  123. package/dist/src/infra/update-restart.js +1 -1
  124. package/dist/src/providers/extension-stream-bridge.js +0 -2
  125. package/dist/src/providers/google-managed-prompt-cache.d.ts +14 -0
  126. package/dist/src/providers/google-managed-prompt-cache.js +125 -0
  127. package/dist/src/providers/index.js +5 -4
  128. package/dist/src/providers/model-call.js +2 -2
  129. package/dist/src/providers/model-supplements.d.ts +2 -0
  130. package/dist/src/providers/model-supplements.js +23 -2
  131. package/dist/src/providers/prompt-cache-fingerprint.d.ts +4 -0
  132. package/dist/src/providers/prompt-cache-fingerprint.js +44 -0
  133. package/dist/src/providers/prompt-cache-lifecycle.d.ts +9 -0
  134. package/dist/src/providers/prompt-cache-lifecycle.js +31 -0
  135. package/dist/src/providers/prompt-cache-observability.d.ts +20 -0
  136. package/dist/src/providers/prompt-cache-observability.js +44 -0
  137. package/dist/src/providers/prompt-cache-payload.d.ts +10 -3
  138. package/dist/src/providers/prompt-cache-payload.js +204 -31
  139. package/dist/src/providers/prompt-cache-plan.d.ts +15 -0
  140. package/dist/src/providers/prompt-cache-plan.js +29 -0
  141. package/dist/src/realtime/runtime.js +9 -2
  142. package/dist/src/realtime/writer.js +5 -0
  143. package/package.json +3 -3
  144. package/dist/gateway/static/root/assets/agents-Cke8EF5Z.js +0 -68
  145. package/dist/gateway/static/root/assets/apps-page-B9D-6sJt.js +0 -1
  146. package/dist/gateway/static/root/assets/automations-page-BFS3dH43.js +0 -6
  147. package/dist/gateway/static/root/assets/browser-settings-page-ELV-Y8rz.js +0 -8
  148. package/dist/gateway/static/root/assets/capabilities-settings-panel-Dbx_nPAI.js +0 -7
  149. package/dist/gateway/static/root/assets/capability-presets-settings-panel-DHg_JMUz.js +0 -3
  150. package/dist/gateway/static/root/assets/channels-settings-DjHOlPq-.js +0 -1
  151. package/dist/gateway/static/root/assets/channels-status-swr-DNDvMWpl.js +0 -1
  152. package/dist/gateway/static/root/assets/connectors-page-RY-W6CQt.js +0 -2
  153. package/dist/gateway/static/root/assets/cron-expression-DHY0LrLq.js +0 -2
  154. package/dist/gateway/static/root/assets/dependency-picker-DDECr6Hj.js +0 -1
  155. package/dist/gateway/static/root/assets/desktop-pet-BoEIRgm2.js +0 -1
  156. package/dist/gateway/static/root/assets/home-page-DPhkqxEb.js +0 -3
  157. package/dist/gateway/static/root/assets/index-Bp8I_eX4.css +0 -1
  158. package/dist/gateway/static/root/assets/index-CKSplmFw.js +0 -152
  159. package/dist/gateway/static/root/assets/local-app-workbench-page-D7FmunJF.js +0 -2
  160. package/dist/gateway/static/root/assets/local-apps-page-C7H807zd.js +0 -1
  161. package/dist/gateway/static/root/assets/logs-page-vrjf-JTk.js +0 -2
  162. package/dist/gateway/static/root/assets/messages-D7kOD_o_.js +0 -3
  163. package/dist/gateway/static/root/assets/notes-workbench-Cp1ierS_.js +0 -4
  164. package/dist/gateway/static/root/assets/preview-open-alternatives-CpblfmWk.js +0 -1
  165. package/dist/gateway/static/root/assets/project-detail-page-Cp_jfZJj.js +0 -1
  166. package/dist/gateway/static/root/assets/projects-page-DYbmweGw.js +0 -1
  167. package/dist/gateway/static/root/assets/remote-access-hub-U2qXVyLH.js +0 -1
  168. package/dist/gateway/static/root/assets/sessions-page-CtNKNhCi.js +0 -1
  169. package/dist/gateway/static/root/assets/settings-advanced-gate-B6IMzhWo.js +0 -1
  170. package/dist/gateway/static/root/assets/setup-status-panel-mUu0iDlD.js +0 -1
  171. package/dist/gateway/static/root/assets/share-preview-page-1M79esSO.js +0 -2
  172. package/dist/gateway/static/root/assets/shares-settings-DadcMA8y.js +0 -1
  173. package/dist/gateway/static/root/assets/skill-reload-api-C3ZzYCz0.js +0 -1
  174. package/dist/gateway/static/root/assets/skills-page-B3kqZ-ke.js +0 -2
  175. package/dist/gateway/static/root/assets/task-detail-page-Bnjpkl4v.js +0 -1
  176. package/dist/gateway/static/root/assets/user-context-page-CBgAAZiB.js +0 -1
  177. package/dist/gateway/static/root/assets/work-discovery-overlay-BvlvyVJX.js +0 -1
  178. package/dist/gateway/static/root/assets/work-discovery-page-BBpnnnrs.js +0 -1
  179. package/dist/gateway/static/root/assets/workflows-page-Dd_9p9ji.js +0 -5
@@ -0,0 +1,44 @@
1
+ import { createHash } from "node:crypto";
2
+ //#region src/providers/prompt-cache-fingerprint.ts
3
+ function asRecord(value) {
4
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
5
+ }
6
+ const MAX_FINGERPRINT_DEPTH = 64;
7
+ const MAX_FINGERPRINT_NODES = 2e4;
8
+ const MAX_FINGERPRINT_STRING_CHARS = 1e5;
9
+ function canonicalize(value, state, depth) {
10
+ state.nodes += 1;
11
+ if (depth > MAX_FINGERPRINT_DEPTH || state.nodes > MAX_FINGERPRINT_NODES) return "\"[fingerprint-limit]\"";
12
+ if (typeof value === "string" && value.length > MAX_FINGERPRINT_STRING_CHARS) return JSON.stringify(`${value.slice(0, MAX_FINGERPRINT_STRING_CHARS)}[fingerprint-limit]`);
13
+ if (Array.isArray(value)) {
14
+ if (state.seen.has(value)) return "\"[circular]\"";
15
+ state.seen.add(value);
16
+ const result = `[${value.map((entry) => canonicalize(entry, state, depth + 1)).join(",")}]`;
17
+ state.seen.delete(value);
18
+ return result;
19
+ }
20
+ const record = asRecord(value);
21
+ if (record) {
22
+ if (state.seen.has(record)) return "\"[circular]\"";
23
+ state.seen.add(record);
24
+ const result = `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${canonicalize(record[key], state, depth + 1)}`).join(",")}}`;
25
+ state.seen.delete(record);
26
+ return result;
27
+ }
28
+ return JSON.stringify(value) ?? "null";
29
+ }
30
+ /** Canonical JSON used only for bounded prompt-cache fingerprints. */
31
+ function canonicalizeCacheValue(value) {
32
+ return canonicalize(value, {
33
+ nodes: 0,
34
+ seen: /* @__PURE__ */ new WeakSet()
35
+ }, 0);
36
+ }
37
+ function digestCacheValue(value) {
38
+ return createHash("sha256").update(canonicalizeCacheValue(value)).digest("base64url");
39
+ }
40
+ function digestCacheText(value) {
41
+ return createHash("sha256").update(value).digest("base64url");
42
+ }
43
+ //#endregion
44
+ export { canonicalizeCacheValue, digestCacheText, digestCacheValue };
@@ -0,0 +1,9 @@
1
+ import type { Api, Model } from '@earendil-works/pi-ai';
2
+ import type { PromptCachePolicy } from './prompt-cache-plan.js';
3
+ export declare function resolvePromptCacheTtlMs(model: Model<Api>, policy: PromptCachePolicy): number;
4
+ export declare function recordPromptCacheTouch(sessionKey: string, model: Model<Api>, usage: {
5
+ cacheRead?: number;
6
+ cacheWrite?: number;
7
+ }, now?: number): void;
8
+ export declare function isPromptCacheExpired(sessionKey: string, model: Model<Api>, policy: PromptCachePolicy, now?: number): boolean;
9
+ export declare function clearPromptCacheTouches(): void;
@@ -0,0 +1,31 @@
1
+ //#region src/providers/prompt-cache-lifecycle.ts
2
+ const cacheTouchByScope = /* @__PURE__ */ new Map();
3
+ const MAX_CACHE_TOUCHES = 512;
4
+ function scopeKey(sessionKey, model) {
5
+ return `${sessionKey}\0${model.provider}/${model.id}`;
6
+ }
7
+ function resolvePromptCacheTtlMs(model, policy) {
8
+ if (model.api === "openai-responses" && /^gpt-5\.6(?:-|$)/.test(model.id)) return 30 * 6e4;
9
+ return policy.lifetime === "long" ? 60 * 6e4 : 5 * 6e4;
10
+ }
11
+ function recordPromptCacheTouch(sessionKey, model, usage, now = Date.now()) {
12
+ if ((usage.cacheRead ?? 0) <= 0 && (usage.cacheWrite ?? 0) <= 0) return;
13
+ const key = scopeKey(sessionKey, model);
14
+ cacheTouchByScope.delete(key);
15
+ cacheTouchByScope.set(key, now);
16
+ while (cacheTouchByScope.size > MAX_CACHE_TOUCHES) {
17
+ const oldest = cacheTouchByScope.keys().next().value;
18
+ if (typeof oldest !== "string") break;
19
+ cacheTouchByScope.delete(oldest);
20
+ }
21
+ }
22
+ function isPromptCacheExpired(sessionKey, model, policy, now = Date.now()) {
23
+ if (policy.mode === "off") return false;
24
+ const touchedAt = cacheTouchByScope.get(scopeKey(sessionKey, model));
25
+ return touchedAt !== void 0 && now - touchedAt >= resolvePromptCacheTtlMs(model, policy);
26
+ }
27
+ function clearPromptCacheTouches() {
28
+ cacheTouchByScope.clear();
29
+ }
30
+ //#endregion
31
+ export { clearPromptCacheTouches, isPromptCacheExpired, recordPromptCacheTouch, resolvePromptCacheTtlMs };
@@ -0,0 +1,20 @@
1
+ import type { Api, Model } from '@earendil-works/pi-ai';
2
+ export type PromptCacheChangeReason = 'model_changed' | 'reasoning_changed' | 'system_changed' | 'dynamic_context_changed' | 'tools_changed';
3
+ export interface PromptCacheSnapshot {
4
+ modelRef: string;
5
+ api: string;
6
+ reasoning?: string;
7
+ stableSystemDigest: string;
8
+ dynamicSystemDigest: string;
9
+ toolDigest: string;
10
+ toolCount: number;
11
+ }
12
+ export declare function buildPromptCacheSnapshot(params: {
13
+ model: Model<Api>;
14
+ systemPrompt?: string;
15
+ tools?: unknown[];
16
+ reasoning?: string;
17
+ }): PromptCacheSnapshot;
18
+ export declare function diffPromptCacheSnapshots(previous: PromptCacheSnapshot | undefined, next: PromptCacheSnapshot): PromptCacheChangeReason[];
19
+ export declare function observePromptCacheSnapshot(scope: string, snapshot: PromptCacheSnapshot): PromptCacheChangeReason[];
20
+ export declare function clearPromptCacheObservations(): void;
@@ -0,0 +1,44 @@
1
+ import { splitPromptCacheBoundary, stripPromptCacheBoundary } from "../agent/prompt/cache-boundary.js";
2
+ import { digestCacheText, digestCacheValue } from "./prompt-cache-fingerprint.js";
3
+ //#region src/providers/prompt-cache-observability.ts
4
+ const MAX_TRACKED_SCOPES = 512;
5
+ const previousByScope = /* @__PURE__ */ new Map();
6
+ function buildPromptCacheSnapshot(params) {
7
+ const rawPrompt = params.systemPrompt ?? "";
8
+ const split = splitPromptCacheBoundary(rawPrompt);
9
+ return {
10
+ modelRef: `${params.model.provider}/${params.model.id}`,
11
+ api: params.model.api,
12
+ ...params.reasoning ? { reasoning: params.reasoning } : {},
13
+ stableSystemDigest: digestCacheText(split?.stablePrefix ?? stripPromptCacheBoundary(rawPrompt)),
14
+ dynamicSystemDigest: digestCacheText(split?.dynamicSuffix ?? ""),
15
+ toolDigest: digestCacheValue(params.tools ?? []),
16
+ toolCount: params.tools?.length ?? 0
17
+ };
18
+ }
19
+ function diffPromptCacheSnapshots(previous, next) {
20
+ if (!previous) return [];
21
+ const reasons = [];
22
+ if (previous.modelRef !== next.modelRef || previous.api !== next.api) reasons.push("model_changed");
23
+ if (previous.reasoning !== next.reasoning) reasons.push("reasoning_changed");
24
+ if (previous.stableSystemDigest !== next.stableSystemDigest) reasons.push("system_changed");
25
+ if (previous.dynamicSystemDigest !== next.dynamicSystemDigest) reasons.push("dynamic_context_changed");
26
+ if (previous.toolDigest !== next.toolDigest) reasons.push("tools_changed");
27
+ return reasons;
28
+ }
29
+ function observePromptCacheSnapshot(scope, snapshot) {
30
+ const reasons = diffPromptCacheSnapshots(previousByScope.get(scope), snapshot);
31
+ previousByScope.delete(scope);
32
+ previousByScope.set(scope, snapshot);
33
+ while (previousByScope.size > MAX_TRACKED_SCOPES) {
34
+ const oldest = previousByScope.keys().next().value;
35
+ if (typeof oldest !== "string") break;
36
+ previousByScope.delete(oldest);
37
+ }
38
+ return reasons;
39
+ }
40
+ function clearPromptCacheObservations() {
41
+ previousByScope.clear();
42
+ }
43
+ //#endregion
44
+ export { buildPromptCacheSnapshot, clearPromptCacheObservations, diffPromptCacheSnapshots, observePromptCacheSnapshot };
@@ -1,13 +1,20 @@
1
1
  import type { Api, Model, SimpleStreamOptions } from '@earendil-works/pi-ai';
2
- export declare function transformPromptCachePayload(model: Model<Api>, payload: unknown): unknown;
2
+ import { type PromptCachePlan, type PromptCachePolicy } from './prompt-cache-plan.js';
3
3
  export declare function buildPromptCacheKey(model: Model<Api>, context: {
4
4
  systemPrompt?: string;
5
5
  tools?: unknown[];
6
- }): string;
6
+ }, reasoning?: string): string;
7
+ export declare function buildPromptCachePlan(model: Model<Api>, context: {
8
+ systemPrompt?: string;
9
+ tools?: unknown[];
10
+ }, policyInput?: PromptCachePolicy, reasoning?: string): PromptCachePlan;
11
+ export declare function transformPromptCachePayload(model: Model<Api>, payload: unknown, plan?: PromptCachePlan, context?: {
12
+ systemPrompt?: string;
13
+ }): unknown;
7
14
  export declare function preparePromptCacheContext<T extends {
8
15
  systemPrompt?: string;
9
16
  }>(model: Model<Api>, context: T): T;
10
17
  export declare function withPromptCachePayloadTransform(model: Model<Api>, context: {
11
18
  systemPrompt?: string;
12
19
  tools?: unknown[];
13
- }, options: SimpleStreamOptions | undefined): SimpleStreamOptions | undefined;
20
+ }, options: SimpleStreamOptions | undefined, policyInput?: PromptCachePolicy): SimpleStreamOptions;
@@ -1,15 +1,25 @@
1
1
  import { splitPromptCacheBoundary, stripPromptCacheBoundary } from "../agent/prompt/cache-boundary.js";
2
- import { createHash } from "node:crypto";
2
+ import { canonicalizeCacheValue, digestCacheText } from "./prompt-cache-fingerprint.js";
3
+ import { resolvePromptCachePolicy, resolvePromptCacheProviderMode } from "./prompt-cache-plan.js";
4
+ import { applyGoogleManagedPromptCache } from "./google-managed-prompt-cache.js";
3
5
  //#region src/providers/prompt-cache-payload.ts
4
6
  function asRecord(value) {
5
7
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
6
8
  }
7
- function supportsPromptCacheKey(model) {
8
- if (model.api === "openai-codex-responses" || model.api === "azure-openai-responses") return true;
9
- if (model.api !== "openai-completions" && model.api !== "openai-responses") return false;
10
- return model.provider === "openai" || model.baseUrl?.includes("api.openai.com") === true;
9
+ function buildPromptCacheKey(model, context, reasoning) {
10
+ const stablePrompt = context.systemPrompt ? splitPromptCacheBoundary(context.systemPrompt)?.stablePrefix ?? stripPromptCacheBoundary(context.systemPrompt) : "";
11
+ return `xopc-v2-${digestCacheText(`v2\0${model.provider}\0${model.id}\0${reasoning ?? ""}\0${stablePrompt}\0${canonicalizeCacheValue(context.tools ?? [])}`)}`;
11
12
  }
12
- function splitAnthropicSystem(payload) {
13
+ function buildPromptCachePlan(model, context, policyInput, reasoning) {
14
+ const policy = resolvePromptCachePolicy(policyInput);
15
+ const providerMode = resolvePromptCacheProviderMode(model, policy);
16
+ return {
17
+ policy,
18
+ providerMode,
19
+ ...providerMode === "implicit" || providerMode === "explicit" ? { cacheKey: buildPromptCacheKey(model, context, reasoning) } : {}
20
+ };
21
+ }
22
+ function splitAnthropicSystem(payload, lifetime) {
13
23
  const record = asRecord(payload);
14
24
  if (!record || !Array.isArray(record.system)) return payload;
15
25
  let changed = false;
@@ -23,7 +33,11 @@ function splitAnthropicSystem(payload) {
23
33
  return [{
24
34
  ...rest,
25
35
  type: block.type ?? "text",
26
- text: split.stablePrefix
36
+ text: split.stablePrefix,
37
+ cache_control: lifetime === "long" ? {
38
+ type: "ephemeral",
39
+ ttl: "1h"
40
+ } : { type: "ephemeral" }
27
41
  }, ...split.dynamicSuffix ? [{
28
42
  type: block.type ?? "text",
29
43
  text: split.dynamicSuffix
@@ -34,6 +48,54 @@ function splitAnthropicSystem(payload) {
34
48
  system
35
49
  } : payload;
36
50
  }
51
+ function addAnthropicHistoryBreakpoint(payload, lifetime) {
52
+ const record = asRecord(payload);
53
+ if (!record || !Array.isArray(record.messages)) return payload;
54
+ const messages = [...record.messages];
55
+ const cacheControl = lifetime === "long" ? {
56
+ type: "ephemeral",
57
+ ttl: "1h"
58
+ } : { type: "ephemeral" };
59
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
60
+ const message = asRecord(messages[index]);
61
+ if (!message || message.role !== "user") continue;
62
+ const blocks = Array.isArray(message.content) ? [...message.content] : void 0;
63
+ const containsToolResult = blocks?.some((block) => asRecord(block)?.type === "tool_result") ?? false;
64
+ if (index === messages.length - 1 && !containsToolResult) continue;
65
+ if (!blocks) {
66
+ if (typeof message.content !== "string") continue;
67
+ messages[index] = {
68
+ ...message,
69
+ content: [{
70
+ type: "text",
71
+ text: message.content,
72
+ cache_control: cacheControl
73
+ }]
74
+ };
75
+ return {
76
+ ...record,
77
+ messages
78
+ };
79
+ }
80
+ for (let blockIndex = blocks.length - 1; blockIndex >= 0; blockIndex -= 1) {
81
+ const block = asRecord(blocks[blockIndex]);
82
+ if (!block) continue;
83
+ blocks[blockIndex] = {
84
+ ...block,
85
+ cache_control: cacheControl
86
+ };
87
+ messages[index] = {
88
+ ...message,
89
+ content: blocks
90
+ };
91
+ return {
92
+ ...record,
93
+ messages
94
+ };
95
+ }
96
+ }
97
+ return payload;
98
+ }
37
99
  function splitBedrockSystem(payload) {
38
100
  const record = asRecord(payload);
39
101
  if (!record || !Array.isArray(record.system)) return payload;
@@ -56,7 +118,7 @@ function splitBedrockSystem(payload) {
56
118
  if (next?.cachePoint) {
57
119
  system.push(next);
58
120
  index += 1;
59
- }
121
+ } else system.push({ cachePoint: { type: "default" } });
60
122
  if (split.dynamicSuffix) system.push({ text: split.dynamicSuffix });
61
123
  }
62
124
  return changed ? {
@@ -64,20 +126,128 @@ function splitBedrockSystem(payload) {
64
126
  system
65
127
  } : payload;
66
128
  }
67
- function transformPromptCachePayload(model, payload) {
68
- if (model.api === "anthropic-messages") return splitAnthropicSystem(payload);
69
- if (model.api === "bedrock-converse-stream") return splitBedrockSystem(payload);
129
+ function addBedrockHistoryBreakpoint(payload) {
130
+ const record = asRecord(payload);
131
+ if (!record || !Array.isArray(record.messages)) return payload;
132
+ const messages = [...record.messages];
133
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
134
+ const message = asRecord(messages[index]);
135
+ if (!message || message.role !== "user" || !Array.isArray(message.content)) continue;
136
+ const content = [...message.content];
137
+ const containsToolResult = content.some((block) => "toolResult" in (asRecord(block) ?? {}));
138
+ if (index === messages.length - 1 && !containsToolResult) continue;
139
+ content.push({ cachePoint: { type: "default" } });
140
+ messages[index] = {
141
+ ...message,
142
+ content
143
+ };
144
+ return {
145
+ ...record,
146
+ messages
147
+ };
148
+ }
70
149
  return payload;
71
150
  }
72
- function canonicalJson(value) {
73
- if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
74
- const record = asRecord(value);
75
- if (record) return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson(record[key])}`).join(",")}}`;
76
- return JSON.stringify(value) ?? "null";
151
+ function addBreakpointToContent(content) {
152
+ if (typeof content === "string") return [{
153
+ type: "input_text",
154
+ text: content,
155
+ prompt_cache_breakpoint: { mode: "explicit" }
156
+ }];
157
+ if (!Array.isArray(content)) return content;
158
+ const blocks = [...content];
159
+ for (let index = blocks.length - 1; index >= 0; index -= 1) {
160
+ const block = asRecord(blocks[index]);
161
+ if (!block) continue;
162
+ blocks[index] = {
163
+ ...block,
164
+ prompt_cache_breakpoint: { mode: "explicit" }
165
+ };
166
+ return blocks;
167
+ }
168
+ return content;
77
169
  }
78
- function buildPromptCacheKey(model, context) {
79
- const stablePrompt = context.systemPrompt ? splitPromptCacheBoundary(context.systemPrompt)?.stablePrefix ?? stripPromptCacheBoundary(context.systemPrompt) : "";
80
- return `xopc-${createHash("sha256").update(`${model.provider}\0${model.id}\0${stablePrompt}\0${canonicalJson(context.tools ?? [])}`).digest("base64url")}`;
170
+ function addOpenAIHistoryBreakpoint(items) {
171
+ if (!Array.isArray(items)) return items;
172
+ const next = [...items];
173
+ for (let index = next.length - 1; index >= 0; index -= 1) {
174
+ const item = asRecord(next[index]);
175
+ if (!item) continue;
176
+ if (item.type === "function_call_output") {
177
+ next[index] = {
178
+ ...item,
179
+ output: addBreakpointToContent(item.output)
180
+ };
181
+ return next;
182
+ }
183
+ if (!("content" in item)) continue;
184
+ if (index === next.length - 1 && item.role === "user") continue;
185
+ const content = addBreakpointToContent(item.content);
186
+ if (content === item.content) continue;
187
+ next[index] = {
188
+ ...item,
189
+ content
190
+ };
191
+ return next;
192
+ }
193
+ return items;
194
+ }
195
+ function applyOpenAIExplicitCache(payload, context, cacheKey) {
196
+ const record = asRecord(payload);
197
+ if (!record) return payload;
198
+ const split = splitPromptCacheBoundary(context.systemPrompt ?? "");
199
+ const stablePrompt = split?.stablePrefix ?? stripPromptCacheBoundary(context.systemPrompt ?? "");
200
+ const dynamicPrompt = split?.dynamicSuffix ?? "";
201
+ const stableContent = [{
202
+ type: "input_text",
203
+ text: stablePrompt,
204
+ prompt_cache_breakpoint: { mode: "explicit" }
205
+ }];
206
+ const dynamicContent = [{
207
+ type: "input_text",
208
+ text: dynamicPrompt
209
+ }];
210
+ if (Array.isArray(record.input)) {
211
+ const expectedInstructions = stripPromptCacheBoundary(context.systemPrompt ?? "");
212
+ const canReplaceInstructions = record.instructions === void 0 || record.instructions === expectedInstructions;
213
+ return {
214
+ ...record,
215
+ ...canReplaceInstructions ? { instructions: [{
216
+ type: "message",
217
+ role: "system",
218
+ content: stableContent
219
+ }, ...dynamicPrompt ? [{
220
+ type: "message",
221
+ role: "system",
222
+ content: dynamicContent
223
+ }] : []] } : {},
224
+ input: addOpenAIHistoryBreakpoint(record.input),
225
+ prompt_cache_key: cacheKey,
226
+ prompt_cache_options: {
227
+ mode: "explicit",
228
+ ttl: "30m"
229
+ }
230
+ };
231
+ }
232
+ return {
233
+ ...record,
234
+ prompt_cache_key: cacheKey,
235
+ prompt_cache_options: {
236
+ mode: "explicit",
237
+ ttl: "30m"
238
+ }
239
+ };
240
+ }
241
+ function transformPromptCachePayload(model, payload, plan = buildPromptCachePlan(model, {}), context = {}) {
242
+ if (plan.providerMode === "none" || plan.providerMode === "managed") return payload;
243
+ if (model.api === "anthropic-messages") return addAnthropicHistoryBreakpoint(splitAnthropicSystem(payload, plan.policy.lifetime), plan.policy.lifetime);
244
+ if (model.api === "bedrock-converse-stream") return addBedrockHistoryBreakpoint(splitBedrockSystem(payload));
245
+ if (plan.providerMode === "explicit" && plan.cacheKey) return applyOpenAIExplicitCache(payload, context, plan.cacheKey);
246
+ const record = asRecord(payload);
247
+ return record && plan.cacheKey ? {
248
+ ...record,
249
+ prompt_cache_key: plan.cacheKey
250
+ } : payload;
81
251
  }
82
252
  function preparePromptCacheContext(model, context) {
83
253
  if (model.api === "anthropic-messages" || model.api === "bedrock-converse-stream") return context;
@@ -87,24 +257,27 @@ function preparePromptCacheContext(model, context) {
87
257
  systemPrompt: stripPromptCacheBoundary(context.systemPrompt)
88
258
  };
89
259
  }
90
- function withPromptCachePayloadTransform(model, context, options) {
91
- const explicitBoundary = model.api === "anthropic-messages" || model.api === "bedrock-converse-stream";
92
- const openAiCacheKey = supportsPromptCacheKey(model) && options?.cacheRetention !== "none";
93
- if (!explicitBoundary && !openAiCacheKey) return options;
260
+ function withPromptCachePayloadTransform(model, context, options, policyInput) {
261
+ const plan = buildPromptCachePlan(model, context, policyInput, options?.reasoning);
94
262
  const upstream = options?.onPayload;
95
- const promptCacheKey = openAiCacheKey ? buildPromptCacheKey(model, context) : void 0;
263
+ const cacheRetention = model.api === "anthropic-messages" || model.api === "bedrock-converse-stream" ? plan.providerMode === "none" ? "none" : plan.policy.lifetime : "none";
96
264
  return {
97
265
  ...options,
266
+ cacheRetention,
98
267
  onPayload: async (payload, payloadModel) => {
99
268
  const upstreamPayload = await upstream?.(payload, payloadModel);
100
- const transformed = transformPromptCachePayload(model, upstreamPayload === void 0 ? payload : upstreamPayload);
101
- const record = asRecord(transformed);
102
- return record && promptCacheKey ? {
103
- ...record,
104
- prompt_cache_key: promptCacheKey
105
- } : transformed;
269
+ const transformed = transformPromptCachePayload(model, upstreamPayload === void 0 ? payload : upstreamPayload, plan, context);
270
+ if (plan.providerMode !== "managed") return transformed;
271
+ return applyGoogleManagedPromptCache({
272
+ model,
273
+ context,
274
+ payload: transformed,
275
+ policy: plan.policy,
276
+ apiKey: options?.apiKey,
277
+ fetchImpl: options?.fetch
278
+ });
106
279
  }
107
280
  };
108
281
  }
109
282
  //#endregion
110
- export { buildPromptCacheKey, preparePromptCacheContext, transformPromptCachePayload, withPromptCachePayloadTransform };
283
+ export { buildPromptCacheKey, buildPromptCachePlan, preparePromptCacheContext, transformPromptCachePayload, withPromptCachePayloadTransform };
@@ -0,0 +1,15 @@
1
+ import type { Api, Model } from '@earendil-works/pi-ai';
2
+ export interface PromptCachePolicy {
3
+ mode: 'off' | 'auto';
4
+ lifetime: 'short' | 'long';
5
+ }
6
+ export type PromptCacheProviderMode = 'none' | 'implicit' | 'explicit' | 'managed';
7
+ export interface PromptCachePlan {
8
+ policy: PromptCachePolicy;
9
+ providerMode: PromptCacheProviderMode;
10
+ cacheKey?: string;
11
+ }
12
+ export declare const DEFAULT_PROMPT_CACHE_POLICY: PromptCachePolicy;
13
+ export declare function resolvePromptCachePolicy(policy?: Partial<PromptCachePolicy>): PromptCachePolicy;
14
+ export declare function isOfficialOpenAIModel(model: Model<Api>): boolean;
15
+ export declare function resolvePromptCacheProviderMode(model: Model<Api>, policy: PromptCachePolicy): PromptCacheProviderMode;
@@ -0,0 +1,29 @@
1
+ //#region src/providers/prompt-cache-plan.ts
2
+ const DEFAULT_PROMPT_CACHE_POLICY = {
3
+ mode: "auto",
4
+ lifetime: "short"
5
+ };
6
+ function resolvePromptCachePolicy(policy) {
7
+ return {
8
+ mode: policy?.mode ?? DEFAULT_PROMPT_CACHE_POLICY.mode,
9
+ lifetime: policy?.lifetime ?? DEFAULT_PROMPT_CACHE_POLICY.lifetime
10
+ };
11
+ }
12
+ function isOfficialOpenAIModel(model) {
13
+ if (model.api === "openai-codex-responses" || model.api === "azure-openai-responses") return true;
14
+ if (model.api !== "openai-completions" && model.api !== "openai-responses") return false;
15
+ return model.provider === "openai" || model.baseUrl?.includes("api.openai.com") === true;
16
+ }
17
+ function supportsExplicitOpenAICache(model) {
18
+ return model.api === "openai-responses" && isOfficialOpenAIModel(model) && /^gpt-5\.6(?:-|$)/.test(model.id);
19
+ }
20
+ function resolvePromptCacheProviderMode(model, policy) {
21
+ if (policy.mode === "off") return "none";
22
+ if (supportsExplicitOpenAICache(model)) return "explicit";
23
+ if (isOfficialOpenAIModel(model)) return "implicit";
24
+ if (model.api === "anthropic-messages" || model.api === "bedrock-converse-stream") return "explicit";
25
+ if (model.api === "google-generative-ai") return "managed";
26
+ return "none";
27
+ }
28
+ //#endregion
29
+ export { DEFAULT_PROMPT_CACHE_POLICY, isOfficialOpenAIModel, resolvePromptCachePolicy, resolvePromptCacheProviderMode };
@@ -241,7 +241,7 @@ var RealtimeRuntime = class {
241
241
  socket.close(4400, "Invalid endpoint message");
242
242
  }
243
243
  });
244
- socket.on("close", () => {
244
+ socket.on("close", (code, reasonBuffer) => {
245
245
  clearTimeout(helloTimer);
246
246
  clearInterval(heartbeatTimer);
247
247
  releaseBudget();
@@ -249,7 +249,14 @@ var RealtimeRuntime = class {
249
249
  for (const handle of subscriptions.values()) handle.unsubscribe();
250
250
  subscriptions.clear();
251
251
  if (endpointId && connectionId) this.endpointTools?.remove(endpointId, connectionId);
252
- if (connectionId) log.info({ connectionId }, "Realtime client disconnected");
252
+ if (connectionId) {
253
+ const reason = reasonBuffer.toString();
254
+ log.info({
255
+ connectionId,
256
+ code,
257
+ ...reason ? { reason } : {}
258
+ }, `Realtime client disconnected (${code}${reason ? `: ${reason}` : ""})`);
259
+ }
253
260
  });
254
261
  }
255
262
  rawDataToText(data) {
@@ -26,6 +26,11 @@ var RealtimeSocketWriter = class {
26
26
  data,
27
27
  bytes: Buffer.byteLength(data)
28
28
  };
29
+ if (item.bytes > MAX_QUEUED_BYTES) {
30
+ this.socket.close(1009, "Realtime event exceeds delivery limit");
31
+ this.close();
32
+ return false;
33
+ }
29
34
  if (this.queuedBytes + item.bytes > MAX_QUEUED_BYTES) {
30
35
  this.socket.close(4413, "Realtime client is too slow");
31
36
  this.close();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xopcai/xopc",
3
- "version": "0.0.202",
3
+ "version": "0.0.204",
4
4
  "description": "Local-first AI system that remembers context, coordinates AI, and sustains long-term progress.",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",
@@ -163,9 +163,9 @@
163
163
  "vitest": "4.1.5",
164
164
  "vue": "^3.5.35",
165
165
  "@xopcai/gateway-contract": "0.0.0",
166
- "@xopcai/realtime-client": "0.0.0",
166
+ "@xopcai/endpoint-tools-protocol": "0.0.0",
167
167
  "@xopcai/realtime-protocol": "0.0.0",
168
- "@xopcai/endpoint-tools-protocol": "0.0.0"
168
+ "@xopcai/realtime-client": "0.0.0"
169
169
  },
170
170
  "scripts": {
171
171
  "assets:brand": "node scripts/generate-brand-assets.mjs",