@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
|
@@ -73,6 +73,7 @@ import { ExecuteToolError, ExtensionRunner, wrapRegisteredTools, } from "./exten
|
|
|
73
73
|
import { projectKernelPrelude } from "./extensions/kernel-prelude.js";
|
|
74
74
|
import { emitSessionShutdownEvent } from "./extensions/runner.js";
|
|
75
75
|
import { normalizeToolExposure, RUNTIME_EXTENSION_PATH } from "./extensions/types.js";
|
|
76
|
+
import { createUserActivitySource, emitUserActivity } from "./extensions/user-activity.js";
|
|
76
77
|
import { deliveryIdOf, ExternalAdmission } from "./external-admission.js";
|
|
77
78
|
import { shouldWarnHighReasoning } from "./high-reasoning-warning.js";
|
|
78
79
|
import { LazyToolActivation } from "./lazy-tool-activation.js";
|
|
@@ -98,12 +99,13 @@ import { SelectorCooldowns } from "./retry-fallback/cooldown.js";
|
|
|
98
99
|
import { classifyRateLimitedWait, degradeWithoutFallback, nextInTurnDelayMs, probeBackSchedule, } from "./retry-fallback/hint-policy.js";
|
|
99
100
|
import { createFallbackLogger } from "./retry-fallback/log.js";
|
|
100
101
|
import { ProbeBackScheduler } from "./retry-fallback/probe-scheduler.js";
|
|
102
|
+
import { AccountUsageLimitError, describeUsageLimitEntry, describeUsageLimitMessage, usageLimitScope, } from "./retry-fallback/usage-limit.js";
|
|
101
103
|
import { validateFallbackChains } from "./retry-fallback/validate.js";
|
|
102
104
|
import { isSessionBusySnapshot, WakeSourceTracker } from "./session-activity.js";
|
|
103
105
|
import { createSessionControlActions } from "./session-control-actions.js";
|
|
104
106
|
import { computeSessionFailureReport } from "./session-failure-report.js";
|
|
105
107
|
import { createSessionLogger } from "./session-log.js";
|
|
106
|
-
import { CURRENT_SESSION_VERSION, getLatestCompactionEntry, SessionManager, } from "./session-manager.js";
|
|
108
|
+
import { CURRENT_SESSION_VERSION, getLatestCompactionEntry, getSessionContextEntryId, SessionManager, } from "./session-manager.js";
|
|
107
109
|
import { generateSessionTitle, sessionTitleRetryPolicy, shouldSkipSessionTitle } from "./session-title-generator.js";
|
|
108
110
|
import { SessionWorkBarrier } from "./session-work-barrier.js";
|
|
109
111
|
import { DEFAULT_STREAM_START_TIMEOUT_MS, DEFAULT_TOOL_NAMES, } from "./settings-manager.js";
|
|
@@ -423,6 +425,7 @@ export class AgentSession {
|
|
|
423
425
|
// (which rebuilds the base prompt with discovered skills) have run.
|
|
424
426
|
this._sessionStartSettled = Promise.resolve();
|
|
425
427
|
this._promptCachePrefixBuilds = new PromptCachePrefixBuilds();
|
|
428
|
+
this._userActivity = createUserActivitySource();
|
|
426
429
|
this._extensionMode = "print";
|
|
427
430
|
this._providerRequiredNoticed = false;
|
|
428
431
|
this._probeBackLaneSeq = 0;
|
|
@@ -571,7 +574,7 @@ export class AgentSession {
|
|
|
571
574
|
// Cooldowns, probe schedules, and circuits measure elapsed time, so they all
|
|
572
575
|
// run on the monotonic clock; a wall-clock jump never parks or releases an entry.
|
|
573
576
|
this._fallbackNow = config.fallbackNow ?? monotonicNow;
|
|
574
|
-
this._selectorCooldowns = new SelectorCooldowns(this._fallbackNow);
|
|
577
|
+
this._selectorCooldowns = new SelectorCooldowns(this._fallbackNow, undefined, config.fallbackWallNow ?? config.fallbackNow ?? Date.now);
|
|
575
578
|
const lease = () => this._fallbackCircuitsLease;
|
|
576
579
|
this._fallbackCircuits = createFallbackCircuitAccess({
|
|
577
580
|
// Read on every use: the hold is taken as construction's last step.
|
|
@@ -742,19 +745,35 @@ export class AgentSession {
|
|
|
742
745
|
* `compaction.model` override ("provider/model"), that model is used for the
|
|
743
746
|
* summarization call instead of the session model — this is what lets an
|
|
744
747
|
* SDK-owned lane (anthropic-subscription) compact on a cheaper/different model.
|
|
745
|
-
*
|
|
746
|
-
*
|
|
748
|
+
* Unset, malformed, or unknown overrides keep a usable session model.
|
|
749
|
+
* A spent account instead uses the held target or another authenticated model.
|
|
747
750
|
*/
|
|
748
751
|
_resolveCompactionModel(sessionModel) {
|
|
749
752
|
const override = this._getCompactionSettings().model;
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
753
|
+
const slash = override?.indexOf("/") ?? -1;
|
|
754
|
+
const configured = override && slash > 0
|
|
755
|
+
? this._modelRuntime.getModel(override.slice(0, slash), override.slice(slash + 1))
|
|
756
|
+
: undefined;
|
|
757
|
+
const usable = (model) => !this._retryFallback.isProviderSpent(model.provider);
|
|
758
|
+
// Preserve the explicit override and original-model geometry on healthy sessions.
|
|
759
|
+
// A held downswitch uses its target only when the original account cannot serve.
|
|
760
|
+
if (usable(sessionModel))
|
|
761
|
+
return configured && usable(configured) ? configured : sessionModel;
|
|
762
|
+
const pending = this._pendingModelSwitch?.model;
|
|
763
|
+
if (pending && usable(pending))
|
|
764
|
+
return pending;
|
|
765
|
+
if (configured && usable(configured))
|
|
766
|
+
return configured;
|
|
767
|
+
const requiredContextWindow = estimateMessagesTokens(filterContextExcludedMessages(this._runtimeMessages())) +
|
|
768
|
+
this._getCompactionSettings().reserveTokens;
|
|
769
|
+
const available = this._modelRuntime
|
|
770
|
+
.getModels()
|
|
771
|
+
.find((model) => usable(model) &&
|
|
772
|
+
model.contextWindow >= requiredContextWindow &&
|
|
773
|
+
this._modelRuntime.hasConfiguredAuth(model.provider));
|
|
774
|
+
if (available)
|
|
775
|
+
return available;
|
|
776
|
+
throw new AccountUsageLimitError(sessionModel.provider, this._retryFallback.accountError(sessionModel.provider));
|
|
758
777
|
}
|
|
759
778
|
async _getCompactionRequestAuth(model, signal, callbacks) {
|
|
760
779
|
const auth = await this._getSummarizationRequestAuth(model, signal, callbacks);
|
|
@@ -1365,6 +1384,39 @@ export class AgentSession {
|
|
|
1365
1384
|
}
|
|
1366
1385
|
_emit(event) {
|
|
1367
1386
|
this._logSessionEvent(event);
|
|
1387
|
+
if (event.type === "entry_appended") {
|
|
1388
|
+
event = { ...event, entry: describeUsageLimitEntry(event.entry) };
|
|
1389
|
+
}
|
|
1390
|
+
if (event.type === "retry_fallback_exhausted" && usageLimitScope(event.lastError) === "account") {
|
|
1391
|
+
event = {
|
|
1392
|
+
...event,
|
|
1393
|
+
lastError: new AccountUsageLimitError(this.model?.provider ?? "", event.lastError).message,
|
|
1394
|
+
};
|
|
1395
|
+
}
|
|
1396
|
+
if (event.type === "agent_end") {
|
|
1397
|
+
event = {
|
|
1398
|
+
...event,
|
|
1399
|
+
messages: event.messages.map((message) => message.role === "assistant" ? describeUsageLimitMessage(message) : message),
|
|
1400
|
+
};
|
|
1401
|
+
}
|
|
1402
|
+
// Keep provider text on the engine message for classification and in session.log,
|
|
1403
|
+
// but publish only the existing user-facing description to clients.
|
|
1404
|
+
if ((event.type === "message_end" || event.type === "message_start" || event.type === "message_update") &&
|
|
1405
|
+
event.message.role === "assistant") {
|
|
1406
|
+
const message = describeUsageLimitMessage(event.message);
|
|
1407
|
+
event =
|
|
1408
|
+
event.type === "message_update"
|
|
1409
|
+
? {
|
|
1410
|
+
...event,
|
|
1411
|
+
message,
|
|
1412
|
+
assistantMessageEvent: {
|
|
1413
|
+
...event.assistantMessageEvent,
|
|
1414
|
+
...("error" in event.assistantMessageEvent ? { error: message } : {}),
|
|
1415
|
+
...("partial" in event.assistantMessageEvent ? { partial: message } : {}),
|
|
1416
|
+
},
|
|
1417
|
+
}
|
|
1418
|
+
: { ...event, message };
|
|
1419
|
+
}
|
|
1368
1420
|
for (const l of [...this._eventListeners]) {
|
|
1369
1421
|
l(event);
|
|
1370
1422
|
}
|
|
@@ -2213,7 +2265,9 @@ export class AgentSession {
|
|
|
2213
2265
|
this._clearCircuitProbeWatchdog();
|
|
2214
2266
|
// Billing and quota exhaustion make the entry unusable for every session,
|
|
2215
2267
|
// whether or not the chain has a candidate left.
|
|
2216
|
-
if (msg.stopReason === "error" &&
|
|
2268
|
+
if (msg.stopReason === "error" &&
|
|
2269
|
+
(isHealthExhaustionFailure(msg.errorMessage) || usageLimitScope(msg.errorMessage) === "account") &&
|
|
2270
|
+
this.model) {
|
|
2217
2271
|
this._retryFallback.noteHealthFailure(this.model, this.thinkingLevel, { errorMessage: msg.errorMessage });
|
|
2218
2272
|
}
|
|
2219
2273
|
const requiredAutoCompaction = this._getRequiredAutoCompactionReason(msg);
|
|
@@ -3275,6 +3329,10 @@ export class AgentSession {
|
|
|
3275
3329
|
onExtensionEvent(channel, handler) {
|
|
3276
3330
|
return this._resourceLoader.onExtensionEvent?.(channel, handler) ?? (() => { });
|
|
3277
3331
|
}
|
|
3332
|
+
/** Host input entry point; not exposed by the extension API or its event bus. */
|
|
3333
|
+
recordUserActivity(kind) {
|
|
3334
|
+
this._userActivity.emit(kind);
|
|
3335
|
+
}
|
|
3278
3336
|
/** Publish on the internal event bus shared by this session's extensions. */
|
|
3279
3337
|
emitExtensionEvent(channel, data) {
|
|
3280
3338
|
this._resourceLoader.emitExtensionEvent?.(channel, data);
|
|
@@ -3467,6 +3525,8 @@ export class AgentSession {
|
|
|
3467
3525
|
submittedByCommand: options?.source === "extension",
|
|
3468
3526
|
});
|
|
3469
3527
|
try {
|
|
3528
|
+
if (options?.source !== "extension")
|
|
3529
|
+
emitUserActivity(this, "prompt");
|
|
3470
3530
|
await this._prompt(text, {
|
|
3471
3531
|
...options,
|
|
3472
3532
|
promptDisposition: (disposition) => {
|
|
@@ -3573,6 +3633,15 @@ export class AgentSession {
|
|
|
3573
3633
|
if (!this.isStreaming) {
|
|
3574
3634
|
try {
|
|
3575
3635
|
await this._maybeRestoreFallbackPrimary();
|
|
3636
|
+
const model = this.model;
|
|
3637
|
+
if (model && !this._pendingModelSwitch && this._retryFallback.isProviderSpent(model.provider)) {
|
|
3638
|
+
this._retryFallback.resetTurn();
|
|
3639
|
+
const errorMessage = this._retryFallback.accountError(model.provider);
|
|
3640
|
+
// Routing around a remembered failure did not observe another provider failure.
|
|
3641
|
+
if (!(await this._retryFallback.tryFallback("transient", { errorMessage }, false))) {
|
|
3642
|
+
throw new AccountUsageLimitError(model.provider, errorMessage);
|
|
3643
|
+
}
|
|
3644
|
+
}
|
|
3576
3645
|
}
|
|
3577
3646
|
catch (error) {
|
|
3578
3647
|
if (ownsPromptStart)
|
|
@@ -4054,6 +4123,8 @@ export class AgentSession {
|
|
|
4054
4123
|
* `input` handlers, skill/template expansion, then the recovery-ordered enqueue.
|
|
4055
4124
|
*/
|
|
4056
4125
|
async _queueUserInput(text, images, behavior, options) {
|
|
4126
|
+
if (options?.source !== "extension")
|
|
4127
|
+
emitUserActivity(this, "prompt");
|
|
4057
4128
|
// Check for extension commands (cannot be queued)
|
|
4058
4129
|
if (text.startsWith("/")) {
|
|
4059
4130
|
this._throwIfExtensionCommand(text);
|
|
@@ -4880,7 +4951,9 @@ export class AgentSession {
|
|
|
4880
4951
|
}
|
|
4881
4952
|
/** Whether a recorded resume requirement still blocks: the current context does not fit the current model. */
|
|
4882
4953
|
_resumeRequirementStillBlocks(model, settings) {
|
|
4883
|
-
|
|
4954
|
+
// Kept assistant usage predates an accepted checkpoint. The projection
|
|
4955
|
+
// estimates its summary and suffix until a fresh provider response supersedes it.
|
|
4956
|
+
const currentContext = estimateProjectedContextTokens(this.sessionManager.buildSessionProjection(), this.sessionManager.getBranch()).tokens;
|
|
4884
4957
|
return !projectModelUsabilityBudget({
|
|
4885
4958
|
model,
|
|
4886
4959
|
systemPrompt: this.agent.state.systemPrompt,
|
|
@@ -6015,6 +6088,7 @@ export class AgentSession {
|
|
|
6015
6088
|
}
|
|
6016
6089
|
throw new Error("Nothing to compact (session too small)");
|
|
6017
6090
|
}
|
|
6091
|
+
const compactionModel = this._resolveCompactionModel(model);
|
|
6018
6092
|
if (this._extensionRunner.hasHandlers("session_before_compact")) {
|
|
6019
6093
|
const messagesBeforeExtension = new Set(this.agent.state.messages);
|
|
6020
6094
|
const extensionResult = (await this._extensionRunner.emit({
|
|
@@ -6050,7 +6124,6 @@ export class AgentSession {
|
|
|
6050
6124
|
// (lifecycle record, preparation, branch) still tracks the session
|
|
6051
6125
|
// model. Falls back to the session model when the override is unset
|
|
6052
6126
|
// or cannot be resolved.
|
|
6053
|
-
const compactionModel = this._resolveCompactionModel(model);
|
|
6054
6127
|
const { model: requestModel, apiKey, headers, extraBody, env, thinkingLevel, } = await this._getCompactionRequestAuth(compactionModel, signal, this._summarizationRetryCallbacks({ source: "compaction", reason: request.reason }));
|
|
6055
6128
|
compactionResult = await this._runDefaultCompaction(preparation, requestModel, apiKey, headers, extraBody, request.customInstructions, signal, env, request.reason, thinkingLevel);
|
|
6056
6129
|
}
|
|
@@ -6371,13 +6444,13 @@ export class AgentSession {
|
|
|
6371
6444
|
}
|
|
6372
6445
|
this._resumeCompactionRequirement = undefined;
|
|
6373
6446
|
}
|
|
6374
|
-
const contextTokens = estimateContextTokens(filterContextExcludedMessages(this.sessionManager.buildSessionContext().messages)).tokens;
|
|
6375
6447
|
const compacted = assistantMessage
|
|
6376
6448
|
? await this._checkCompaction(assistantMessage, skipAbortedCheck, inlineReason, retryAfterCompaction)
|
|
6377
6449
|
: false;
|
|
6378
6450
|
if (compacted || (assistantMessage && this._postCompactionUsageExemptAssistants.has(assistantMessage))) {
|
|
6379
6451
|
return compacted;
|
|
6380
6452
|
}
|
|
6453
|
+
const contextTokens = this._estimatePrePromptContextTokens();
|
|
6381
6454
|
// Under a virtual selection, the physical model of the latest response supplies the limits;
|
|
6382
6455
|
// before one, the virtual model's declared limits apply. An undeclared window (<= 0) is unknown, so
|
|
6383
6456
|
// admission never refuses a turn as "already over the threshold" (#2677). Compaction after a turn
|
|
@@ -6389,13 +6462,7 @@ export class AgentSession {
|
|
|
6389
6462
|
!shouldCompact(contextTokens, limitsModel.contextWindow, settings)) {
|
|
6390
6463
|
return false;
|
|
6391
6464
|
}
|
|
6392
|
-
const latestCompaction = getLatestCompactionEntry(this.sessionManager.getBranch());
|
|
6393
6465
|
const assistantBeforeLatestCompaction = assistantMessage !== undefined && this._isAssistantFromBeforeLatestCompaction(assistantMessage);
|
|
6394
|
-
const hasPostCompactionCustomState = latestCompaction !== null &&
|
|
6395
|
-
this.agent.state.messages.some((message) => message.role === "custom" && message.timestamp >= new Date(latestCompaction.timestamp).getTime());
|
|
6396
|
-
if (assistantBeforeLatestCompaction && !hasPostCompactionCustomState) {
|
|
6397
|
-
return false;
|
|
6398
|
-
}
|
|
6399
6466
|
if (assistantBeforeLatestCompaction && assistantMessage) {
|
|
6400
6467
|
const compacted = await this._runPrePromptCompaction(assistantMessage, skipAbortedCheck, inlineReason);
|
|
6401
6468
|
if (compacted)
|
|
@@ -6406,6 +6473,32 @@ export class AgentSession {
|
|
|
6406
6473
|
}
|
|
6407
6474
|
throw new RequiredCompactionError();
|
|
6408
6475
|
}
|
|
6476
|
+
/** Share resume's branch provenance rule without projecting trusted usage on every request. */
|
|
6477
|
+
_estimatePrePromptContextTokens(additions = []) {
|
|
6478
|
+
const branch = this.sessionManager.getBranch();
|
|
6479
|
+
const messages = [...this.sessionManager.buildSessionContext().messages, ...additions];
|
|
6480
|
+
const estimate = estimateContextTokens(messages);
|
|
6481
|
+
const usageMessage = estimate.lastUsageIndex === null ? undefined : messages[estimate.lastUsageIndex];
|
|
6482
|
+
const usageEntryId = usageMessage ? getSessionContextEntryId(usageMessage) : undefined;
|
|
6483
|
+
let needsProjection = false;
|
|
6484
|
+
for (let i = branch.length - 1; i >= 0; i--) {
|
|
6485
|
+
const entry = branch[i];
|
|
6486
|
+
if (usageEntryId !== undefined && entry.id === usageEntryId)
|
|
6487
|
+
break;
|
|
6488
|
+
if (entry.type === "compaction" || entry.type === "context_edit") {
|
|
6489
|
+
needsProjection = true;
|
|
6490
|
+
break;
|
|
6491
|
+
}
|
|
6492
|
+
}
|
|
6493
|
+
if (!needsProjection)
|
|
6494
|
+
return estimate.tokens;
|
|
6495
|
+
if (this._prePromptProjection?.branch !== branch) {
|
|
6496
|
+
this._prePromptProjection = { branch, projection: this.sessionManager.buildSessionProjection() };
|
|
6497
|
+
}
|
|
6498
|
+
const { projection } = this._prePromptProjection;
|
|
6499
|
+
return estimateProjectedContextTokens({ ...projection, messages: [...projection.messages, ...additions] }, branch)
|
|
6500
|
+
.tokens;
|
|
6501
|
+
}
|
|
6409
6502
|
/**
|
|
6410
6503
|
* Pending queued input the provider will also carry on this turn. Steering and
|
|
6411
6504
|
* follow-up text enqueued after the admission projection was assembled - by a
|
|
@@ -6448,21 +6541,12 @@ export class AgentSession {
|
|
|
6448
6541
|
const settings = this._getCompactionSettings();
|
|
6449
6542
|
const reserveTokens = resolveEffectiveReserveTokens(model.contextWindow, settings);
|
|
6450
6543
|
const isOversized = () => {
|
|
6451
|
-
const
|
|
6452
|
-
...this.agent.state.messages,
|
|
6544
|
+
const contextTokens = this._estimatePrePromptContextTokens([
|
|
6453
6545
|
...messages,
|
|
6454
6546
|
// Re-read the queues on every sample: this closure is evaluated again
|
|
6455
6547
|
// after compaction, when more input may have arrived.
|
|
6456
6548
|
...this._pendingQueuedInputMessages(),
|
|
6457
6549
|
]);
|
|
6458
|
-
const estimate = estimateContextTokens(providerMessages);
|
|
6459
|
-
const usageMessage = estimate.lastUsageIndex === null ? undefined : providerMessages[estimate.lastUsageIndex];
|
|
6460
|
-
// Kept assistant usage can describe the pre-compaction request. Once a
|
|
6461
|
-
// compaction boundary exists, fall back to byte-derived message estimates
|
|
6462
|
-
// until a provider response refreshes that usage.
|
|
6463
|
-
const contextTokens = usageMessage?.role === "assistant" && this._isAssistantFromBeforeLatestCompaction(usageMessage)
|
|
6464
|
-
? estimateMessagesTokens(providerMessages)
|
|
6465
|
-
: estimate.tokens;
|
|
6466
6550
|
return contextTokens > model.contextWindow - reserveTokens;
|
|
6467
6551
|
};
|
|
6468
6552
|
if (!settings.enabled || !isOversized())
|
|
@@ -6638,8 +6722,7 @@ export class AgentSession {
|
|
|
6638
6722
|
let contextTokens;
|
|
6639
6723
|
let staleUsageContentTokens;
|
|
6640
6724
|
if (inlineReason) {
|
|
6641
|
-
|
|
6642
|
-
contextTokens = estimateContextTokens(messages).tokens;
|
|
6725
|
+
contextTokens = this._estimatePrePromptContextTokens();
|
|
6643
6726
|
}
|
|
6644
6727
|
else {
|
|
6645
6728
|
const directContextTokens = assistantMessage.usage ? calculateContextTokens(assistantMessage.usage) : 0;
|
|
@@ -6786,12 +6869,7 @@ export class AgentSession {
|
|
|
6786
6869
|
const settings = this._getCompactionSettings();
|
|
6787
6870
|
if (!model || !settings.enabled)
|
|
6788
6871
|
return;
|
|
6789
|
-
const
|
|
6790
|
-
const estimate = estimateContextTokens(canonicalMessages);
|
|
6791
|
-
const usageMessage = estimate.lastUsageIndex === null ? undefined : canonicalMessages[estimate.lastUsageIndex];
|
|
6792
|
-
const contextTokens = usageMessage?.role === "assistant" && this._isAssistantFromBeforeLatestCompaction(usageMessage)
|
|
6793
|
-
? estimateMessagesTokens(canonicalMessages)
|
|
6794
|
-
: estimate.tokens;
|
|
6872
|
+
const contextTokens = this._estimatePrePromptContextTokens();
|
|
6795
6873
|
// An explicit user prompt passes the proactive policy in the compaction
|
|
6796
6874
|
// extension's before_agent_start, which automatic continuations never emit.
|
|
6797
6875
|
// Sampling the same predicate here makes both routes compact at the same
|
|
@@ -7379,9 +7457,13 @@ export class AgentSession {
|
|
|
7379
7457
|
},
|
|
7380
7458
|
getContextUsage: () => this.getContextUsage(),
|
|
7381
7459
|
getCompactionSettings: () => this._getCompactionSettings(),
|
|
7460
|
+
getCompactionModel: () => this.model && this._retryFallback.isProviderSpent(this.model.provider)
|
|
7461
|
+
? this._resolveCompactionModel(this.model)
|
|
7462
|
+
: this.model,
|
|
7382
7463
|
getPromptCacheSafeWaitSeconds: () => this.resolvePromptCacheSafeWaitSeconds(),
|
|
7383
7464
|
getPromptCacheGoalBackstopMaxSeconds: () => this.settingsManager.getPromptCacheGoalBackstopMaxSeconds(),
|
|
7384
7465
|
getPromptCacheKeepAliveSettings: () => this.settingsManager.getPromptCacheKeepAliveSettings(),
|
|
7466
|
+
onUserActivity: (listener) => this._userActivity.subscribe(listener),
|
|
7385
7467
|
getLookAtSettings: () => {
|
|
7386
7468
|
const global = this.settingsManager.getGlobalSettings().lookAt;
|
|
7387
7469
|
const project = this.settingsManager.getProjectSettings().lookAt;
|
|
@@ -8018,6 +8100,7 @@ export class AgentSession {
|
|
|
8018
8100
|
return (describeProviderFailureForUser(message.errorMessage, {
|
|
8019
8101
|
attempts,
|
|
8020
8102
|
model,
|
|
8103
|
+
usageLimit: usageLimitScope(message.errorMessage),
|
|
8021
8104
|
recovery: this._retryFallback.hasConfiguredChain() ? "chain-exhausted" : "no-fallback-configured",
|
|
8022
8105
|
}) ?? (message.errorMessage === undefined ? undefined : stripTurnRetrySuppressionPrefix(message.errorMessage)));
|
|
8023
8106
|
}
|
|
@@ -8109,7 +8192,31 @@ export class AgentSession {
|
|
|
8109
8192
|
throw error;
|
|
8110
8193
|
}
|
|
8111
8194
|
};
|
|
8112
|
-
if (
|
|
8195
|
+
if (usageLimitScope(errorMessage) === "account") {
|
|
8196
|
+
switchedFallback = await tryFallback(isBillingErrorMessage(errorMessage) ? "billing" : "transient", {
|
|
8197
|
+
errorMessage,
|
|
8198
|
+
retryAfterMs: this._getProviderRetryDelayMs(errorMessage),
|
|
8199
|
+
});
|
|
8200
|
+
if (!switchedFallback) {
|
|
8201
|
+
const chainKey = this._retryFallback.exhaustedChainKey;
|
|
8202
|
+
if (chainKey)
|
|
8203
|
+
this._emit({ type: "retry_fallback_exhausted", chainKey, lastError: errorMessage });
|
|
8204
|
+
if (this._retryAttempt > 0) {
|
|
8205
|
+
this._emit({
|
|
8206
|
+
type: "auto_retry_end",
|
|
8207
|
+
success: false,
|
|
8208
|
+
attempt: this._retryAttempt,
|
|
8209
|
+
finalError: this._terminalFailureText(message, this._retryAttempt),
|
|
8210
|
+
});
|
|
8211
|
+
}
|
|
8212
|
+
this._retryAttempt = 0;
|
|
8213
|
+
this._resetHintTierState();
|
|
8214
|
+
this._resolveRetry();
|
|
8215
|
+
return "not-handled";
|
|
8216
|
+
}
|
|
8217
|
+
this._retryAttempt = 1;
|
|
8218
|
+
}
|
|
8219
|
+
else if (sameModelRemint) {
|
|
8113
8220
|
this._retryAttempt++;
|
|
8114
8221
|
if (this._retryAttempt > retryProfile.turn.maxRetries) {
|
|
8115
8222
|
if (this._retryAttempt > 1) {
|
|
@@ -8498,7 +8605,9 @@ export class AgentSession {
|
|
|
8498
8605
|
attempt: this._retryAttempt,
|
|
8499
8606
|
maxAttempts: retryProfile.turn.maxRetries,
|
|
8500
8607
|
delayMs,
|
|
8501
|
-
errorMessage
|
|
8608
|
+
errorMessage: usageLimitScope(errorMessage) === "account"
|
|
8609
|
+
? new AccountUsageLimitError(message.provider, errorMessage).message
|
|
8610
|
+
: errorMessage,
|
|
8502
8611
|
});
|
|
8503
8612
|
// Remove error message from agent state (keep in session for history)
|
|
8504
8613
|
const messages = this.agent.state.messages;
|
|
@@ -8535,7 +8644,7 @@ export class AgentSession {
|
|
|
8535
8644
|
// before the continuation so a rejected compaction never admits that model.
|
|
8536
8645
|
const model = this.model;
|
|
8537
8646
|
const compactionSettings = this._getCompactionSettings();
|
|
8538
|
-
const contextTokens =
|
|
8647
|
+
const contextTokens = this._estimatePrePromptContextTokens();
|
|
8539
8648
|
if (compactionSettings.enabled &&
|
|
8540
8649
|
model &&
|
|
8541
8650
|
shouldCompact(contextTokens, model.contextWindow, compactionSettings)) {
|
|
@@ -8,6 +8,7 @@ import { dropFailedAssistantTurns, getCurrentSystemMessage, normalizeContext, re
|
|
|
8
8
|
import { streamSimple } from "@earendil-works/pi-ai/compat";
|
|
9
9
|
import { estimateContextTokens as estimateProviderContextTokens } from "@earendil-works/pi-ai/utils/estimate";
|
|
10
10
|
import { resolveEffectiveReserveTokens } from "../extensions/builtin/compaction/policy.js";
|
|
11
|
+
import { withoutServerSearch } from "../extensions/builtin/server-search-fallback.js";
|
|
11
12
|
import { convertToLlm, isContextExcludedCustomMessage } from "../messages.js";
|
|
12
13
|
import { buildSessionProjection, sessionEntryToContextMessages, } from "../session-manager.js";
|
|
13
14
|
import { estimateCacheKey, isTransientMessage, serializeForEstimate } from "./estimate-cache-key.js";
|
|
@@ -629,6 +630,7 @@ export async function completeSummarization(model, context, options, streamFn, r
|
|
|
629
630
|
// opt into short retention for an exact provider-context prefix.
|
|
630
631
|
const isolatedOptions = {
|
|
631
632
|
...options,
|
|
633
|
+
onPayload: withoutServerSearch(options.onPayload, options.cacheRetention),
|
|
632
634
|
cacheRetention: options.cacheRetention ?? "none",
|
|
633
635
|
affinitySessionId: options.affinitySessionId ?? options.sessionId,
|
|
634
636
|
sessionId: uuidv7(),
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { hasServerSearchFailed, isServerSearchDisabled, isServerSearchFallback, markServerSearchFallback, } from "../server-search-fallback.js";
|
|
1
2
|
const WEB_SEARCH_MAX_USES = 8;
|
|
2
3
|
const ENABLE_ENV = "PI_ANTHROPIC_WEB_SEARCH";
|
|
3
4
|
const ALLOWED_DOMAINS_ENV = "PI_ANTHROPIC_WEB_SEARCH_ALLOWED_DOMAINS";
|
|
@@ -38,25 +39,26 @@ function resolveTarget(target) {
|
|
|
38
39
|
}
|
|
39
40
|
return target;
|
|
40
41
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
// Mirrors pi-ai's `AnthropicMessagesCompat.supportsWebSearch` default:
|
|
50
|
-
// first-party api.anthropic.com only. Anthropic-compatible endpoints (e.g.,
|
|
51
|
-
// kimi-coding) may execute the server-side search but reject the replayed
|
|
52
|
-
// server_tool_use / web_search_tool_result blocks on the next request.
|
|
42
|
+
// https://platform.claude.com/docs/en/agents-and-tools/tool-use/web-search-tool
|
|
43
|
+
// supportsWebSearch means BOTH main-request search and unchanged encrypted-result
|
|
44
|
+
// replay. Preserve main's first-party API/Claude OAuth default; gateways opt in.
|
|
45
|
+
// ccapi's Messages route passed a real search + replay for senpi#3122; opt its
|
|
46
|
+
// provider/model entries in with compat.supportsWebSearch: true. Never infer that
|
|
47
|
+
// compatibility from a proxy hostname. Other compatible routes remain explicit.
|
|
53
48
|
export function supportsNativeAnthropicWebSearch(target) {
|
|
54
49
|
const model = resolveTarget(target);
|
|
55
50
|
if (model?.api !== "anthropic-messages") {
|
|
56
51
|
return false;
|
|
57
52
|
}
|
|
58
53
|
const compat = model.compat;
|
|
59
|
-
|
|
54
|
+
if (compat?.supportsWebSearch !== undefined)
|
|
55
|
+
return compat.supportsWebSearch;
|
|
56
|
+
try {
|
|
57
|
+
return new URL(model.baseUrl).hostname === "api.anthropic.com";
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
60
62
|
}
|
|
61
63
|
function parseDomainListEnv(envVar) {
|
|
62
64
|
const envValue = process.env[envVar];
|
|
@@ -132,6 +134,8 @@ export function addAnthropicWebSearchToPayload(target, payload) {
|
|
|
132
134
|
if (!isRecord(payload)) {
|
|
133
135
|
return payload;
|
|
134
136
|
}
|
|
137
|
+
if (isServerSearchDisabled(payload) || isServerSearchFallback(payload))
|
|
138
|
+
return stripNativeAnthropicWebSearch(payload);
|
|
135
139
|
if (!supportsNativeAnthropicWebSearch(model)) {
|
|
136
140
|
// Anthropic-compatible endpoints (e.g., kimi-coding) may execute the
|
|
137
141
|
// server-side search but reject the replayed server_tool_use /
|
|
@@ -175,7 +179,11 @@ Prefer web_search over guessing when freshness matters.
|
|
|
175
179
|
`;
|
|
176
180
|
export default function anthropicWebSearchExtension(pi) {
|
|
177
181
|
pi.on("before_provider_request", (event, ctx) => {
|
|
178
|
-
|
|
182
|
+
const model = event.model ?? ctx.model;
|
|
183
|
+
const payload = hasServerSearchFailed(ctx.sessionManager, model)
|
|
184
|
+
? markServerSearchFallback(event.payload)
|
|
185
|
+
: event.payload;
|
|
186
|
+
return addAnthropicWebSearchToPayload(model, payload);
|
|
179
187
|
});
|
|
180
188
|
pi.on("session_start", async (_event, ctx) => {
|
|
181
189
|
syncUi(ctx);
|
|
@@ -3,6 +3,7 @@ import { convertToLlm, filterContextExcludedMessages } from "../../../messages.j
|
|
|
3
3
|
import { noticeEntryRenderer } from "../../notice/index.js";
|
|
4
4
|
import { formatWarmTokenCount } from "../goal/cache-warm.js";
|
|
5
5
|
import { createSessionPrewarm } from "./session-prewarm.js";
|
|
6
|
+
import { createStartupActivityGate } from "./startup-activity-gate.js";
|
|
6
7
|
export const CACHE_KEEPALIVE_ENTRY_TYPE = "cache-keepalive";
|
|
7
8
|
export const CACHE_WARM_PING_EVENT = "cache_warm_ping";
|
|
8
9
|
export const renderCacheKeepAliveEntry = noticeEntryRenderer((entry) => {
|
|
@@ -20,6 +21,7 @@ export function createCacheKeepAliveExtension(dependencies = {}) {
|
|
|
20
21
|
const warm = dependencies.warmPromptCache ?? warmPromptCache;
|
|
21
22
|
return (pi) => {
|
|
22
23
|
const prewarm = createSessionPrewarm(pi, { warm, isPrewarmModel: dependencies.isPromptCachePrewarmModel });
|
|
24
|
+
const startupActivity = createStartupActivityGate();
|
|
23
25
|
let ctx;
|
|
24
26
|
let timer;
|
|
25
27
|
let inFlight = false;
|
|
@@ -57,7 +59,7 @@ export function createCacheKeepAliveExtension(dependencies = {}) {
|
|
|
57
59
|
}
|
|
58
60
|
}
|
|
59
61
|
function arm() {
|
|
60
|
-
if (parked || timer !== undefined || inFlight)
|
|
62
|
+
if (startupActivity.waiting || parked || timer !== undefined || inFlight)
|
|
61
63
|
return;
|
|
62
64
|
const current = ctx;
|
|
63
65
|
const settings = current?.getPromptCacheKeepAliveSettings?.();
|
|
@@ -119,7 +121,7 @@ export function createCacheKeepAliveExtension(dependencies = {}) {
|
|
|
119
121
|
const iteration = attempts;
|
|
120
122
|
const pingGeneration = generation;
|
|
121
123
|
try {
|
|
122
|
-
const preparation = await current.prepareProviderRequest?.(lastMessages);
|
|
124
|
+
const preparation = await current.prepareProviderRequest?.(lastMessages, { allowServerSearch: true });
|
|
123
125
|
if (pingGeneration !== generation) {
|
|
124
126
|
inFlight = false;
|
|
125
127
|
arm();
|
|
@@ -191,6 +193,7 @@ export function createCacheKeepAliveExtension(dependencies = {}) {
|
|
|
191
193
|
}
|
|
192
194
|
}
|
|
193
195
|
pi.on("session_start", (_event, nextCtx) => {
|
|
196
|
+
prewarm.start(nextCtx);
|
|
194
197
|
stop("session-restart");
|
|
195
198
|
ctx = nextCtx;
|
|
196
199
|
attempts = 0;
|
|
@@ -199,8 +202,7 @@ export function createCacheKeepAliveExtension(dependencies = {}) {
|
|
|
199
202
|
const usage = lastAssistantUsage(lastMessages);
|
|
200
203
|
lastUsage = usage;
|
|
201
204
|
lastCompletedAtMs = lastAssistantTimestamp(lastMessages);
|
|
202
|
-
arm
|
|
203
|
-
prewarm.start(nextCtx);
|
|
205
|
+
startupActivity.start(nextCtx, arm);
|
|
204
206
|
});
|
|
205
207
|
pi.on("agent_end", (event, nextCtx) => {
|
|
206
208
|
ctx = nextCtx;
|
|
@@ -211,6 +213,7 @@ export function createCacheKeepAliveExtension(dependencies = {}) {
|
|
|
211
213
|
return;
|
|
212
214
|
}
|
|
213
215
|
lastMessages = [...event.messages];
|
|
216
|
+
startupActivity.release();
|
|
214
217
|
lastUsage = usage;
|
|
215
218
|
lastCompletedAtMs = Date.now();
|
|
216
219
|
armOnSettle = true;
|
|
@@ -241,6 +244,7 @@ export function createCacheKeepAliveExtension(dependencies = {}) {
|
|
|
241
244
|
pi.on("input", () => stop("user-input"));
|
|
242
245
|
pi.on("session_shutdown", () => {
|
|
243
246
|
stop("session-dispose");
|
|
247
|
+
startupActivity.release();
|
|
244
248
|
prewarm.cancel();
|
|
245
249
|
ctx = undefined;
|
|
246
250
|
});
|
|
@@ -11,7 +11,8 @@ export interface SessionPrewarm {
|
|
|
11
11
|
cancel(): void;
|
|
12
12
|
}
|
|
13
13
|
/**
|
|
14
|
-
*
|
|
14
|
+
* Arm at session start, then prewarm once on the first draft edit (senpi#3140).
|
|
15
|
+
* A submitted prompt consumes the opportunity: the real request warms its own prefix.
|
|
15
16
|
* The request is the host's `getPromptCachePrefixRequest()` prefix, the first user turn's
|
|
16
17
|
* request with an empty conversation, because the platform reuses a prefix only up to a
|
|
17
18
|
* block boundary: a prewarmed system prompt that stops short of the turn's is never read.
|
|
@@ -2,7 +2,8 @@ import { isOpenAIResponsesPromptCacheModel, } from "@earendil-works/pi-ai";
|
|
|
2
2
|
import { PROMPT_CACHE_PREWARM_ENTRY_TYPE } from "./prewarm-entry.js";
|
|
3
3
|
export const PROMPT_CACHE_PREWARM_TIMEOUT_MS = 30_000;
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* Arm at session start, then prewarm once on the first draft edit (senpi#3140).
|
|
6
|
+
* A submitted prompt consumes the opportunity: the real request warms its own prefix.
|
|
6
7
|
* The request is the host's `getPromptCachePrefixRequest()` prefix, the first user turn's
|
|
7
8
|
* request with an empty conversation, because the platform reuses a prefix only up to a
|
|
8
9
|
* block boundary: a prewarmed system prompt that stops short of the turn's is never read.
|
|
@@ -15,6 +16,11 @@ export const PROMPT_CACHE_PREWARM_TIMEOUT_MS = 30_000;
|
|
|
15
16
|
export function createSessionPrewarm(pi, dependencies) {
|
|
16
17
|
const isPrewarmModel = dependencies.isPrewarmModel ?? isOpenAIResponsesPromptCacheModel;
|
|
17
18
|
let current;
|
|
19
|
+
let context;
|
|
20
|
+
let used = false;
|
|
21
|
+
let submitted = false;
|
|
22
|
+
let parked = false;
|
|
23
|
+
let unsubscribeActivity;
|
|
18
24
|
function cancel() {
|
|
19
25
|
current?.abort();
|
|
20
26
|
current = undefined;
|
|
@@ -30,6 +36,8 @@ export function createSessionPrewarm(pi, dependencies) {
|
|
|
30
36
|
return;
|
|
31
37
|
}
|
|
32
38
|
const { request } = prefix;
|
|
39
|
+
if (submitted || ctx.getPromptCacheKeepAliveSettings?.().enabled !== true)
|
|
40
|
+
return;
|
|
33
41
|
const result = await dependencies.warm(request.model, request.context, {
|
|
34
42
|
...request.options,
|
|
35
43
|
signal,
|
|
@@ -53,15 +61,55 @@ export function createSessionPrewarm(pi, dependencies) {
|
|
|
53
61
|
function append(data) {
|
|
54
62
|
pi.appendEntry(PROMPT_CACHE_PREWARM_ENTRY_TYPE, data);
|
|
55
63
|
}
|
|
64
|
+
function onActivity(kind) {
|
|
65
|
+
if (kind === "prompt") {
|
|
66
|
+
submitted = true;
|
|
67
|
+
used = true;
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (context === undefined || parked || used)
|
|
71
|
+
return;
|
|
72
|
+
used = true;
|
|
73
|
+
const ctx = context;
|
|
74
|
+
const model = ctx.model;
|
|
75
|
+
if (ctx.getPromptCacheKeepAliveSettings?.().enabled !== true ||
|
|
76
|
+
model === undefined ||
|
|
77
|
+
model.cacheRetention === "none" ||
|
|
78
|
+
!isPrewarmModel(model) ||
|
|
79
|
+
!ctx.isIdle() ||
|
|
80
|
+
ctx.hasPendingMessages())
|
|
81
|
+
return;
|
|
82
|
+
const controller = new AbortController();
|
|
83
|
+
current = controller;
|
|
84
|
+
void run(ctx, model, controller);
|
|
85
|
+
}
|
|
86
|
+
pi.on("model_select", (_event, ctx) => {
|
|
87
|
+
cancel();
|
|
88
|
+
context = ctx;
|
|
89
|
+
});
|
|
90
|
+
pi.on("session_parked", () => {
|
|
91
|
+
parked = true;
|
|
92
|
+
cancel();
|
|
93
|
+
});
|
|
94
|
+
pi.on("session_resumed", (_event, ctx) => {
|
|
95
|
+
parked = false;
|
|
96
|
+
context = ctx;
|
|
97
|
+
});
|
|
98
|
+
pi.on("session_shutdown", () => {
|
|
99
|
+
unsubscribeActivity?.();
|
|
100
|
+
unsubscribeActivity = undefined;
|
|
101
|
+
context = undefined;
|
|
102
|
+
cancel();
|
|
103
|
+
});
|
|
56
104
|
return {
|
|
57
105
|
start(ctx) {
|
|
58
106
|
cancel();
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
107
|
+
unsubscribeActivity?.();
|
|
108
|
+
context = ctx;
|
|
109
|
+
used = false;
|
|
110
|
+
submitted = false;
|
|
111
|
+
parked = false;
|
|
112
|
+
unsubscribeActivity = ctx.onUserActivity?.(onActivity);
|
|
65
113
|
},
|
|
66
114
|
cancel,
|
|
67
115
|
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ExtensionContext } from "../../types.ts";
|
|
2
|
+
/** Restored history is not admission; real activity or a completed turn is. */
|
|
3
|
+
export declare function createStartupActivityGate(): {
|
|
4
|
+
readonly waiting: boolean;
|
|
5
|
+
start(ctx: ExtensionContext, onActivity: () => void): void;
|
|
6
|
+
release: () => void;
|
|
7
|
+
};
|
|
8
|
+
//# sourceMappingURL=startup-activity-gate.d.ts.map
|