@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.
- package/CHANGELOG.md +46 -0
- package/dist/bundle/chunks/anthropic-messages-GYG6YZY5.js +10 -0
- package/dist/bundle/chunks/{app-server-command-NHDOL4HQ.js → app-server-command-OL6PPUOA.js} +1 -1
- package/dist/bundle/chunks/{azure-openai-responses-EJF2XJ7Q.js → azure-openai-responses-GU6FOSJR.js} +1 -1
- package/dist/bundle/chunks/{chunk-ZABLYCHT.js → chunk-46JEQXD7.js} +1 -1
- package/dist/bundle/chunks/{chunk-VE3D74HN.js → chunk-55VKYA5V.js} +1 -1
- package/dist/bundle/chunks/{chunk-7UNN3YHW.js → chunk-5AVFHHDF.js} +230 -221
- package/dist/bundle/chunks/{chunk-2JE2DOIF.js → chunk-5DPTM5E2.js} +1 -1
- package/dist/bundle/chunks/chunk-5UPUSGP5.js +2 -0
- package/dist/bundle/chunks/chunk-5VMRMY6N.js +16 -0
- package/dist/bundle/chunks/{chunk-7X3VRAM3.js → chunk-7P3QPVTF.js} +1 -1
- package/dist/bundle/chunks/{chunk-T6IYKWIH.js → chunk-BUGZZVQW.js} +1 -1
- package/dist/bundle/chunks/chunk-DL7EFVT2.js +12 -0
- package/dist/bundle/chunks/chunk-E5YUVGLQ.js +11 -0
- package/dist/bundle/chunks/{chunk-RLFQICEY.js → chunk-EQJY2B5F.js} +1 -1
- package/dist/bundle/chunks/{chunk-VYKBN4MR.js → chunk-GC2RU5TS.js} +1 -1
- package/dist/bundle/chunks/chunk-GPKEJOTQ.js +202 -0
- package/dist/bundle/chunks/{chunk-KCVSSFOV.js → chunk-GQ5PWY6P.js} +4 -3
- package/dist/bundle/chunks/{chunk-4GPJOGXZ.js → chunk-I4AEC6MH.js} +1 -1
- package/dist/bundle/chunks/chunk-JXSFGRBE.js +8 -0
- package/dist/bundle/chunks/{chunk-YGCMN2CX.js → chunk-NAY5LB53.js} +1 -1
- package/dist/bundle/chunks/{chunk-EBOD66AS.js → chunk-PBN2CATL.js} +2 -2
- package/dist/bundle/chunks/chunk-PFNBY43W.js +2 -0
- package/dist/bundle/chunks/{chunk-CY2B4QRR.js → chunk-PUWEQK2W.js} +1 -1
- package/dist/bundle/chunks/{chunk-5DBPUQSX.js → chunk-SF6AUNQZ.js} +1 -1
- package/dist/bundle/chunks/{chunk-G2XAU4BX.js → chunk-TMTT5CLA.js} +1 -1
- package/dist/bundle/chunks/{chunk-2JKDZXIY.js → chunk-VWFKLW73.js} +2 -2
- package/dist/bundle/chunks/chunk-WZMSBRTW.js +2 -0
- package/dist/bundle/chunks/{chunk-55HLKZSB.js → chunk-XVJRRLGN.js} +1 -1
- package/dist/bundle/chunks/chunk-Z2PWWBBD.js +31 -0
- package/dist/bundle/chunks/cli-main-RSIYAR4J.js +3 -0
- package/dist/bundle/chunks/{cloudflare-workers-ai-system-one-SWVU2AXJ.js → cloudflare-workers-ai-system-one-LVWRKOUD.js} +1 -1
- package/dist/bundle/chunks/{google-generative-ai-XG4YX6FQ.js → google-generative-ai-JMGYPTXL.js} +1 -1
- package/dist/bundle/chunks/{google-vertex-BBOZ6OUB.js → google-vertex-CI4FPIU7.js} +1 -1
- package/dist/bundle/chunks/{help-fast-path-EZ3KEW6V.js → help-fast-path-M7KMBM6K.js} +1 -1
- package/dist/bundle/chunks/host-command-6UR3DSFR.js +2 -0
- package/dist/bundle/chunks/host-lifecycle-2BKCCK5X.js +3 -0
- package/dist/bundle/chunks/host-lifecycle.js +10 -7
- package/dist/bundle/chunks/host-session-command-A2MZHEBP.js +19 -0
- package/dist/bundle/chunks/interactive-mode-EJBMTJOH.js +2 -0
- package/dist/bundle/chunks/{list-tips-WE2YGQM6.js → list-tips-BEQAOA4N.js} +1 -1
- package/dist/bundle/chunks/{mistral-conversations-RB3ZBCGE.js → mistral-conversations-5OD7662T.js} +1 -1
- package/dist/bundle/chunks/{multi-session-host-V4CUQLV7.js → multi-session-host-YYMLHCUA.js} +4 -4
- package/dist/bundle/chunks/{openai-codex-responses-DBTPUXRW.js → openai-codex-responses-FN3UPAFI.js} +1 -1
- package/dist/bundle/chunks/{openai-completions-QAJQC4BY.js → openai-completions-KMUQ3T4M.js} +1 -1
- package/dist/bundle/chunks/{openai-responses-UBJWCEHG.js → openai-responses-V4Q5GVLO.js} +1 -1
- package/dist/bundle/chunks/{package-manager-cli-WTRDT4ZH.js → package-manager-cli-6A5ZP26E.js} +1 -1
- package/dist/bundle/chunks/rotation-stream-PT6LLBMJ.js +2 -0
- package/dist/bundle/chunks/rpc-mode-XNYJPYUM.js +2 -0
- package/dist/bundle/chunks/runtime-session-ZBHFMT65.js +2 -0
- package/dist/bundle/chunks/{schedule-command-PJVRKAKD.js → schedule-command-FYQG2MFX.js} +1 -1
- package/dist/bundle/chunks/session-control-endpoint-7TX4XHN4.js +3 -0
- package/dist/bundle/chunks/session-picker-4RNB27SM.js +2 -0
- package/dist/bundle/chunks/session-worker.js +418 -412
- package/dist/bundle/chunks/{typesafe-system-one-B5TW3FHM.js → typesafe-system-one-WUHW7HR4.js} +1 -1
- package/dist/bundle/cli.js +1 -1
- package/dist/bundle/index.js +1 -1
- package/dist/bundle/rpc-entry.js +1 -1
- package/dist/bundle/runtime-manifest.json +1 -1
- package/dist/core/agent-session.d.ts +11 -2
- package/dist/core/agent-session.js +152 -43
- package/dist/core/compaction/compaction.js +2 -0
- package/dist/core/extensions/builtin/anthropic-web-search/index.js +22 -14
- package/dist/core/extensions/builtin/cache-keepalive/index.js +8 -4
- package/dist/core/extensions/builtin/cache-keepalive/session-prewarm.d.ts +2 -1
- package/dist/core/extensions/builtin/cache-keepalive/session-prewarm.js +55 -7
- package/dist/core/extensions/builtin/cache-keepalive/startup-activity-gate.d.ts +8 -0
- package/dist/core/extensions/builtin/cache-keepalive/startup-activity-gate.js +25 -0
- package/dist/core/extensions/builtin/compaction/index.js +13 -5
- package/dist/core/extensions/builtin/compaction/speculative.d.ts +1 -0
- package/dist/core/extensions/builtin/compaction/speculative.js +8 -4
- package/dist/core/extensions/builtin/moved-path-guard/tool-classes.js +1 -0
- package/dist/core/extensions/builtin/openai-web-search/index.d.ts +4 -1
- package/dist/core/extensions/builtin/openai-web-search/index.js +54 -17
- package/dist/core/extensions/builtin/server-search-fallback.d.ts +12 -0
- package/dist/core/extensions/builtin/server-search-fallback.js +50 -0
- package/dist/core/extensions/builtin/terminal/runtime-session.d.ts +10 -1
- package/dist/core/extensions/builtin/terminal/runtime-session.js +48 -4
- package/dist/core/extensions/builtin/websearch/index.d.ts +1 -1
- package/dist/core/extensions/builtin/websearch/index.js +119 -18
- package/dist/core/extensions/builtin/websearch/server-search.d.ts +11 -0
- package/dist/core/extensions/builtin/websearch/server-search.js +230 -0
- package/dist/core/extensions/builtin/websearch/websearch/native.d.ts +2 -0
- package/dist/core/extensions/builtin/websearch/websearch/search-model.js +15 -0
- package/dist/core/extensions/runner.d.ts +8 -1
- package/dist/core/extensions/runner.js +56 -7
- package/dist/core/extensions/types.d.ts +12 -2
- package/dist/core/extensions/user-activity.d.ts +10 -0
- package/dist/core/extensions/user-activity.js +20 -0
- package/dist/core/model-config-schema.d.ts +34 -0
- package/dist/core/model-config-schema.js +2 -0
- package/dist/core/model-config.js +1 -0
- package/dist/core/reserved-keywords.d.ts +28 -0
- package/dist/core/reserved-keywords.js +81 -0
- package/dist/core/retry-fallback/candidates.d.ts +1 -1
- package/dist/core/retry-fallback/candidates.js +1 -12
- package/dist/core/retry-fallback/controller.d.ts +3 -1
- package/dist/core/retry-fallback/controller.js +11 -5
- package/dist/core/retry-fallback/cooldown.d.ts +2 -1
- package/dist/core/retry-fallback/cooldown.js +21 -12
- package/dist/core/retry-fallback/unusable.d.ts +2 -0
- package/dist/core/retry-fallback/unusable.js +14 -2
- package/dist/core/retry-fallback/usage-limit.d.ts +10 -1
- package/dist/core/retry-fallback/usage-limit.js +39 -2
- package/dist/core/session-title-generator.js +2 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -0
- package/dist/modes/index.d.ts +1 -1
- package/dist/modes/interactive/components/assistant-render-descriptors.js +7 -1
- package/dist/modes/interactive/composer-user-activity.d.ts +6 -0
- package/dist/modes/interactive/composer-user-activity.js +30 -0
- package/dist/modes/interactive/grok/chrome.js +2 -1
- package/dist/modes/interactive/interactive-mode.js +15 -6
- package/dist/modes/interactive/session-control-registry.js +8 -1
- package/dist/modes/interactive/theme/reserved-keyword-rainbow.d.ts +11 -0
- package/dist/modes/interactive/theme/reserved-keyword-rainbow.js +50 -0
- package/dist/modes/interactive/theme/theme.d.ts +3 -1
- package/dist/modes/interactive/theme/theme.js +9 -0
- package/dist/modes/print-mode.js +7 -2
- package/dist/modes/rpc/connection-handler.js +43 -11
- package/dist/modes/rpc/custom-capability.d.ts +2 -0
- package/dist/modes/rpc/custom-capability.js +2 -0
- package/dist/modes/rpc/host-admission-proxy.d.ts +9 -0
- package/dist/modes/rpc/host-admission-proxy.js +211 -0
- package/dist/modes/rpc/host-admission.d.ts +21 -0
- package/dist/modes/rpc/host-admission.js +59 -0
- package/dist/modes/rpc/host-capabilities.js +2 -1
- package/dist/modes/rpc/host-client-occupancy.d.ts +2 -1
- package/dist/modes/rpc/host-client-occupancy.js +7 -0
- package/dist/modes/rpc/host-decision.d.ts +2 -2
- package/dist/modes/rpc/host-decision.js +4 -0
- package/dist/modes/rpc/host-ensure-types.d.ts +2 -0
- package/dist/modes/rpc/host-ensure.js +31 -3
- package/dist/modes/rpc/host-lifecycle-drain.d.ts +2 -1
- package/dist/modes/rpc/host-lifecycle-drain.js +3 -1
- package/dist/modes/rpc/host-lifecycle-proxy.d.ts +3 -2
- package/dist/modes/rpc/host-lifecycle-proxy.js +10 -19
- package/dist/modes/rpc/host-lifecycle-shutdown.d.ts +2 -0
- package/dist/modes/rpc/host-lifecycle-shutdown.js +3 -1
- package/dist/modes/rpc/host-lifecycle-ticker.d.ts +11 -0
- package/dist/modes/rpc/host-lifecycle-ticker.js +27 -0
- package/dist/modes/rpc/host-lifecycle.js +15 -23
- package/dist/modes/rpc/host-outcome.js +1 -0
- package/dist/modes/rpc/host-protocol-info.d.ts +2 -0
- package/dist/modes/rpc/host-protocol-info.js +1 -0
- package/dist/modes/rpc/host-session-runner.js +22 -1
- package/dist/modes/rpc/host-status-all.js +1 -0
- package/dist/modes/rpc/host-status.d.ts +1 -0
- package/dist/modes/rpc/host-status.js +5 -1
- package/dist/modes/rpc/rpc-client.d.ts +7 -1
- package/dist/modes/rpc/rpc-client.js +12 -0
- package/dist/modes/rpc/rpc-types.d.ts +49 -1
- package/dist/modes/rpc/rpc-types.js +5 -0
- package/dist/modes/rpc/session-command-activity.js +2 -0
- package/dist/modes/rpc/session-held.js +3 -0
- package/docs/extensions.md +13 -1
- package/docs/providers.md +88 -0
- package/docs/rpc.md +150 -5
- package/docs/web-search.md +3 -1
- package/package.json +7 -7
- package/dist/bundle/chunks/anthropic-messages-5QQJVQPZ.js +0 -9
- package/dist/bundle/chunks/chunk-2A3MS2Z7.js +0 -8
- package/dist/bundle/chunks/chunk-6SBY4ZWC.js +0 -2
- package/dist/bundle/chunks/chunk-6WCOSBI7.js +0 -2
- package/dist/bundle/chunks/chunk-E75RO54O.js +0 -14
- package/dist/bundle/chunks/chunk-JMYO34RK.js +0 -2
- package/dist/bundle/chunks/chunk-KRNXUTZJ.js +0 -2
- package/dist/bundle/chunks/chunk-L4HVJWIF.js +0 -11
- package/dist/bundle/chunks/chunk-N5WLLHUE.js +0 -2
- package/dist/bundle/chunks/chunk-NEFNNQSV.js +0 -9
- package/dist/bundle/chunks/chunk-PWIYVLIA.js +0 -9
- package/dist/bundle/chunks/chunk-Q3R44CGT.js +0 -5
- package/dist/bundle/chunks/chunk-QIARZN7G.js +0 -31
- package/dist/bundle/chunks/chunk-QKU6JOA4.js +0 -2
- package/dist/bundle/chunks/chunk-QYWXDE47.js +0 -2
- package/dist/bundle/chunks/chunk-XHELSLN3.js +0 -188
- package/dist/bundle/chunks/chunk-YDJ4HGWO.js +0 -16
- package/dist/bundle/chunks/chunk-ZJHRDQCH.js +0 -2
- package/dist/bundle/chunks/cli-main-TUBNBZNK.js +0 -3
- package/dist/bundle/chunks/host-command-WO7VKJBJ.js +0 -2
- package/dist/bundle/chunks/host-lifecycle-X2J5FTJL.js +0 -3
- package/dist/bundle/chunks/host-session-command-ICBOH5RP.js +0 -19
- package/dist/bundle/chunks/interactive-mode-UGDNCGQ5.js +0 -2
- package/dist/bundle/chunks/rotation-stream-OODURUGZ.js +0 -2
- package/dist/bundle/chunks/rpc-mode-22MCT32O.js +0 -2
- package/dist/bundle/chunks/runtime-session-I4NDNGC5.js +0 -2
- package/dist/bundle/chunks/session-control-endpoint-SFPRQBDO.js +0 -3
- package/dist/bundle/chunks/session-picker-3ZQPV7Z3.js +0 -2
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** Idle and lifetime-owner checks share one ticker and never overlap owner probes. */
|
|
2
|
+
export function startSupervisorTicker(options) {
|
|
3
|
+
const { activity, owner, draining, shuttingDown, shutdown } = options;
|
|
4
|
+
const tickIntervalMs = Math.max(20, Math.min(1_000, options.idleExitMs / 4));
|
|
5
|
+
let ownerCheck = false;
|
|
6
|
+
const ticker = setInterval(() => {
|
|
7
|
+
if (!draining() && activity.refresh() === "exit" && activity.clients.unclassifiedCount === 0)
|
|
8
|
+
void shutdown("idle", 0);
|
|
9
|
+
if (!ownerCheck && !shuttingDown() && !draining()) {
|
|
10
|
+
ownerCheck = true;
|
|
11
|
+
void owner
|
|
12
|
+
.shouldExit(activity)
|
|
13
|
+
.then((exit) => {
|
|
14
|
+
if (exit && !shuttingDown() && !draining())
|
|
15
|
+
void shutdown("owner_gone", 0);
|
|
16
|
+
})
|
|
17
|
+
.finally(() => {
|
|
18
|
+
ownerCheck = false;
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
}, tickIntervalMs);
|
|
22
|
+
return () => {
|
|
23
|
+
clearInterval(ticker);
|
|
24
|
+
owner.stop();
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=host-lifecycle-ticker.js.map
|
|
@@ -3,7 +3,8 @@ import { randomUUID } from "node:crypto";
|
|
|
3
3
|
import { realpathSync } from "node:fs";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
|
-
import { getAgentDir, isBundledNode } from "../../config.js";
|
|
6
|
+
import { getAgentDir, isBundledNode, VERSION } from "../../config.js";
|
|
7
|
+
import { HostAdmission } from "./host-admission.js";
|
|
7
8
|
import { classifyChildExit, noteChildExit } from "./host-child-exit.js";
|
|
8
9
|
import { hostCrashCleanupPaths } from "./host-cleanup-paths.js";
|
|
9
10
|
import { createHostDaemonPaths, generationPaths } from "./host-daemon-paths.js";
|
|
@@ -15,6 +16,7 @@ import { resolveHostPolicy } from "./host-lifecycle-policy.js";
|
|
|
15
16
|
import { adoptPublicSocket, createPublicProxy, ensurePublicSocketSecret, listen, prepareSocketPath, waitForListener, } from "./host-lifecycle-proxy.js";
|
|
16
17
|
import { createInternalSocketPath, readSettingsFile, recordChildPid } from "./host-lifecycle-scratch.js";
|
|
17
18
|
import { performShutdown, registerSupervisorSignals, supervisorSender, } from "./host-lifecycle-shutdown.js";
|
|
19
|
+
import { startSupervisorTicker } from "./host-lifecycle-ticker.js";
|
|
18
20
|
import { errorMessage, writeStderrLine } from "./host-supervisor-log.js";
|
|
19
21
|
import { PUBLIC_SOCKET_IDENTITY_FILE, statSocketIdentity, writeSocketIdentityFile } from "./socket-ownership.js";
|
|
20
22
|
import { createSocketSecret, socketSecretPath } from "./socket-transport.js";
|
|
@@ -110,13 +112,14 @@ export async function runHostSupervisor(launch) {
|
|
|
110
112
|
const verdict = classifyChildExit(code, signal);
|
|
111
113
|
void state.childExitRecorded.then(() => shutdown(verdict.reason, verdict.exitCode));
|
|
112
114
|
});
|
|
113
|
-
const
|
|
115
|
+
const admission = new HostAdmission({ protocolVersion: 1, serverVersion: VERSION, capabilities: [], instanceId });
|
|
116
|
+
const drain = new SupervisorDrain(child, () => state.shuttingDown, () => admission.stop());
|
|
114
117
|
const server = createPublicProxy({
|
|
115
118
|
internalSocket,
|
|
116
119
|
...(internalSecret ? { internalSecret } : {}),
|
|
117
120
|
...(publicSecret ? { publicSecret } : {}),
|
|
118
121
|
clients: activity.clients,
|
|
119
|
-
|
|
122
|
+
admission,
|
|
120
123
|
onDetach: () => activity.refresh(),
|
|
121
124
|
onAdmit: () => owner.activity(),
|
|
122
125
|
});
|
|
@@ -124,26 +127,14 @@ export async function runHostSupervisor(launch) {
|
|
|
124
127
|
if (!state.shuttingDown)
|
|
125
128
|
void shutdown(`public socket listener failed: ${errorMessage(cause)}`, 1);
|
|
126
129
|
});
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
.shouldExit(activity)
|
|
136
|
-
.then((exit) => {
|
|
137
|
-
if (exit && !state.shuttingDown && !drain.active)
|
|
138
|
-
void shutdown("owner_gone", 0);
|
|
139
|
-
})
|
|
140
|
-
.finally(() => {
|
|
141
|
-
ownerCheck = false;
|
|
142
|
-
});
|
|
143
|
-
}
|
|
144
|
-
}, tickIntervalMs);
|
|
145
|
-
watchers.push(() => clearInterval(ticker));
|
|
146
|
-
watchers.push(() => owner.stop());
|
|
130
|
+
watchers.push(startSupervisorTicker({
|
|
131
|
+
idleExitMs: policy.idleExitMs,
|
|
132
|
+
activity,
|
|
133
|
+
owner,
|
|
134
|
+
draining: () => drain.active,
|
|
135
|
+
shuttingDown: () => state.shuttingDown,
|
|
136
|
+
shutdown,
|
|
137
|
+
}));
|
|
147
138
|
const teardown = {
|
|
148
139
|
paths,
|
|
149
140
|
generation,
|
|
@@ -154,6 +145,7 @@ export async function runHostSupervisor(launch) {
|
|
|
154
145
|
child,
|
|
155
146
|
activity,
|
|
156
147
|
drain,
|
|
148
|
+
admission,
|
|
157
149
|
state,
|
|
158
150
|
stopWatchers: () => {
|
|
159
151
|
for (const stop of watchers.splice(0))
|
|
@@ -16,6 +16,8 @@ import type { RpcLaunchProfile } from "./rpc-types.ts";
|
|
|
16
16
|
* host from an older release answers without them; absence is read as "uncomparable", never as zero.
|
|
17
17
|
*/
|
|
18
18
|
export interface HostProtocolInfo {
|
|
19
|
+
/** Absent on older hosts. A stopping generation never admits a new session. */
|
|
20
|
+
readonly lifecycle?: "ready" | "stopping";
|
|
19
21
|
readonly protocolVersion: number;
|
|
20
22
|
/** Informational only. Nothing in this module compares it. */
|
|
21
23
|
readonly serverVersion: string;
|
|
@@ -14,6 +14,7 @@ export function parseHostProtocolInfo(data) {
|
|
|
14
14
|
protocolVersion: typeof data.protocolVersion === "number" ? data.protocolVersion : 0,
|
|
15
15
|
serverVersion: data.serverVersion,
|
|
16
16
|
capabilities: data.capabilities,
|
|
17
|
+
...((data.lifecycle === "ready" || data.lifecycle === "stopping") && { lifecycle: data.lifecycle }),
|
|
17
18
|
...(typeof data.instanceId === "string" && { instanceId: data.instanceId }),
|
|
18
19
|
...(typeof data.generation === "number" &&
|
|
19
20
|
Number.isSafeInteger(data.generation) && { generation: data.generation }),
|
|
@@ -4,13 +4,30 @@ import { createHostDaemonPaths } from "./host-daemon-paths.js";
|
|
|
4
4
|
import { HostEnsureRefusedError } from "./host-decision.js";
|
|
5
5
|
import { endpointKindOfSocket } from "./host-endpoints.js";
|
|
6
6
|
import { ensureHost } from "./host-ensure.js";
|
|
7
|
+
import { STOP_WAIT_BUDGET_MS } from "./host-ensure-stop.js";
|
|
7
8
|
import { HOST_EXIT_OK, refusal } from "./host-outcome.js";
|
|
8
9
|
import { mapError } from "./host-session-errors.js";
|
|
9
10
|
import { openHostSession } from "./host-session-open.js";
|
|
10
11
|
import { resolveSessionRef } from "./host-session-ref.js";
|
|
11
12
|
import { waitForHostSession } from "./host-session-wait.js";
|
|
12
|
-
import { RpcClient } from "./rpc-client.js";
|
|
13
|
+
import { RpcClient, RpcCommandError } from "./rpc-client.js";
|
|
13
14
|
export async function runHostSessionRequest(request) {
|
|
15
|
+
const deadline = Date.now() + STOP_WAIT_BUDGET_MS;
|
|
16
|
+
for (;;) {
|
|
17
|
+
try {
|
|
18
|
+
return await runHostSessionRequestOnce(request, Math.max(0, deadline - Date.now()));
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
if (request.action === "open" &&
|
|
22
|
+
error instanceof RpcCommandError &&
|
|
23
|
+
error.errorCode === "host_stopping" &&
|
|
24
|
+
Date.now() < deadline)
|
|
25
|
+
continue;
|
|
26
|
+
return mapError(error, request.target.socket);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
async function runHostSessionRequestOnce(request, stoppingTimeoutMs) {
|
|
14
31
|
const { socket, agentDir } = request.target;
|
|
15
32
|
let client;
|
|
16
33
|
let ensured;
|
|
@@ -27,6 +44,7 @@ export async function runHostSessionRequest(request) {
|
|
|
27
44
|
env: daemonEnvOverrides(process.env, request.spec.env),
|
|
28
45
|
policy: request.spec.policy,
|
|
29
46
|
upgrade: "never",
|
|
47
|
+
stoppingTimeoutMs,
|
|
30
48
|
});
|
|
31
49
|
if (!ensured.reused) {
|
|
32
50
|
await writeDaemonEnvKeys(createHostDaemonPaths({ socket, agentDir }), daemonEnvKeys(process.env, request.spec.env));
|
|
@@ -160,6 +178,9 @@ export async function runHostSessionRequest(request) {
|
|
|
160
178
|
}
|
|
161
179
|
}
|
|
162
180
|
catch (error) {
|
|
181
|
+
// Only an explicit open owns a launch spec. No other operation may invent one.
|
|
182
|
+
if (request.action === "open" && error instanceof RpcCommandError && error.errorCode === "host_stopping")
|
|
183
|
+
throw error;
|
|
163
184
|
if (error instanceof HostEnsureRefusedError) {
|
|
164
185
|
return refusal("refuse", undefined, { reason: error.reason, socket, detail: error.detail });
|
|
165
186
|
}
|
|
@@ -21,6 +21,7 @@ export type { HostGenerationRow } from "./host-generations.ts";
|
|
|
21
21
|
export type { HostPathClaimRow, HostSessionRow } from "./host-status-rows.ts";
|
|
22
22
|
export interface HostStatusReport {
|
|
23
23
|
readonly reachable: boolean;
|
|
24
|
+
readonly lifecycle: "ready" | "stopping" | null;
|
|
24
25
|
readonly socket: string;
|
|
25
26
|
readonly pid: number | null;
|
|
26
27
|
readonly instanceId: string | null;
|
|
@@ -47,10 +47,13 @@ export async function probeHostStatus(options, read = {}) {
|
|
|
47
47
|
const metrics = current ? await readHostProcessMetrics(current.pid) : UNOBSERVED_METRICS;
|
|
48
48
|
// A socket that did not answer who it is will not answer what it holds: asking again would only
|
|
49
49
|
// spend a second budget on a hung endpoint.
|
|
50
|
-
const listing = host === undefined
|
|
50
|
+
const listing = host === undefined || host.lifecycle === "stopping"
|
|
51
|
+
? []
|
|
52
|
+
: await readSessionListing(options.socket, includeWorkers, timeoutMs);
|
|
51
53
|
const claims = await readClaimRows(paths, generations);
|
|
52
54
|
const report = {
|
|
53
55
|
reachable: host !== undefined,
|
|
56
|
+
lifecycle: host?.lifecycle ?? null,
|
|
54
57
|
socket: options.socket,
|
|
55
58
|
pid: current?.pid ?? null,
|
|
56
59
|
instanceId: host?.instanceId ?? current?.instanceId ?? null,
|
|
@@ -106,6 +109,7 @@ export function hostSummary(host) {
|
|
|
106
109
|
return {
|
|
107
110
|
protocolVersion: host.protocolVersion,
|
|
108
111
|
serverVersion: host.serverVersion,
|
|
112
|
+
lifecycle: host.lifecycle ?? null,
|
|
109
113
|
capabilities: host.capabilities,
|
|
110
114
|
instanceId: host.instanceId ?? null,
|
|
111
115
|
generation: host.generation ?? null,
|
|
@@ -21,7 +21,7 @@ export declare class RpcCommandError extends Error {
|
|
|
21
21
|
}
|
|
22
22
|
import type { SessionEntry, SessionTreeNode } from "../../core/session-manager.ts";
|
|
23
23
|
import type { JsonAgentSessionEvent } from "../json-event.ts";
|
|
24
|
-
import type { EditAssistantMessageResult, EditUserMessageResult, RpcAccountFailoverEvent, RpcAuthAccountsChangedEvent, RpcExtensionEvent, RpcExtensionUIProgress, RpcExtensionUIRequest, RpcExtensionUIResponse, RpcOpenQueuedEvent, RpcProviderAccount, RpcSessionModelEntry, RpcSessionParkedEvent, RpcSessionReplacedEvent, RpcSessionState, RpcSlashCommand } from "./rpc-types.ts";
|
|
24
|
+
import type { EditAssistantMessageResult, EditUserMessageResult, RpcAccountFailoverEvent, RpcAuthAccountsChangedEvent, RpcEvalCell, RpcEvalCellSnapshot, RpcExtensionEvent, RpcExtensionUIProgress, RpcExtensionUIRequest, RpcExtensionUIResponse, RpcOpenQueuedEvent, RpcProviderAccount, RpcSessionModelEntry, RpcSessionParkedEvent, RpcSessionReplacedEvent, RpcSessionState, RpcSlashCommand } from "./rpc-types.ts";
|
|
25
25
|
export interface RpcClientOptions {
|
|
26
26
|
/** Connect to an existing multi-session Unix socket instead of spawning a child. */
|
|
27
27
|
socketPath?: string;
|
|
@@ -476,6 +476,12 @@ export declare class RpcClient {
|
|
|
476
476
|
getCommands(): Promise<RpcSlashCommand[]>;
|
|
477
477
|
/** Invoke one extension-owned RPC request handler and return its structured result. */
|
|
478
478
|
requestExtension<T = unknown>(name: string, data?: unknown): Promise<T>;
|
|
479
|
+
/** Read live and recently settled eval cells without a model tool call. */
|
|
480
|
+
listEvalCells(): Promise<RpcEvalCell[]>;
|
|
481
|
+
/** Read a bounded UTF-8 output tail; the host caps tailBytes at 64 KiB. */
|
|
482
|
+
peekEvalCell(cellId: string, tailBytes?: number): Promise<RpcEvalCellSnapshot>;
|
|
483
|
+
/** Stop a session-owned cell and return its confirmed terminal snapshot. */
|
|
484
|
+
stopEvalCell(cellId: string): Promise<RpcEvalCellSnapshot>;
|
|
479
485
|
/** List safe metadata for the named provider's configured account slots. */
|
|
480
486
|
getProviderAccounts(provider: string): Promise<RpcProviderAccount[]>;
|
|
481
487
|
/** Pin a provider account for future session-affine requests, or clear the pin. */
|
|
@@ -782,6 +782,18 @@ export class RpcClient {
|
|
|
782
782
|
});
|
|
783
783
|
return this.getData(response);
|
|
784
784
|
}
|
|
785
|
+
/** Read live and recently settled eval cells without a model tool call. */
|
|
786
|
+
async listEvalCells() {
|
|
787
|
+
return this.getData(await this.send({ type: "eval_cells.list" }));
|
|
788
|
+
}
|
|
789
|
+
/** Read a bounded UTF-8 output tail; the host caps tailBytes at 64 KiB. */
|
|
790
|
+
async peekEvalCell(cellId, tailBytes) {
|
|
791
|
+
return this.getData(await this.send({ type: "eval_cells.peek", cellId, ...(tailBytes === undefined ? {} : { tailBytes }) }));
|
|
792
|
+
}
|
|
793
|
+
/** Stop a session-owned cell and return its confirmed terminal snapshot. */
|
|
794
|
+
async stopEvalCell(cellId) {
|
|
795
|
+
return this.getData(await this.send({ type: "eval_cells.stop", cellId }));
|
|
796
|
+
}
|
|
785
797
|
/** List safe metadata for the named provider's configured account slots. */
|
|
786
798
|
async getProviderAccounts(provider) {
|
|
787
799
|
const response = await this.send({ type: "get_provider_accounts", provider });
|
|
@@ -331,6 +331,18 @@ type RpcSessionCommand = {
|
|
|
331
331
|
type: "extension_request";
|
|
332
332
|
name: string;
|
|
333
333
|
data?: unknown;
|
|
334
|
+
} | {
|
|
335
|
+
id?: string;
|
|
336
|
+
type: "eval_cells.list";
|
|
337
|
+
} | {
|
|
338
|
+
id?: string;
|
|
339
|
+
type: "eval_cells.peek";
|
|
340
|
+
cellId: string;
|
|
341
|
+
tailBytes?: number;
|
|
342
|
+
} | {
|
|
343
|
+
id?: string;
|
|
344
|
+
type: "eval_cells.stop";
|
|
345
|
+
cellId: string;
|
|
334
346
|
} | {
|
|
335
347
|
id?: string;
|
|
336
348
|
type: "get_auth_providers";
|
|
@@ -409,6 +421,9 @@ export declare const RPC_ERROR_STREAMING = "streaming";
|
|
|
409
421
|
export declare const RPC_ERROR_ENTRY_NOT_FOUND = "not_found";
|
|
410
422
|
/** `set_model` named a provider/model pair the session's registry does not offer. */
|
|
411
423
|
export declare const RPC_ERROR_MODEL_NOT_FOUND = "model_not_found";
|
|
424
|
+
export declare const RPC_ERROR_EVAL_CELL_NOT_FOUND = "eval_cell_not_found";
|
|
425
|
+
export declare const RPC_ERROR_INVALID_EVAL_CELL_REQUEST = "invalid_eval_cell_request";
|
|
426
|
+
export declare const RPC_ERROR_EVAL_CELLS_UNAVAILABLE = "eval_cells_unavailable";
|
|
412
427
|
export declare const RPC_ERROR_NOT_ASSISTANT = "not_assistant";
|
|
413
428
|
export declare const RPC_ERROR_NOT_USER = "not_user";
|
|
414
429
|
export declare const RPC_ERROR_EMPTY_TEXT = "empty";
|
|
@@ -437,7 +452,9 @@ export declare const RPC_ERROR_RELEASE_UNSUPPORTED = "release_unsupported";
|
|
|
437
452
|
export declare const RPC_ERROR_RELEASE_FAILED = "release_failed";
|
|
438
453
|
/** A draining host parks every session itself; `release_session` and `open_session` are refused meanwhile. */
|
|
439
454
|
export declare const RPC_ERROR_HOST_DRAINING = "host_draining";
|
|
440
|
-
|
|
455
|
+
/** The supervisor has closed admission; a launch-owning caller may ensure a successor. */
|
|
456
|
+
export declare const RPC_ERROR_HOST_STOPPING = "host_stopping";
|
|
457
|
+
export type RpcErrorCode = typeof RPC_ERROR_HOST_STOPPING | typeof RPC_ERROR_UNKNOWN_SESSION | typeof RPC_ERROR_SESSION_CLOSING | typeof RPC_ERROR_SESSION_PATH_IN_USE | typeof RPC_ERROR_SESSION_HELD | typeof RPC_ERROR_SESSION_RESERVATION_LIMIT | typeof RPC_ERROR_MISSING_SESSION_ID | typeof RPC_ERROR_MULTI_SESSION_DISABLED | typeof RPC_ERROR_INVALID_PATH | typeof RPC_ERROR_OPEN_FAILED | typeof RPC_ERROR_MEDIA_NOT_FOUND | typeof RPC_ERROR_INVALID_SESSION_CONTEXT | typeof RPC_ERROR_INVALID_SESSION_KIND | typeof RPC_ERROR_INVALID_LAUNCH_PROFILE | typeof RPC_ERROR_INVALID_SESSION_ID | typeof RPC_ERROR_SESSION_ID_IN_USE | typeof RPC_ERROR_HOST_MEMORY_PRESSURE | typeof RPC_ERROR_WARM_FAILED | typeof RPC_ERROR_STREAMING | typeof RPC_ERROR_ENTRY_NOT_FOUND | typeof RPC_ERROR_MODEL_NOT_FOUND | typeof RPC_ERROR_EVAL_CELL_NOT_FOUND | typeof RPC_ERROR_INVALID_EVAL_CELL_REQUEST | typeof RPC_ERROR_EVAL_CELLS_UNAVAILABLE | typeof RPC_ERROR_NOT_ASSISTANT | typeof RPC_ERROR_NOT_USER | typeof RPC_ERROR_EMPTY_TEXT | typeof RPC_ERROR_UNKNOWN_COMMAND | typeof RPC_ERROR_STALE_LEAF | typeof RPC_ERROR_TURN_ACTIVE | typeof RPC_ERROR_SESSION_BUSY | typeof RPC_ERROR_ATTACHED | typeof RPC_ERROR_INVALID_RELEASE_REASON | typeof RPC_ERROR_RELEASE_UNSUPPORTED | typeof RPC_ERROR_RELEASE_FAILED | typeof RPC_ERROR_HOST_DRAINING;
|
|
441
458
|
/** Every established command accepts an additive routing envelope. */
|
|
442
459
|
export type RpcCommand = (RpcSessionCommand & {
|
|
443
460
|
sessionId?: string;
|
|
@@ -707,6 +724,8 @@ export interface RpcProtocolIdentity {
|
|
|
707
724
|
readonly launch_profile: RpcLaunchProfile;
|
|
708
725
|
}
|
|
709
726
|
export interface RpcProtocolInfo extends RpcProtocolIdentity {
|
|
727
|
+
/** Supervised socket hosts expose admission state independently of child responsiveness. */
|
|
728
|
+
readonly lifecycle?: "ready" | "stopping";
|
|
710
729
|
readonly protocolVersion: 1;
|
|
711
730
|
/** Informational engine version. Never compare it for compatibility - use the fields above. */
|
|
712
731
|
readonly serverVersion: string;
|
|
@@ -1204,6 +1223,18 @@ export type RpcResponse = {
|
|
|
1204
1223
|
command: "extension_request";
|
|
1205
1224
|
success: true;
|
|
1206
1225
|
data: unknown;
|
|
1226
|
+
} | {
|
|
1227
|
+
id?: string;
|
|
1228
|
+
type: "response";
|
|
1229
|
+
command: "eval_cells.list";
|
|
1230
|
+
success: true;
|
|
1231
|
+
data: RpcEvalCell[];
|
|
1232
|
+
} | {
|
|
1233
|
+
id?: string;
|
|
1234
|
+
type: "response";
|
|
1235
|
+
command: "eval_cells.peek" | "eval_cells.stop";
|
|
1236
|
+
success: true;
|
|
1237
|
+
data: RpcEvalCellSnapshot;
|
|
1207
1238
|
} | {
|
|
1208
1239
|
id?: string;
|
|
1209
1240
|
type: "response";
|
|
@@ -1259,6 +1290,23 @@ export type RpcResponse = {
|
|
|
1259
1290
|
errorCode?: string;
|
|
1260
1291
|
errorData?: unknown;
|
|
1261
1292
|
};
|
|
1293
|
+
/** Session-owned cell metadata; live and recently settled cells share the same list shape. */
|
|
1294
|
+
export interface RpcEvalCell {
|
|
1295
|
+
readonly cellId: string;
|
|
1296
|
+
readonly language: "js" | "py" | "rb" | "jl";
|
|
1297
|
+
readonly state: "queued" | "running" | "detached" | "completed" | "failed" | "cancelled";
|
|
1298
|
+
readonly startedAtMs: number;
|
|
1299
|
+
readonly summary?: string;
|
|
1300
|
+
readonly queuedBehind?: readonly string[];
|
|
1301
|
+
}
|
|
1302
|
+
/** Bounded text-only snapshot: no duplicated result body, code, or inline images. */
|
|
1303
|
+
export interface RpcEvalCellSnapshot extends RpcEvalCell {
|
|
1304
|
+
readonly outputTail: string;
|
|
1305
|
+
readonly stateRetained?: boolean;
|
|
1306
|
+
readonly interruptNote?: string;
|
|
1307
|
+
readonly hardLimitSeconds?: number;
|
|
1308
|
+
readonly runBudgetSeconds?: number;
|
|
1309
|
+
}
|
|
1262
1310
|
/** Success payload of `edit_assistant_message`. `leafId` is the session leaf after the call. */
|
|
1263
1311
|
export type EditAssistantMessageResult = {
|
|
1264
1312
|
outcome: "edited";
|
|
@@ -46,6 +46,9 @@ export const RPC_ERROR_STREAMING = "streaming";
|
|
|
46
46
|
export const RPC_ERROR_ENTRY_NOT_FOUND = "not_found";
|
|
47
47
|
/** `set_model` named a provider/model pair the session's registry does not offer. */
|
|
48
48
|
export const RPC_ERROR_MODEL_NOT_FOUND = "model_not_found";
|
|
49
|
+
export const RPC_ERROR_EVAL_CELL_NOT_FOUND = "eval_cell_not_found";
|
|
50
|
+
export const RPC_ERROR_INVALID_EVAL_CELL_REQUEST = "invalid_eval_cell_request";
|
|
51
|
+
export const RPC_ERROR_EVAL_CELLS_UNAVAILABLE = "eval_cells_unavailable";
|
|
49
52
|
export const RPC_ERROR_NOT_ASSISTANT = "not_assistant";
|
|
50
53
|
export const RPC_ERROR_NOT_USER = "not_user";
|
|
51
54
|
export const RPC_ERROR_EMPTY_TEXT = "empty";
|
|
@@ -74,4 +77,6 @@ export const RPC_ERROR_RELEASE_UNSUPPORTED = "release_unsupported";
|
|
|
74
77
|
export const RPC_ERROR_RELEASE_FAILED = "release_failed";
|
|
75
78
|
/** A draining host parks every session itself; `release_session` and `open_session` are refused meanwhile. */
|
|
76
79
|
export const RPC_ERROR_HOST_DRAINING = "host_draining";
|
|
80
|
+
/** The supervisor has closed admission; a launch-owning caller may ensure a successor. */
|
|
81
|
+
export const RPC_ERROR_HOST_STOPPING = "host_stopping";
|
|
77
82
|
//# sourceMappingURL=rpc-types.js.map
|
package/docs/extensions.md
CHANGED
|
@@ -709,7 +709,7 @@ The `systemPromptOptions` field gives extensions access to the same structured d
|
|
|
709
709
|
|
|
710
710
|
Inside `before_agent_start`, `event.systemPrompt` and `ctx.getSystemPrompt()` both reflect the chained system prompt as of the current handler. Later `before_agent_start` handlers can still modify it again.
|
|
711
711
|
|
|
712
|
-
`event.preview` is `true` when senpi composes the next turn's system prompt before any user prompt exists
|
|
712
|
+
`event.preview` is `true` when senpi composes the next turn's system prompt before any user prompt exists. Prompt-cache prewarm sends that prefix only after the first real composer activity and only when `promptCache.keepAlive.enabled` is `true` (default `false`, so prewarm is off by default). Session start, resume, fork, reload, and extension-injected text never trigger prewarm. `ctx.getPromptCachePrefixRequest()` also returns this prefix to extensions. In a preview `event.prompt` is empty and no turn follows.
|
|
713
713
|
|
|
714
714
|
A preview only reaches handlers that opt in with `{ previewSafe: true }`:
|
|
715
715
|
|
|
@@ -870,6 +870,8 @@ Fired before each LLM call. Modify messages non-destructively. See [Session Form
|
|
|
870
870
|
|
|
871
871
|
`event.source` is `"agent"` for the live agent request and `"projection"` for `prepareProviderRequest` calls, including summarizer inputs. Projections must not advance state belonging to the live request, even when their messages match the branch. The session journal can lag a live request, so transcript equality is not a reliable substitute for this marker.
|
|
872
872
|
|
|
873
|
+
`ctx.prepareProviderRequest(messages, excludeExtensionPath?, options?)` returns the projected context and its payload transform. In this release the transform excludes server-search tools by default, while retaining client function tools such as `web_search`. Call `ctx.prepareProviderRequest(messages, undefined, { allowServerSearch: true })` when a request must mirror the main turn's native tools and include fields, as keepalive does. Cache-friendly compaction retains the original main-turn payload hook separately; standalone summaries and session titles exclude server search.
|
|
874
|
+
|
|
873
875
|
```typescript
|
|
874
876
|
pi.on("context", async (event, ctx) => {
|
|
875
877
|
// event.messages - deep copy, safe to modify
|
|
@@ -1259,6 +1261,12 @@ Optional read-only getter for the absolute path to the current session's authori
|
|
|
1259
1261
|
|
|
1260
1262
|
Shell tools and eval kernels expose this value as `PI_GOAL_STORE_FILE` and `ctx.cwd` as `PI_SESSION_CWD`. Hand-built contexts and older hosts may omit the getter, in which case `PI_GOAL_STORE_FILE` is unset.
|
|
1261
1263
|
|
|
1264
|
+
### ctx.onUserActivity()
|
|
1265
|
+
|
|
1266
|
+
Optional subscription-only hook for host-authenticated activity. `ctx.onUserActivity(listener)` calls a synchronous listener with `"draft"` for actual terminal composer edits or `"prompt"` for user-originated prompt, steer, or follow-up admission, including RPC. It returns an unsubscribe function and is also removed when the extension generation retires. The host owns the emitter; `pi.events.emit("user_activity", ...)` cannot invoke this hook. Extension `pasteToEditor`, programmatic draft restoration, and lifecycle events do not publish activity.
|
|
1267
|
+
|
|
1268
|
+
Cache prewarm consumes the first activity per session generation. It is opt-in via `promptCache.keepAlive.enabled` (default `false`): a first draft edit may prewarm, while prompt admission uses the real request instead of issuing an extra warm call. This one-shot policy does not gate the existing idle keep-alive loop after completed turns.
|
|
1269
|
+
|
|
1262
1270
|
### ctx.modelRegistry / ctx.model / ctx.thinkingLevel / ctx.scopedModels
|
|
1263
1271
|
|
|
1264
1272
|
Access to models, providers, and resolved authentication. `ctx.modelRegistry.getProvider(id)` returns the effective pi-ai provider, while `getProviderAuth(id)` resolves its current API key, headers, base URL, and provider-scoped environment without requiring a loaded model. `ctx.model` is the active model, and `ctx.thinkingLevel` is its current effective thinking level.
|
|
@@ -1329,6 +1337,10 @@ if (usage && usage.tokens > 100_000) {
|
|
|
1329
1337
|
}
|
|
1330
1338
|
```
|
|
1331
1339
|
|
|
1340
|
+
### ctx.getCompactionModel()
|
|
1341
|
+
|
|
1342
|
+
Optional on hosts that expose session-owned summarization routing. Returns the current session model while its account is usable, preserving builtin compaction behavior even when core compaction has a model override. For a spent account, it tries the held switch target, the configured summary override, then an authenticated alternate with enough context capacity for the active transcript and reserved output. It does not change the active session selection and throws an account-limit error when no usable summary model is available.
|
|
1343
|
+
|
|
1332
1344
|
### ctx.compact()
|
|
1333
1345
|
|
|
1334
1346
|
Trigger compaction without awaiting completion. Use `onComplete` and `onError` for follow-up actions.
|
package/docs/providers.md
CHANGED
|
@@ -4,6 +4,7 @@ Senpi supports subscription-based providers via OAuth and API key providers via
|
|
|
4
4
|
|
|
5
5
|
## Table of Contents
|
|
6
6
|
|
|
7
|
+
- [In-Turn Web Search](#in-turn-web-search)
|
|
7
8
|
- [Subscriptions](#subscriptions)
|
|
8
9
|
- [API Keys](#api-keys)
|
|
9
10
|
- [Auth File](#auth-file)
|
|
@@ -13,6 +14,93 @@ Senpi supports subscription-based providers via OAuth and API key providers via
|
|
|
13
14
|
- [Custom Providers](#custom-providers)
|
|
14
15
|
- [Resolution Order](#resolution-order)
|
|
15
16
|
|
|
17
|
+
## In-Turn Web Search
|
|
18
|
+
|
|
19
|
+
The session model uses its provider's server search before the standalone
|
|
20
|
+
`web_search` tool. A successful native search does not issue an auxiliary
|
|
21
|
+
request on another model. A search-specific request error or native result
|
|
22
|
+
error records one `websearch-native-fallback` entry and enables the standalone
|
|
23
|
+
tool for that session/model, including later prompts and switches back to it.
|
|
24
|
+
Starting a new session clears the failed-model set. Existing core retries own their retry
|
|
25
|
+
when one is already pending; otherwise the extension continues once after
|
|
26
|
+
the agent settles. User cancellation never schedules that continuation.
|
|
27
|
+
A provider that recovers from a failed search and completes its answer does
|
|
28
|
+
not receive a duplicate continuation. Sonar's inherent retrieval has no
|
|
29
|
+
independent server-tool error contract; its model errors remain with core
|
|
30
|
+
retry rather than receiving unsupported function-tool definitions.
|
|
31
|
+
Standalone summaries, session titles and default projections exclude only server-search tools, not client function declarations. Tool-less requests do not gain empty tool arrays. Cache-friendly compaction and keepalive retain the exact main-turn payload transform, tools and include fields.
|
|
32
|
+
|
|
33
|
+
Anthropic Messages supports `web_search_20250305`. The direct Anthropic API and
|
|
34
|
+
Claude OAuth retain main's default-on behavior. Anthropic-compatible gateways
|
|
35
|
+
must explicitly declare search and replay support. For example, merge this override into your existing provider entry,
|
|
36
|
+
keeping its current credential configuration:
|
|
37
|
+
|
|
38
|
+
```json
|
|
39
|
+
{
|
|
40
|
+
"providers": {
|
|
41
|
+
"anthropic": {
|
|
42
|
+
"baseUrl": "https://your-proxy.example/anthropic",
|
|
43
|
+
"modelOverrides": {
|
|
44
|
+
"claude-opus-5-5": {
|
|
45
|
+
"compat": { "supportsWebSearch": true }
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
This opt-in was verified with a real ccapi search and exact encrypted-result
|
|
54
|
+
replay. Senpi does not infer support from a proxy hostname. Anthropic requires
|
|
55
|
+
the assistant blocks and their `encrypted_content` to be replayed unchanged.
|
|
56
|
+
|
|
57
|
+
| Route | In-turn tool / API | Capability and replay contract |
|
|
58
|
+
| --- | --- | --- |
|
|
59
|
+
| Direct Anthropic API / Claude OAuth | `web_search_20250305`, Messages | Default-on (unchanged from main); `compat.supportsWebSearch: false` opts out. No new direct-endpoint probe is claimed here. |
|
|
60
|
+
| Anthropic-compatible gateways, including ccapi | `web_search_20250305`, Messages | Opt-in `compat.supportsWebSearch: true`. ccapi search and encrypted-result replay verified live. |
|
|
61
|
+
| OpenAI API / Azure | `web_search_preview`, Responses | Default-on (unchanged from main). Custom Responses endpoints remain opt-in `compat.supportsWebSearchPreview: true`. The current `web_search` name is also supported. |
|
|
62
|
+
| ChatGPT/Codex subscription | `web_search`, Codex Responses | First-party subscription default, except Spark; custom endpoints require opt-in. Real search and output-item replay verified. |
|
|
63
|
+
| xAI | `web_search`, Responses | Opt-in `compat.supportsWebSearchPreview: true`. Catalogs select the tool shape but do not enable it. Held OAuth probe: HTTP 403, not live-verified. |
|
|
64
|
+
| Google Gemini / Vertex | `googleSearch`, GenerateContent | Opt-in `compat.supportsWebSearch: true` on Gemini 2/3. Held probe: HTTP 400 `API_KEY_INVALID`, not live-verified. Explicit mixed-tool rejection enables the session/model fallback. |
|
|
65
|
+
| OpenRouter | `openrouter:web_search`, Chat Completions or Responses | Opt-in `compat.supportsWebSearch: true`; doc only, not live-verified. Uses OpenRouter's own tool shape. |
|
|
66
|
+
| Moonshot PAYG | `$web_search` / `builtin_function`, Chat Completions | Opt-in `compat.supportsWebSearch: true`; doc only, not live-verified. Echoes server-generated arguments without another search request. Retires October 20, 2026. Cannot be discovered or lazily activated through tool_search, or declared on another route. |
|
|
67
|
+
| Kimi for Coding | Anthropic-compatible Messages | Not live-verified (no personal credential). Coding-plan integration docs do not specify a server-search name/shape or replay contract. Not default-on; generic Messages opt-in is not verification. Follow-up [#3134](https://github.com/code-yeongyu/senpi/issues/3134). |
|
|
68
|
+
| DeepSeek | Claude Code search on its Anthropic-compatible endpoint | Not live-verified (no personal credential). Claude Code docs claim API search but do not publish its raw shape/replay contract; a manually configured Messages route remains opt-in, not default-on. Built-in Chat Completions remains unsupported, and Responses docs list `web_search` as ignored. Follow-up [#3134](https://github.com/code-yeongyu/senpi/issues/3134). |
|
|
69
|
+
| Z.AI PAYG chat | `web_search`, Chat Completions | Explicit `compat.supportsWebSearch: true` on that route; never enabled automatically on the coding-plan endpoint. |
|
|
70
|
+
| GLM coding plan, z.ai Anthropic endpoint | `web_search_20250305`, Messages | Supported, opt-in `compat.supportsWebSearch: true`. Live response `msg_202610120132474ee314e9947c4ea2` returned server search with a `tool_result` block, which now remains visible and replays with its same-model server-tool pair even when the search usage counter is zero. OpenAI-compatible coding probe: not verified (length limit, no search payload); bigmodel.cn: not verified (HTTP 401, expired key). Remote MCP `webSearchPrime` remains a separate option. |
|
|
71
|
+
| Perplexity Sonar | Search inherent in its main completion | No extra tool injection. Native-bypass routing is opt-in `compat.supportsWebSearch: true`; doc only, not live-verified. |
|
|
72
|
+
| Exa, Tavily, Brave, Kagi, Serper, Serpdive, Keenable, Google CSE, SearXNG and result-page engines | Dedicated retrieval APIs/MCP | Standalone search fallbacks, not session-model server tools. |
|
|
73
|
+
|
|
74
|
+
Official references:
|
|
75
|
+
|
|
76
|
+
Custom xAI-compatible Responses entries declare both
|
|
77
|
+
`supportsWebSearchPreview: true` and `webSearchToolType: "web_search"`.
|
|
78
|
+
The type selection belongs to the model compatibility entry, not a provider-id
|
|
79
|
+
or hostname guess. OpenAI-compatible endpoints that only support the legacy
|
|
80
|
+
preview type can retain `webSearchToolType: "web_search_preview"`.
|
|
81
|
+
|
|
82
|
+
- [Anthropic tool and replay](https://platform.claude.com/docs/en/agents-and-tools/tool-use/web-search-tool)
|
|
83
|
+
- [OpenAI Responses search](https://developers.openai.com/api/docs/guides/tools-web-search)
|
|
84
|
+
- [Codex cached/live search](https://developers.openai.com/codex/config-basic/)
|
|
85
|
+
- [xAI Responses search](https://docs.x.ai/developers/tools/web-search)
|
|
86
|
+
- [Google grounding](https://ai.google.dev/gemini-api/docs/google-search)
|
|
87
|
+
- [Google SDK examples](https://github.com/googleapis/js-genai/blob/main/codegen_instructions.md)
|
|
88
|
+
- [OpenRouter server search](https://openrouter.ai/docs/guides/features/server-tools/web-search)
|
|
89
|
+
- [Moonshot built-in flow](https://platform.kimi.ai/docs/guide/use-web-search)
|
|
90
|
+
- [Kimi coding endpoint](https://www.kimi.com/code/docs/en/third-party-tools/claude-code)
|
|
91
|
+
- [DeepSeek Claude Code integration](https://api-docs.deepseek.com/quick_start/agent_integrations/claude_code)
|
|
92
|
+
- [DeepSeek Responses compatibility: built-in search ignored](https://api-docs.deepseek.com/guides/responses_api#tools)
|
|
93
|
+
- [Z.AI PAYG chat search](https://docs.z.ai/guides/tools/web-search)
|
|
94
|
+
- [GLM coding-plan search MCP](https://docs.z.ai/devpack/mcp/search-mcp-server)
|
|
95
|
+
- [GLM Claude Code / Anthropic integration](https://docs.z.ai/devpack/tool/claude)
|
|
96
|
+
- [Perplexity migration and conversation contract](https://docs.perplexity.ai/docs/sonar/quickstart)
|
|
97
|
+
|
|
98
|
+
The `nativeModel` setting and cheaper same-provider defaults apply only to
|
|
99
|
+
auxiliary search on routes without an in-turn server tool. They cannot
|
|
100
|
+
preempt server search: a different `nativeModel` setting is ignored on an enabled
|
|
101
|
+
server-search route. If an eligible route needs the standalone fallback,
|
|
102
|
+
that retry uses the session model rather than swapping to a cheaper model.
|
|
103
|
+
|
|
16
104
|
## Subscriptions
|
|
17
105
|
|
|
18
106
|
Use `/login` in interactive mode, then select a provider:
|