@code-yeongyu/senpi 2026.10.11 → 2026.10.12

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 (188) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/dist/bundle/chunks/anthropic-messages-GYG6YZY5.js +10 -0
  3. package/dist/bundle/chunks/{app-server-command-NHDOL4HQ.js → app-server-command-OL6PPUOA.js} +1 -1
  4. package/dist/bundle/chunks/{azure-openai-responses-EJF2XJ7Q.js → azure-openai-responses-GU6FOSJR.js} +1 -1
  5. package/dist/bundle/chunks/{chunk-ZABLYCHT.js → chunk-46JEQXD7.js} +1 -1
  6. package/dist/bundle/chunks/{chunk-VE3D74HN.js → chunk-55VKYA5V.js} +1 -1
  7. package/dist/bundle/chunks/{chunk-7UNN3YHW.js → chunk-5AVFHHDF.js} +230 -221
  8. package/dist/bundle/chunks/{chunk-2JE2DOIF.js → chunk-5DPTM5E2.js} +1 -1
  9. package/dist/bundle/chunks/chunk-5UPUSGP5.js +2 -0
  10. package/dist/bundle/chunks/chunk-5VMRMY6N.js +16 -0
  11. package/dist/bundle/chunks/{chunk-7X3VRAM3.js → chunk-7P3QPVTF.js} +1 -1
  12. package/dist/bundle/chunks/{chunk-T6IYKWIH.js → chunk-BUGZZVQW.js} +1 -1
  13. package/dist/bundle/chunks/chunk-DL7EFVT2.js +12 -0
  14. package/dist/bundle/chunks/chunk-E5YUVGLQ.js +11 -0
  15. package/dist/bundle/chunks/{chunk-RLFQICEY.js → chunk-EQJY2B5F.js} +1 -1
  16. package/dist/bundle/chunks/{chunk-VYKBN4MR.js → chunk-GC2RU5TS.js} +1 -1
  17. package/dist/bundle/chunks/chunk-GPKEJOTQ.js +202 -0
  18. package/dist/bundle/chunks/{chunk-KCVSSFOV.js → chunk-GQ5PWY6P.js} +4 -3
  19. package/dist/bundle/chunks/{chunk-4GPJOGXZ.js → chunk-I4AEC6MH.js} +1 -1
  20. package/dist/bundle/chunks/chunk-JXSFGRBE.js +8 -0
  21. package/dist/bundle/chunks/{chunk-YGCMN2CX.js → chunk-NAY5LB53.js} +1 -1
  22. package/dist/bundle/chunks/{chunk-EBOD66AS.js → chunk-PBN2CATL.js} +2 -2
  23. package/dist/bundle/chunks/chunk-PFNBY43W.js +2 -0
  24. package/dist/bundle/chunks/{chunk-CY2B4QRR.js → chunk-PUWEQK2W.js} +1 -1
  25. package/dist/bundle/chunks/{chunk-5DBPUQSX.js → chunk-SF6AUNQZ.js} +1 -1
  26. package/dist/bundle/chunks/{chunk-G2XAU4BX.js → chunk-TMTT5CLA.js} +1 -1
  27. package/dist/bundle/chunks/{chunk-2JKDZXIY.js → chunk-VWFKLW73.js} +2 -2
  28. package/dist/bundle/chunks/chunk-WZMSBRTW.js +2 -0
  29. package/dist/bundle/chunks/{chunk-55HLKZSB.js → chunk-XVJRRLGN.js} +1 -1
  30. package/dist/bundle/chunks/chunk-Z2PWWBBD.js +31 -0
  31. package/dist/bundle/chunks/cli-main-RSIYAR4J.js +3 -0
  32. package/dist/bundle/chunks/{cloudflare-workers-ai-system-one-SWVU2AXJ.js → cloudflare-workers-ai-system-one-LVWRKOUD.js} +1 -1
  33. package/dist/bundle/chunks/{google-generative-ai-XG4YX6FQ.js → google-generative-ai-JMGYPTXL.js} +1 -1
  34. package/dist/bundle/chunks/{google-vertex-BBOZ6OUB.js → google-vertex-CI4FPIU7.js} +1 -1
  35. package/dist/bundle/chunks/{help-fast-path-EZ3KEW6V.js → help-fast-path-M7KMBM6K.js} +1 -1
  36. package/dist/bundle/chunks/host-command-6UR3DSFR.js +2 -0
  37. package/dist/bundle/chunks/host-lifecycle-2BKCCK5X.js +3 -0
  38. package/dist/bundle/chunks/host-lifecycle.js +10 -7
  39. package/dist/bundle/chunks/host-session-command-A2MZHEBP.js +19 -0
  40. package/dist/bundle/chunks/interactive-mode-EJBMTJOH.js +2 -0
  41. package/dist/bundle/chunks/{list-tips-WE2YGQM6.js → list-tips-BEQAOA4N.js} +1 -1
  42. package/dist/bundle/chunks/{mistral-conversations-RB3ZBCGE.js → mistral-conversations-5OD7662T.js} +1 -1
  43. package/dist/bundle/chunks/{multi-session-host-V4CUQLV7.js → multi-session-host-YYMLHCUA.js} +4 -4
  44. package/dist/bundle/chunks/{openai-codex-responses-DBTPUXRW.js → openai-codex-responses-FN3UPAFI.js} +1 -1
  45. package/dist/bundle/chunks/{openai-completions-QAJQC4BY.js → openai-completions-KMUQ3T4M.js} +1 -1
  46. package/dist/bundle/chunks/{openai-responses-UBJWCEHG.js → openai-responses-V4Q5GVLO.js} +1 -1
  47. package/dist/bundle/chunks/{package-manager-cli-WTRDT4ZH.js → package-manager-cli-6A5ZP26E.js} +1 -1
  48. package/dist/bundle/chunks/rotation-stream-PT6LLBMJ.js +2 -0
  49. package/dist/bundle/chunks/rpc-mode-XNYJPYUM.js +2 -0
  50. package/dist/bundle/chunks/runtime-session-ZBHFMT65.js +2 -0
  51. package/dist/bundle/chunks/{schedule-command-PJVRKAKD.js → schedule-command-FYQG2MFX.js} +1 -1
  52. package/dist/bundle/chunks/session-control-endpoint-7TX4XHN4.js +3 -0
  53. package/dist/bundle/chunks/session-picker-4RNB27SM.js +2 -0
  54. package/dist/bundle/chunks/session-worker.js +418 -412
  55. package/dist/bundle/chunks/{typesafe-system-one-B5TW3FHM.js → typesafe-system-one-WUHW7HR4.js} +1 -1
  56. package/dist/bundle/cli.js +1 -1
  57. package/dist/bundle/index.js +1 -1
  58. package/dist/bundle/rpc-entry.js +1 -1
  59. package/dist/bundle/runtime-manifest.json +1 -1
  60. package/dist/core/agent-session.d.ts +11 -2
  61. package/dist/core/agent-session.js +152 -43
  62. package/dist/core/compaction/compaction.js +2 -0
  63. package/dist/core/extensions/builtin/anthropic-web-search/index.js +22 -14
  64. package/dist/core/extensions/builtin/cache-keepalive/index.js +8 -4
  65. package/dist/core/extensions/builtin/cache-keepalive/session-prewarm.d.ts +2 -1
  66. package/dist/core/extensions/builtin/cache-keepalive/session-prewarm.js +55 -7
  67. package/dist/core/extensions/builtin/cache-keepalive/startup-activity-gate.d.ts +8 -0
  68. package/dist/core/extensions/builtin/cache-keepalive/startup-activity-gate.js +25 -0
  69. package/dist/core/extensions/builtin/compaction/index.js +13 -5
  70. package/dist/core/extensions/builtin/compaction/speculative.d.ts +1 -0
  71. package/dist/core/extensions/builtin/compaction/speculative.js +8 -4
  72. package/dist/core/extensions/builtin/moved-path-guard/tool-classes.js +1 -0
  73. package/dist/core/extensions/builtin/openai-web-search/index.d.ts +4 -1
  74. package/dist/core/extensions/builtin/openai-web-search/index.js +54 -17
  75. package/dist/core/extensions/builtin/server-search-fallback.d.ts +12 -0
  76. package/dist/core/extensions/builtin/server-search-fallback.js +50 -0
  77. package/dist/core/extensions/builtin/terminal/runtime-session.d.ts +10 -1
  78. package/dist/core/extensions/builtin/terminal/runtime-session.js +48 -4
  79. package/dist/core/extensions/builtin/websearch/index.d.ts +1 -1
  80. package/dist/core/extensions/builtin/websearch/index.js +119 -18
  81. package/dist/core/extensions/builtin/websearch/server-search.d.ts +11 -0
  82. package/dist/core/extensions/builtin/websearch/server-search.js +230 -0
  83. package/dist/core/extensions/builtin/websearch/websearch/native.d.ts +2 -0
  84. package/dist/core/extensions/builtin/websearch/websearch/search-model.js +15 -0
  85. package/dist/core/extensions/runner.d.ts +8 -1
  86. package/dist/core/extensions/runner.js +56 -7
  87. package/dist/core/extensions/types.d.ts +12 -2
  88. package/dist/core/extensions/user-activity.d.ts +10 -0
  89. package/dist/core/extensions/user-activity.js +20 -0
  90. package/dist/core/model-config-schema.d.ts +34 -0
  91. package/dist/core/model-config-schema.js +2 -0
  92. package/dist/core/model-config.js +1 -0
  93. package/dist/core/reserved-keywords.d.ts +28 -0
  94. package/dist/core/reserved-keywords.js +81 -0
  95. package/dist/core/retry-fallback/candidates.d.ts +1 -1
  96. package/dist/core/retry-fallback/candidates.js +1 -12
  97. package/dist/core/retry-fallback/controller.d.ts +3 -1
  98. package/dist/core/retry-fallback/controller.js +11 -5
  99. package/dist/core/retry-fallback/cooldown.d.ts +2 -1
  100. package/dist/core/retry-fallback/cooldown.js +21 -12
  101. package/dist/core/retry-fallback/unusable.d.ts +2 -0
  102. package/dist/core/retry-fallback/unusable.js +14 -2
  103. package/dist/core/retry-fallback/usage-limit.d.ts +10 -1
  104. package/dist/core/retry-fallback/usage-limit.js +39 -2
  105. package/dist/core/session-title-generator.js +2 -0
  106. package/dist/index.d.ts +2 -1
  107. package/dist/index.js +1 -0
  108. package/dist/modes/index.d.ts +1 -1
  109. package/dist/modes/interactive/components/assistant-render-descriptors.js +7 -1
  110. package/dist/modes/interactive/composer-user-activity.d.ts +6 -0
  111. package/dist/modes/interactive/composer-user-activity.js +30 -0
  112. package/dist/modes/interactive/grok/chrome.js +2 -1
  113. package/dist/modes/interactive/interactive-mode.js +15 -6
  114. package/dist/modes/interactive/session-control-registry.js +8 -1
  115. package/dist/modes/interactive/theme/reserved-keyword-rainbow.d.ts +11 -0
  116. package/dist/modes/interactive/theme/reserved-keyword-rainbow.js +50 -0
  117. package/dist/modes/interactive/theme/theme.d.ts +3 -1
  118. package/dist/modes/interactive/theme/theme.js +9 -0
  119. package/dist/modes/print-mode.js +7 -2
  120. package/dist/modes/rpc/connection-handler.js +43 -11
  121. package/dist/modes/rpc/custom-capability.d.ts +2 -0
  122. package/dist/modes/rpc/custom-capability.js +2 -0
  123. package/dist/modes/rpc/host-admission-proxy.d.ts +9 -0
  124. package/dist/modes/rpc/host-admission-proxy.js +211 -0
  125. package/dist/modes/rpc/host-admission.d.ts +21 -0
  126. package/dist/modes/rpc/host-admission.js +59 -0
  127. package/dist/modes/rpc/host-capabilities.js +2 -1
  128. package/dist/modes/rpc/host-client-occupancy.d.ts +2 -1
  129. package/dist/modes/rpc/host-client-occupancy.js +7 -0
  130. package/dist/modes/rpc/host-decision.d.ts +2 -2
  131. package/dist/modes/rpc/host-decision.js +4 -0
  132. package/dist/modes/rpc/host-ensure-types.d.ts +2 -0
  133. package/dist/modes/rpc/host-ensure.js +31 -3
  134. package/dist/modes/rpc/host-lifecycle-drain.d.ts +2 -1
  135. package/dist/modes/rpc/host-lifecycle-drain.js +3 -1
  136. package/dist/modes/rpc/host-lifecycle-proxy.d.ts +3 -2
  137. package/dist/modes/rpc/host-lifecycle-proxy.js +10 -19
  138. package/dist/modes/rpc/host-lifecycle-shutdown.d.ts +2 -0
  139. package/dist/modes/rpc/host-lifecycle-shutdown.js +3 -1
  140. package/dist/modes/rpc/host-lifecycle-ticker.d.ts +11 -0
  141. package/dist/modes/rpc/host-lifecycle-ticker.js +27 -0
  142. package/dist/modes/rpc/host-lifecycle.js +15 -23
  143. package/dist/modes/rpc/host-outcome.js +1 -0
  144. package/dist/modes/rpc/host-protocol-info.d.ts +2 -0
  145. package/dist/modes/rpc/host-protocol-info.js +1 -0
  146. package/dist/modes/rpc/host-session-runner.js +22 -1
  147. package/dist/modes/rpc/host-status-all.js +1 -0
  148. package/dist/modes/rpc/host-status.d.ts +1 -0
  149. package/dist/modes/rpc/host-status.js +5 -1
  150. package/dist/modes/rpc/rpc-client.d.ts +7 -1
  151. package/dist/modes/rpc/rpc-client.js +12 -0
  152. package/dist/modes/rpc/rpc-types.d.ts +49 -1
  153. package/dist/modes/rpc/rpc-types.js +5 -0
  154. package/dist/modes/rpc/session-command-activity.js +2 -0
  155. package/dist/modes/rpc/session-held.js +3 -0
  156. package/docs/extensions.md +13 -1
  157. package/docs/providers.md +88 -0
  158. package/docs/rpc.md +150 -5
  159. package/docs/web-search.md +3 -1
  160. package/package.json +7 -7
  161. package/dist/bundle/chunks/anthropic-messages-5QQJVQPZ.js +0 -9
  162. package/dist/bundle/chunks/chunk-2A3MS2Z7.js +0 -8
  163. package/dist/bundle/chunks/chunk-6SBY4ZWC.js +0 -2
  164. package/dist/bundle/chunks/chunk-6WCOSBI7.js +0 -2
  165. package/dist/bundle/chunks/chunk-E75RO54O.js +0 -14
  166. package/dist/bundle/chunks/chunk-JMYO34RK.js +0 -2
  167. package/dist/bundle/chunks/chunk-KRNXUTZJ.js +0 -2
  168. package/dist/bundle/chunks/chunk-L4HVJWIF.js +0 -11
  169. package/dist/bundle/chunks/chunk-N5WLLHUE.js +0 -2
  170. package/dist/bundle/chunks/chunk-NEFNNQSV.js +0 -9
  171. package/dist/bundle/chunks/chunk-PWIYVLIA.js +0 -9
  172. package/dist/bundle/chunks/chunk-Q3R44CGT.js +0 -5
  173. package/dist/bundle/chunks/chunk-QIARZN7G.js +0 -31
  174. package/dist/bundle/chunks/chunk-QKU6JOA4.js +0 -2
  175. package/dist/bundle/chunks/chunk-QYWXDE47.js +0 -2
  176. package/dist/bundle/chunks/chunk-XHELSLN3.js +0 -188
  177. package/dist/bundle/chunks/chunk-YDJ4HGWO.js +0 -16
  178. package/dist/bundle/chunks/chunk-ZJHRDQCH.js +0 -2
  179. package/dist/bundle/chunks/cli-main-TUBNBZNK.js +0 -3
  180. package/dist/bundle/chunks/host-command-WO7VKJBJ.js +0 -2
  181. package/dist/bundle/chunks/host-lifecycle-X2J5FTJL.js +0 -3
  182. package/dist/bundle/chunks/host-session-command-ICBOH5RP.js +0 -19
  183. package/dist/bundle/chunks/interactive-mode-UGDNCGQ5.js +0 -2
  184. package/dist/bundle/chunks/rotation-stream-OODURUGZ.js +0 -2
  185. package/dist/bundle/chunks/rpc-mode-22MCT32O.js +0 -2
  186. package/dist/bundle/chunks/runtime-session-I4NDNGC5.js +0 -2
  187. package/dist/bundle/chunks/session-control-endpoint-SFPRQBDO.js +0 -3
  188. package/dist/bundle/chunks/session-picker-3ZQPV7Z3.js +0 -2
@@ -0,0 +1,230 @@
1
+ import { isAnthropicWebSearchEnabled, supportsNativeAnthropicWebSearch } from "../anthropic-web-search/index.js";
2
+ import { isOpenaiWebSearchEnabled, supportsNativeOpenAiWebSearch } from "../openai-web-search/index.js";
3
+ import { isServerSearchDisabled, isServerSearchFallback, SERVER_SEARCH_FALLBACK } from "../server-search-fallback.js";
4
+ function isRecord(value) {
5
+ return value !== null && typeof value === "object" && !Array.isArray(value);
6
+ }
7
+ export function additionalServerSearch(model) {
8
+ if (!model)
9
+ return undefined;
10
+ const declared = model.compat && "supportsWebSearch" in model.compat ? model.compat.supportsWebSearch : undefined;
11
+ if (declared !== true)
12
+ return undefined;
13
+ // Google Search grounding uses GenerateContent config.tools, not Messages/Responses.
14
+ // Gemini 2/3 document this native tool, but search mixed with function tools
15
+ // remains opt-in until a live endpoint probe succeeds.
16
+ // https://ai.google.dev/gemini-api/docs/google-search
17
+ // https://github.com/googleapis/js-genai/blob/main/codegen_instructions.md
18
+ if ((model.api === "google-generative-ai" || model.api === "google-vertex") &&
19
+ /^gemini-(?:2[.-]|3[.-]|flash-latest|pro-latest)/.test(model.id) &&
20
+ !/(image|live|tts|embedding|computer-use)/.test(model.id)) {
21
+ return "google";
22
+ }
23
+ // OpenRouter's own server tool works on Chat Completions and Responses.
24
+ // Do not translate it into the upstream vendor's tool shape.
25
+ // https://openrouter.ai/docs/guides/features/server-tools/web-search
26
+ if (model.provider === "openrouter" && (model.api === "openai-completions" || model.api === "openai-responses")) {
27
+ return "openrouter";
28
+ }
29
+ // PAYG Moonshot executes $web_search and returns its result in generated
30
+ // arguments; the client echoes those arguments as a tool result, without
31
+ // making another search request. This is NOT the Kimi coding-plan endpoint.
32
+ // https://platform.kimi.ai/docs/guide/use-web-search
33
+ if (model.api === "openai-completions" && (model.provider === "moonshotai" || model.provider === "moonshotai-cn")) {
34
+ return "moonshot";
35
+ }
36
+ // The same provider id can point at PAYG chat or the coding-plan endpoint.
37
+ // Only an explicit per-route flag enables the PAYG in-chat server tool.
38
+ // https://docs.z.ai/guides/tools/web-search
39
+ // https://docs.z.ai/devpack/mcp/search-mcp-server
40
+ if (model.api === "openai-completions" &&
41
+ (model.provider === "zai" || model.provider === "z-ai") &&
42
+ declared === true) {
43
+ return "zai";
44
+ }
45
+ // Sonar searches inside the main completion without a tools[] declaration.
46
+ // Synchronous Sonar calls are now reformulated as Agent API calls upstream.
47
+ // https://docs.perplexity.ai/docs/sonar/quickstart
48
+ if (model.provider === "perplexity" && model.api === "openai-completions" && /^sonar/.test(model.id)) {
49
+ return "perplexity";
50
+ }
51
+ // DeepSeek documents search on /anthropic, not the default Chat Completions API.
52
+ // An Anthropic-format model opts in through compat.supportsWebSearch, including replay.
53
+ // https://api-docs.deepseek.com/quick_start/agent_integrations/claude_code
54
+ // Kimi coding-plan docs do not specify a search/replay contract. Keep the explicit
55
+ // Messages compat opt-in; do not confuse it with PAYG's deprecated $web_search.
56
+ // https://www.kimi.com/code/docs/en/third-party-tools/claude-code
57
+ // https://platform.kimi.ai/docs/guide/use-web-search
58
+ // GLM coding-plan search is documented as webSearchPrime over remote MCP, not
59
+ // the PAYG chat tool. Do not inject the PAYG tool on the coding endpoint.
60
+ // https://docs.z.ai/devpack/mcp/search-mcp-server
61
+ // https://docs.z.ai/guides/tools/web-search
62
+ return undefined;
63
+ }
64
+ export function hasServerSearch(model) {
65
+ if (!model)
66
+ return false;
67
+ return ((supportsNativeAnthropicWebSearch(model) && isAnthropicWebSearchEnabled()) ||
68
+ (supportsNativeOpenAiWebSearch(model) && isOpenaiWebSearchEnabled()) ||
69
+ additionalServerSearch(model) !== undefined);
70
+ }
71
+ function isMoonshotSearchTool(tool) {
72
+ return (isRecord(tool) &&
73
+ (tool.name === "$web_search" || (isRecord(tool.function) && tool.function.name === "$web_search")));
74
+ }
75
+ function isClientSearchTool(tool) {
76
+ return (isRecord(tool) &&
77
+ (tool.type === "function" || "input_schema" in tool) &&
78
+ (tool.name === "web_search" || (isRecord(tool.function) && tool.function.name === "web_search")));
79
+ }
80
+ function withTools(payload, tools) {
81
+ const result = { ...payload };
82
+ if (tools.length)
83
+ result.tools = tools;
84
+ else
85
+ delete result.tools;
86
+ return result;
87
+ }
88
+ function isSearchTool(tool) {
89
+ if (!isRecord(tool))
90
+ return false;
91
+ return (tool.name === "web_search" ||
92
+ isMoonshotSearchTool(tool) ||
93
+ (isRecord(tool.function) && tool.function.name === "web_search") ||
94
+ (typeof tool.type === "string" &&
95
+ (tool.type === "openrouter:web_search" || tool.type === "web_search" || tool.type.startsWith("web_search_"))));
96
+ }
97
+ function googleTools(tools, retainFunction, disabled) {
98
+ const result = [];
99
+ for (const tool of tools) {
100
+ if (!isRecord(tool) || "googleSearch" in tool || "google_search" in tool)
101
+ continue;
102
+ if (!Array.isArray(tool.functionDeclarations)) {
103
+ result.push(tool);
104
+ continue;
105
+ }
106
+ const declarations = tool.functionDeclarations.filter((declaration) => !isRecord(declaration) || retainFunction || declaration.name !== "web_search");
107
+ if (declarations.length)
108
+ result.push({ ...tool, functionDeclarations: declarations });
109
+ }
110
+ if (!retainFunction && !disabled)
111
+ result.push({ googleSearch: {} });
112
+ return result;
113
+ }
114
+ /** Preserve adapter-built function search on fallback; never reconstruct its normalized schema. */
115
+ export function serverSearchPayload(model, payload, failed = false) {
116
+ if (!isRecord(payload))
117
+ return payload;
118
+ const disabled = isServerSearchDisabled(payload);
119
+ const fallback = failed || isServerSearchFallback(payload);
120
+ if (additionalServerSearch(model) !== "moonshot") {
121
+ if (isMoonshotSearchTool(payload.tool_choice)) {
122
+ const { tool_choice: _choice, ...rest } = payload;
123
+ payload = rest;
124
+ }
125
+ if (!isRecord(payload))
126
+ return payload;
127
+ if (Array.isArray(payload.tools)) {
128
+ const tools = payload.tools.filter((tool) => !isMoonshotSearchTool(tool));
129
+ if (tools.length !== payload.tools.length)
130
+ payload = withTools(payload, tools);
131
+ }
132
+ if (isRecord(payload) && isRecord(payload.config) && Array.isArray(payload.config.tools)) {
133
+ payload = {
134
+ ...payload,
135
+ config: withTools(payload.config, payload.config.tools.flatMap((tool) => {
136
+ if (!isRecord(tool) || !Array.isArray(tool.functionDeclarations))
137
+ return [tool];
138
+ const declarations = tool.functionDeclarations.filter((declaration) => !isMoonshotSearchTool(declaration));
139
+ return declarations.length ? [{ ...tool, functionDeclarations: declarations }] : [];
140
+ })),
141
+ };
142
+ }
143
+ }
144
+ if (!model || !isRecord(payload))
145
+ return payload;
146
+ const google = model.api === "google-generative-ai" || model.api === "google-vertex";
147
+ if (google && (additionalServerSearch(model) === "google" || fallback || disabled)) {
148
+ const config = isRecord(payload.config) ? payload.config : {};
149
+ const tools = Array.isArray(config.tools) ? config.tools : [];
150
+ return {
151
+ ...payload,
152
+ config: withTools(config, googleTools(tools, fallback || disabled, disabled)),
153
+ ...(fallback ? { [SERVER_SEARCH_FALLBACK]: true } : {}),
154
+ };
155
+ }
156
+ const kind = additionalServerSearch(model);
157
+ if (!fallback && !kind && !disabled)
158
+ return payload;
159
+ const tools = (Array.isArray(payload.tools) ? payload.tools : []).filter((tool) => {
160
+ if (!isSearchTool(tool))
161
+ return true;
162
+ if (!disabled && !fallback)
163
+ return false;
164
+ // Responses/Completions use type:function; Messages uses input_schema.
165
+ // These declarations already came through their provider's schema builder.
166
+ return isClientSearchTool(tool);
167
+ });
168
+ if (!fallback && !disabled && kind === "openrouter") {
169
+ tools.push({ type: "openrouter:web_search" });
170
+ }
171
+ else if (!fallback && !disabled && kind === "moonshot") {
172
+ tools.push({ type: "builtin_function", function: { name: "$web_search" } });
173
+ }
174
+ else if (!fallback && !disabled && kind === "zai") {
175
+ tools.push({ type: "web_search", web_search: { enable: true, search_result: true } });
176
+ }
177
+ const result = withTools(payload, tools);
178
+ if (fallback || disabled) {
179
+ // An include or tool_choice targeting the removed server tool would reject the retry.
180
+ if (Array.isArray(result.include)) {
181
+ result.include = result.include.filter((item) => item !== "web_search_call.action.sources");
182
+ }
183
+ if (isRecord(result.tool_choice) &&
184
+ isSearchTool(result.tool_choice) &&
185
+ !isClientSearchTool(result.tool_choice) &&
186
+ !(result.tool_choice.name === "web_search" && tools.some(isClientSearchTool)))
187
+ delete result.tool_choice;
188
+ }
189
+ return fallback ? { ...result, [SERVER_SEARCH_FALLBACK]: true } : result;
190
+ }
191
+ /** Only search-specific errors trigger fallback; cancellation and unrelated provider failures do not. */
192
+ export function serverSearchFailure(message, model) {
193
+ if (message.stopReason === "aborted")
194
+ return undefined;
195
+ // Sonar has inherent retrieval, not an independent server tool declaration or
196
+ // tool-result error contract. Its model errors stay with core retry; never
197
+ // turn an arbitrary Sonar error into an unsupported function-tool request.
198
+ if (model?.provider === "perplexity" && model.api === "openai-completions" && /^sonar/.test(model.id))
199
+ return undefined;
200
+ for (const block of message.content) {
201
+ if (block.type !== "providerNative" || !isRecord(block.raw))
202
+ continue;
203
+ const raw = block.raw;
204
+ if (raw.type === "web_search_call" && raw.status === "failed") {
205
+ return "server search result: failed";
206
+ }
207
+ if (raw.type !== "web_search_tool_result" || !isRecord(raw.content))
208
+ continue;
209
+ if (raw.content.type === "web_search_tool_result_error" && typeof raw.content.error_code === "string") {
210
+ return `server search result: ${raw.content.error_code.replace(/[^a-z_]/g, "").slice(0, 60)}`;
211
+ }
212
+ }
213
+ if (message.stopReason === "error" &&
214
+ /(\b401\b|invalid[_ -]?(?:api[_ -]?)?key|authentication|unauthorized|quota|billing|insufficient)/i.test(message.errorMessage ?? "")) {
215
+ return undefined;
216
+ }
217
+ if (message.stopReason === "error" &&
218
+ model !== undefined &&
219
+ (model.api === "google-generative-ai" || model.api === "google-vertex") &&
220
+ /(multiple tools|combining.*tools|tools.*combination|tool use with function calling is unsupported)/i.test(message.errorMessage ?? "")) {
221
+ return "server search request rejected (mixed tools unsupported)";
222
+ }
223
+ if (message.stopReason === "error" &&
224
+ /(web_search_\d{8}|web_search_preview|openrouter:web_search|googleSearch|google_search|(?:tool(?:[_ -]?type)?|type)\s*[:=]?\s*["']?web_search\b)/i.test(message.errorMessage ?? "")) {
225
+ const status = message.errorMessage?.match(/\b(400|403|429|500|502|503)\b/)?.[1];
226
+ return `server search request rejected${status ? ` (HTTP ${status})` : ""}`;
227
+ }
228
+ return undefined;
229
+ }
230
+ //# sourceMappingURL=server-search.js.map
@@ -1,9 +1,11 @@
1
+ import type { Api, Model } from "@earendil-works/pi-ai";
1
2
  import type { SearchProvider, SearchProviderEntry } from "./types.ts";
2
3
  export interface NativeModelInfo {
3
4
  provider: string;
4
5
  id: string;
5
6
  baseUrl: string;
6
7
  api?: string;
8
+ compat?: Model<Api>["compat"];
7
9
  cost?: {
8
10
  input: number;
9
11
  output: number;
@@ -1,3 +1,4 @@
1
+ import { hasServerSearch } from "../server-search.js";
1
2
  import { nativeMapping, nativeRouteKey } from "./native.js";
2
3
  export const SESSION_SEARCH_MODEL = "session";
3
4
  // Cheaper same-provider models for hosted search, after oh-my-pi's per-provider `web-search-model` catalog axis.
@@ -40,6 +41,20 @@ function defaultChoice(session, candidates) {
40
41
  export function resolveNativeSearchModel(session, registry, setting) {
41
42
  if (!session || !nativeRouteKey(session))
42
43
  return undefined;
44
+ // Main-request server search is always the active model's job. If that fails,
45
+ // the standalone retry also keeps that model; only no-server routes default
46
+ // to the cheaper auxiliary model below. An auxiliary setting cannot preempt
47
+ // the in-turn tool. See senpi#3122.
48
+ if (session.api !== undefined &&
49
+ hasServerSearch({
50
+ api: session.api,
51
+ provider: session.provider,
52
+ id: session.id,
53
+ baseUrl: session.baseUrl,
54
+ compat: session.compat,
55
+ })) {
56
+ return { model: session.id, source: "session" };
57
+ }
43
58
  if (setting === SESSION_SEARCH_MODEL)
44
59
  return { model: session.id, source: "session" };
45
60
  const candidates = sameRouteModels(session, registry);
@@ -14,6 +14,9 @@ import type { SessionManager } from "../session-manager.ts";
14
14
  import type { BuildSystemPromptOptions } from "../system-prompt.ts";
15
15
  import type { VirtualModelDefinition } from "../virtual-models.ts";
16
16
  import type { AgentBeforeSettleEvent, BeforeAgentStartEvent, BeforeAgentStartEventResult, BeforeProviderHeadersEvent, BeforeProviderRequestEvent, BoundaryContextPreview, ContextEvent, ContextWithSystemEvent, EntryRenderer, EntryRendererOptions, Extension, ExtensionActions, ExtensionCommandContext, ExtensionCommandContextActions, ExtensionContext, ExtensionContextActions, ExtensionError, ExtensionEvent, ExtensionFlag, ExtensionMode, ExtensionRuntime, ExtensionShortcut, ExtensionToolContext, ExtensionUIContext, InputEvent, InputEventResult, InputSource, LoadExtensionsResult, MarkdownTransformer, MessageEndEvent, MessageRenderer, ModelSelectEvent, ModelSelectEventResult, ProjectTrustContext, ProjectTrustEvent, ProjectTrustEventResult, ProviderConfig, ProviderRequestPreparation, RegisteredMcpServerDeclaration, RegisteredTool, ReplacedSessionContext, ResolvedCommand, ResourcesDiscoverEvent, SessionBeforeCompactResult, SessionBeforeForkResult, SessionBeforeReloadResult, SessionBeforeSwitchResult, SessionBeforeTreeResult, SessionBoundaryDraft, SessionShutdownEvent, ToolCallEvent, ToolCallEventResult, ToolResultEvent, ToolResultEventResult, TurnEndEvent, UserBashEvent, UserBashEventResult } from "./types.ts";
17
+ export declare class ExtensionRpcRequestNotFoundError extends Error {
18
+ constructor(name: string);
19
+ }
17
20
  /** Combined result from all before_agent_start handlers */
18
21
  interface BeforeAgentStartCombinedResult {
19
22
  messages?: NonNullable<BeforeAgentStartEventResult["message"]>[];
@@ -132,6 +135,7 @@ export declare class ExtensionRunner {
132
135
  private isCompactingFn;
133
136
  private checkReloadVetoFn;
134
137
  private getContextUsageFn;
138
+ private getCompactionModelFn;
135
139
  private getCompactionSettingsFn;
136
140
  private getPromptCacheSafeWaitSecondsFn;
137
141
  private getPromptCacheGoalBackstopMaxSecondsFn;
@@ -151,6 +155,7 @@ export declare class ExtensionRunner {
151
155
  private getLoadedHookSourcesFn;
152
156
  private getSystemPromptOptionsFn;
153
157
  private getPromptCachePrefixRequestFn;
158
+ private onUserActivityFn;
154
159
  private getAgentDirFn;
155
160
  private executeToolFn;
156
161
  private getCallableToolsFn;
@@ -289,7 +294,9 @@ export declare class ExtensionRunner {
289
294
  * handlers then see the full transcript and their output is used as returned.
290
295
  */
291
296
  emitContext(messages: AgentMessage[], excludeExtensionPath?: string, source?: ContextEvent["source"]): Promise<AgentMessage[]>;
292
- prepareProviderRequest(messages: AgentMessage[], excludeExtensionPath?: string): Promise<ProviderRequestPreparation>;
297
+ prepareProviderRequest(messages: AgentMessage[], excludeExtensionPath?: string, options?: {
298
+ allowServerSearch?: boolean;
299
+ }): Promise<ProviderRequestPreparation>;
293
300
  emitBeforeProviderRequest(payload: unknown, excludeExtensionPath?: string, request?: {
294
301
  model: Model<Api>;
295
302
  headers: ProviderHeaders;
@@ -9,11 +9,18 @@ import { createEventBus, EXTENSION_RPC_EVENT_CHANNEL } from "../event-bus.js";
9
9
  import { DEFAULT_SESSION_SHUTDOWN_HANDLER_TIMEOUT_MS, DEFAULT_SESSION_SHUTDOWN_HANDLER_WARN_MS, SettingsManager, } from "../settings-manager.js";
10
10
  import { goalFilePath } from "./builtin/goal/persistence.js";
11
11
  import { goalStoreRef } from "./builtin/goal/store-ref.js";
12
+ import { disableServerSearch } from "./builtin/server-search-fallback.js";
12
13
  import { dispatchContextMessages } from "./context-dispatch.js";
13
14
  import { kernelToolsStorage } from "./kernel-tools-context.js";
14
15
  import { drainPendingProviderRegistrations } from "./loader.js";
15
16
  import { SessionStartTurnGate } from "./session-start-turn-gate.js";
16
17
  import { RUNTIME_EXTENSION_PATH } from "./types.js";
18
+ export class ExtensionRpcRequestNotFoundError extends Error {
19
+ constructor(name) {
20
+ super(`Unknown extension RPC request: ${name}`);
21
+ this.name = "ExtensionRpcRequestNotFoundError";
22
+ }
23
+ }
17
24
  // Extension shortcuts compete with canonical keybinding ids from keybindings.json.
18
25
  // Only editor-global shortcuts are reserved here. Picker-specific bindings are not.
19
26
  const RESERVED_KEYBINDINGS_FOR_EXTENSION_CONFLICTS = [
@@ -323,6 +330,7 @@ export class ExtensionRunner {
323
330
  this.shutdownHandler = contextActions.shutdown;
324
331
  this.getContextUsageFn = contextActions.getContextUsage;
325
332
  this.getCompactionSettingsFn = contextActions.getCompactionSettings;
333
+ this.getCompactionModelFn = contextActions.getCompactionModel;
326
334
  if (contextActions.getPromptCacheSafeWaitSeconds)
327
335
  this.getPromptCacheSafeWaitSecondsFn = contextActions.getPromptCacheSafeWaitSeconds;
328
336
  if (contextActions.getPromptCacheGoalBackstopMaxSeconds)
@@ -348,6 +356,7 @@ export class ExtensionRunner {
348
356
  this.getAgentDirFn = contextActions.getAgentDir;
349
357
  this.getSystemPromptOptionsFn = contextActions.getSystemPromptOptions ?? (() => ({ cwd: this.cwd }));
350
358
  this.getPromptCachePrefixRequestFn = contextActions.getPromptCachePrefixRequest;
359
+ this.onUserActivityFn = contextActions.onUserActivity;
351
360
  this.executeToolFn = contextActions.executeTool;
352
361
  this.getCallableToolsFn = contextActions.getCallableTools ?? (() => []);
353
362
  for (const extension of this.extensions) {
@@ -826,7 +835,7 @@ export class ExtensionRunner {
826
835
  return handler === undefined ? [] : [handler];
827
836
  });
828
837
  if (matches.length === 0) {
829
- throw new Error(`Unknown extension RPC request: ${normalizedName}`);
838
+ throw new ExtensionRpcRequestNotFoundError(normalizedName);
830
839
  }
831
840
  if (matches.length > 1) {
832
841
  throw new Error(`Multiple extension RPC request handlers registered: ${normalizedName}`);
@@ -971,6 +980,15 @@ export class ExtensionRunner {
971
980
  runner.assertActive();
972
981
  return runner.getCompactionSettingsFn();
973
982
  },
983
+ get getCompactionModel() {
984
+ runner.assertActive();
985
+ return runner.getCompactionModelFn
986
+ ? () => {
987
+ runner.assertActive();
988
+ return runner.getCompactionModelFn?.();
989
+ }
990
+ : undefined;
991
+ },
974
992
  getPromptCacheSafeWaitSeconds: () => {
975
993
  runner.assertActive();
976
994
  return runner.getPromptCacheSafeWaitSecondsFn();
@@ -983,6 +1001,22 @@ export class ExtensionRunner {
983
1001
  runner.assertActive();
984
1002
  return runner.getPromptCacheKeepAliveSettingsFn();
985
1003
  },
1004
+ onUserActivity: (listener) => {
1005
+ runner.assertActive();
1006
+ const unsubscribe = runner.onUserActivityFn?.((kind) => {
1007
+ try {
1008
+ listener(kind);
1009
+ }
1010
+ catch (error) {
1011
+ runner.emitError({
1012
+ extensionPath: excludeBeforeProviderRequestExtensionPath ?? RUNTIME_EXTENSION_PATH,
1013
+ event: "user_activity",
1014
+ error: error instanceof Error ? error.message : String(error),
1015
+ });
1016
+ }
1017
+ });
1018
+ return runner.runtime.trackEventBusSubscription(unsubscribe ?? (() => { }));
1019
+ },
986
1020
  getLookAtSettings: () => {
987
1021
  runner.assertActive();
988
1022
  return runner.getLookAtSettingsFn();
@@ -1003,9 +1037,9 @@ export class ExtensionRunner {
1003
1037
  runner.assertActive();
1004
1038
  runner.compactFn(options);
1005
1039
  },
1006
- prepareProviderRequest: async (messages) => {
1040
+ prepareProviderRequest: async (messages, options) => {
1007
1041
  runner.assertActive();
1008
- return runner.prepareProviderRequest(messages, excludeBeforeProviderRequestExtensionPath);
1042
+ return runner.prepareProviderRequest(messages, excludeBeforeProviderRequestExtensionPath, options);
1009
1043
  },
1010
1044
  getPromptCachePrefixRequest: async (options) => {
1011
1045
  runner.assertActive();
@@ -1133,9 +1167,24 @@ export class ExtensionRunner {
1133
1167
  const ctx = this.createContext();
1134
1168
  let entries = [];
1135
1169
  let shouldContinue = false;
1136
- let context = await buildContext(entries);
1137
- let valid = true;
1170
+ // Reported only when the runner retires before its first preview build completes.
1171
+ let context = {
1172
+ contextEntries: [],
1173
+ contextMessages: [],
1174
+ llmMessages: [],
1175
+ pendingMessages: [],
1176
+ canContinue: false,
1177
+ };
1138
1178
  const retired = () => ({ entries: [], continue: false, context, valid: false });
1179
+ try {
1180
+ context = await buildContext(entries);
1181
+ }
1182
+ catch (err) {
1183
+ if (!this.isActive)
1184
+ return retired();
1185
+ throw err;
1186
+ }
1187
+ let valid = true;
1139
1188
  for (const { ext, handlers } of snapshotEventHandlers(this.extensions, baseEvent.type)) {
1140
1189
  for (const handler of handlers) {
1141
1190
  if (!this.isActive)
@@ -1546,11 +1595,11 @@ export class ExtensionRunner {
1546
1595
  source,
1547
1596
  });
1548
1597
  }
1549
- async prepareProviderRequest(messages, excludeExtensionPath) {
1598
+ async prepareProviderRequest(messages, excludeExtensionPath, options) {
1550
1599
  const transformedMessages = await this.emitContext(messages, excludeExtensionPath, "projection");
1551
1600
  return {
1552
1601
  messages: transformedMessages,
1553
- transformPayload: async (payload) => await this.emitBeforeProviderRequest(payload, excludeExtensionPath),
1602
+ transformPayload: async (payload) => await this.emitBeforeProviderRequest(options?.allowServerSearch ? payload : disableServerSearch(payload), excludeExtensionPath),
1554
1603
  transformHeaders: async (headers) => await this.emitBeforeProviderHeaders(headers),
1555
1604
  };
1556
1605
  }
@@ -40,6 +40,7 @@ import type { McpServerDeclaration } from "./builtin/mcp/config-schema.ts";
40
40
  import type { EvalHandleHost } from "./eval-handle-host.ts";
41
41
  import type { ExtensionKernelTools } from "./kernel-tools-context.ts";
42
42
  import type { SessionControlActions, SessionControlWakeEvent } from "./session-control-types.ts";
43
+ import type { UserActivityKind } from "./user-activity.ts";
43
44
  export type { ExecOptions, ExecResult } from "../exec.ts";
44
45
  export type { AppKeybinding, KeybindingsManager } from "../keybindings.ts";
45
46
  export type { BuildSystemPromptOptions, NormalizedBuildSystemPromptOptions } from "../system-prompt.ts";
@@ -432,6 +433,8 @@ export interface ExtensionContext {
432
433
  getContextUsage(): ContextUsage | undefined;
433
434
  /** Get resolved compaction settings from global/project/user overrides. */
434
435
  getCompactionSettings(): CompactionPreparation["settings"];
436
+ /** Session-selected usable summarization model; throws when every authenticated account is spent. */
437
+ getCompactionModel?(): Model<Api> | undefined;
435
438
  /**
436
439
  * Longest a tool may block in the foreground before the active model's prompt
437
440
  * cache expires, or `undefined` when no cache-derived budget applies. Reads the
@@ -440,6 +443,8 @@ export interface ExtensionContext {
440
443
  getPromptCacheSafeWaitSeconds?(): number | undefined;
441
444
  /** Maximum Goal monitor continuation backstop configured for prompt-cache waits. */
442
445
  getPromptCacheGoalBackstopMaxSeconds?(): number;
446
+ /** Subscribe to host-authenticated activity; extensions cannot publish it. */
447
+ onUserActivity?(listener: (kind: UserActivityKind) => void): () => void;
443
448
  /** Resolved opt-in prompt-cache keep-alive policy. */
444
449
  getPromptCacheKeepAliveSettings?(): {
445
450
  enabled: boolean;
@@ -468,9 +473,12 @@ export interface ExtensionContext {
468
473
  compact(options?: CompactOptions): void;
469
474
  /**
470
475
  * Prepare a request-local provider context through the normal extension
471
- * boundary. Persisted session messages are never modified.
476
+ * boundary. Persisted session messages are never modified. Server search is
477
+ * disabled for these auxiliary requests unless preserving a keepalive prefix.
472
478
  */
473
- prepareProviderRequest?(messages: AgentMessage[]): Promise<ProviderRequestPreparation>;
479
+ prepareProviderRequest?(messages: AgentMessage[], options?: {
480
+ allowServerSearch?: boolean;
481
+ }): Promise<ProviderRequestPreparation>;
474
482
  /**
475
483
  * The provider request prefix the next user turn will send, with an empty conversation:
476
484
  * the system prompt composed through a `before_agent_start` preview pass, the session's
@@ -2379,8 +2387,10 @@ export interface ExtensionContextActions {
2379
2387
  shutdown: () => void;
2380
2388
  getContextUsage: () => ContextUsage | undefined;
2381
2389
  getCompactionSettings: () => CompactionPreparation["settings"];
2390
+ getCompactionModel?: () => Model<Api> | undefined;
2382
2391
  getPromptCacheSafeWaitSeconds?: () => number | undefined;
2383
2392
  getPromptCacheGoalBackstopMaxSeconds?: () => number;
2393
+ onUserActivity?: (listener: (kind: UserActivityKind) => void) => () => void;
2384
2394
  getPromptCacheKeepAliveSettings?: () => {
2385
2395
  enabled: boolean;
2386
2396
  maxRequestsPerSession: number;
@@ -0,0 +1,10 @@
1
+ export type UserActivityKind = "draft" | "prompt";
2
+ /** Only the owning AgentSession receives the emitter; extensions can only subscribe. */
3
+ export declare function createUserActivitySource(): {
4
+ emit(kind: UserActivityKind): void;
5
+ subscribe(listener: (kind: UserActivityKind) => void): () => void;
6
+ };
7
+ export declare function emitUserActivity(session: {
8
+ recordUserActivity?(kind: UserActivityKind): void;
9
+ } | undefined, kind: UserActivityKind): void;
10
+ //# sourceMappingURL=user-activity.d.ts.map
@@ -0,0 +1,20 @@
1
+ /** Only the owning AgentSession receives the emitter; extensions can only subscribe. */
2
+ export function createUserActivitySource() {
3
+ const listeners = new Set();
4
+ return {
5
+ emit(kind) {
6
+ for (const listener of listeners)
7
+ listener(kind);
8
+ },
9
+ subscribe(listener) {
10
+ listeners.add(listener);
11
+ return () => {
12
+ listeners.delete(listener);
13
+ };
14
+ },
15
+ };
16
+ }
17
+ export function emitUserActivity(session, kind) {
18
+ session?.recordUserActivity?.(kind);
19
+ }
20
+ //# sourceMappingURL=user-activity.js.map