@code-yeongyu/senpi 2026.11.10-2 → 2026.11.10-3
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 +22 -0
- package/dist/bundle/chunks/{args-77GYOPEO.js → args-HFYB6URH.js} +1 -1
- package/dist/bundle/chunks/{azure-openai-responses-YFZ734T3.js → azure-openai-responses-MNN2TINI.js} +1 -1
- package/dist/bundle/chunks/{chunk-ZUQCYIRY.js → chunk-2ODGHBUD.js} +1 -1
- package/dist/bundle/chunks/{chunk-MSDBBOB2.js → chunk-5H43O4MA.js} +1 -1
- package/dist/bundle/chunks/{chunk-IKJCA5SH.js → chunk-6Q7Y2G5A.js} +1 -1
- package/dist/bundle/chunks/{chunk-YCPURLPU.js → chunk-AC6MDRAE.js} +64 -64
- package/dist/bundle/chunks/{chunk-33MZJTQ3.js → chunk-B4WD5XHP.js} +1 -1
- package/dist/bundle/chunks/{chunk-V5FSENW6.js → chunk-COKX65TE.js} +1 -1
- package/dist/bundle/chunks/{chunk-YNGEZBTP.js → chunk-DEQLHN2J.js} +2 -2
- package/dist/bundle/chunks/{chunk-EJB5E2JK.js → chunk-DHAAWL2H.js} +1 -1
- package/dist/bundle/chunks/{chunk-2EJ4BGBM.js → chunk-DHORIIVQ.js} +1 -1
- package/dist/bundle/chunks/{chunk-2M54EAAY.js → chunk-DIHIEJFV.js} +1 -1
- package/dist/bundle/chunks/{chunk-RCHIMH7N.js → chunk-DQY26AX3.js} +1 -1
- package/dist/bundle/chunks/{chunk-3Q5ZENSD.js → chunk-E6ZBTSC6.js} +1 -1
- package/dist/bundle/chunks/{chunk-74IB3JPP.js → chunk-H3VJVKUK.js} +4 -4
- package/dist/bundle/chunks/{chunk-OF3XVGG4.js → chunk-HZDKQOWB.js} +1 -1
- package/dist/bundle/chunks/chunk-IREUIMFE.js +11 -0
- package/dist/bundle/chunks/{chunk-TUEZNIBK.js → chunk-JSD6O3RJ.js} +2 -2
- package/dist/bundle/chunks/{chunk-UOZDDYBN.js → chunk-L3KVP7VJ.js} +1 -1
- package/dist/bundle/chunks/{chunk-7MN7YNO5.js → chunk-L7DGTA3L.js} +1 -1
- package/dist/bundle/chunks/{chunk-J2NGJKXL.js → chunk-LXINMDMX.js} +1 -1
- package/dist/bundle/chunks/{chunk-QATU6BBC.js → chunk-NN7NESW2.js} +1 -1
- package/dist/bundle/chunks/{chunk-2U6HXWXS.js → chunk-NNQ34ZDW.js} +1 -1
- package/dist/bundle/chunks/chunk-NQKCJQHB.js +2 -0
- package/dist/bundle/chunks/{chunk-XH24X5B2.js → chunk-O5WJVIEU.js} +1 -1
- package/dist/bundle/chunks/{chunk-O3FG6HML.js → chunk-PJI5GT7U.js} +1 -1
- package/dist/bundle/chunks/{chunk-BPNSOZ2C.js → chunk-QUVSIW2R.js} +1 -1
- package/dist/bundle/chunks/{chunk-WXYMAUFX.js → chunk-QX7WD3IM.js} +1 -1
- package/dist/bundle/chunks/{chunk-Y4ONXQMR.js → chunk-RGHVPUUX.js} +1 -1
- package/dist/bundle/chunks/{chunk-RYPM5BZQ.js → chunk-S7NAJQB3.js} +1 -1
- package/dist/bundle/chunks/{chunk-LT353NSF.js → chunk-T4CUD7RF.js} +1 -1
- package/dist/bundle/chunks/{chunk-J7FW7MED.js → chunk-TCZORL3V.js} +1 -1
- package/dist/bundle/chunks/{chunk-KU72IKV5.js → chunk-VGSWOLIY.js} +1 -1
- package/dist/bundle/chunks/{chunk-Q4G5DECT.js → chunk-Z2C5N2LM.js} +2 -2
- package/dist/bundle/chunks/{cli-main-Y6ALA5OI.js → cli-main-ZTT3N4DO.js} +1 -1
- package/dist/bundle/chunks/{credential-accounts-LVVPKFEY.js → credential-accounts-5KHVGSCY.js} +1 -1
- package/dist/bundle/chunks/{dispatch-EP35KKJ3.js → dispatch-YUYZQXRP.js} +1 -1
- package/dist/bundle/chunks/{hand-entry-IPDAIOGI.js → hand-entry-UV5XPMBF.js} +3 -3
- package/dist/bundle/chunks/{headless-host-HKGNJGYT.js → headless-host-53UMJMM6.js} +1 -1
- package/dist/bundle/chunks/{health-write-FBF74QJ7.js → health-write-JLSF5EVV.js} +1 -1
- package/dist/bundle/chunks/{help-fast-path-5SCPY3OH.js → help-fast-path-552QLMWT.js} +1 -1
- package/dist/bundle/chunks/{host-command-2326GCUP.js → host-command-BZN7WYYV.js} +1 -1
- package/dist/bundle/chunks/{host-session-command-BZYBDOIE.js → host-session-command-O5WYHZXH.js} +1 -1
- package/dist/bundle/chunks/{interactive-mode-LBQB7DDR.js → interactive-mode-OCU4NDPC.js} +1 -1
- package/dist/bundle/chunks/{login-health-EVXVLHB2.js → login-health-TOSQT5RG.js} +1 -1
- package/dist/bundle/chunks/{main-FFRFP34W.js → main-CNDP6HCF.js} +1 -1
- package/dist/bundle/chunks/{multi-session-host-NDIHHJ2L.js → multi-session-host-ODGJAGJP.js} +1 -1
- package/dist/bundle/chunks/openai-codex-responses-VRGOAFEG.js +10 -0
- package/dist/bundle/chunks/{openai-responses-NIVBWE2H.js → openai-responses-I7PXRWCM.js} +2 -2
- package/dist/bundle/chunks/{package-manager-cli-XCPAMOYI.js → package-manager-cli-HSPUG4RJ.js} +1 -1
- package/dist/bundle/chunks/{role-HQUN4XXK.js → role-AEOLJRTP.js} +1 -1
- package/dist/bundle/chunks/{rotation-stream-F3BEMXBE.js → rotation-stream-E3FJFHXJ.js} +1 -1
- package/dist/bundle/chunks/{rpc-mode-TKNZM4PZ.js → rpc-mode-KOZPXU3U.js} +1 -1
- package/dist/bundle/chunks/{run-attached-tui-JDUKXDI2.js → run-attached-tui-4AENRP7P.js} +1 -1
- package/dist/bundle/chunks/{schedule-command-HF2DHABB.js → schedule-command-SG44FE23.js} +1 -1
- package/dist/bundle/chunks/{session-control-endpoint-A6R6W2JQ.js → session-control-endpoint-5A24JYPE.js} +1 -1
- package/dist/bundle/chunks/{session-picker-GWA4S2PO.js → session-picker-73YRS3GE.js} +1 -1
- package/dist/bundle/chunks/{session-slot-CZGQQCOC.js → session-slot-RH7JLO3F.js} +1 -1
- package/dist/bundle/chunks/session-worker.js +139 -139
- package/dist/bundle/chunks/{state-store-S7QAESIF.js → state-store-IECVTYXC.js} +1 -1
- package/dist/bundle/cli.js +2 -2
- 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 +12 -2
- package/dist/core/agent-session.js +59 -8
- package/dist/core/assistant-error-redaction.d.ts +9 -0
- package/dist/core/assistant-error-redaction.js +27 -0
- package/dist/core/config-reference-redaction.js +90 -10
- package/dist/core/context-files.d.ts +10 -0
- package/dist/core/context-files.js +58 -12
- package/dist/core/egress/wire.d.ts +13 -0
- package/dist/core/extensions/loader.js +24 -4
- package/dist/core/extensions/runner.d.ts +9 -1
- package/dist/core/extensions/runner.js +9 -4
- package/dist/core/extensions/types.d.ts +17 -3
- package/dist/core/hand/hook-advertise.d.ts +41 -0
- package/dist/core/hand/hook-advertise.js +98 -0
- package/dist/core/hand/hook-contract.d.ts +66 -0
- package/dist/core/hand/hook-contract.js +138 -0
- package/dist/core/resource-loader.js +2 -0
- package/dist/core/retry-fallback/log.d.ts +7 -1
- package/dist/core/retry-fallback/log.js +82 -5
- package/dist/core/retry-fallback/model-config-error.js +2 -1
- package/dist/core/settings-manager.d.ts +6 -0
- package/dist/core/settings-manager.js +8 -0
- package/dist/core/turn-origin.d.ts +19 -13
- package/dist/core/turn-origin.js +34 -30
- package/dist/core/ultrafast-lanes.d.ts +1 -0
- package/dist/core/ultrafast-lanes.js +4 -2
- package/dist/modes/interactive/components/footer.d.ts +23 -5
- package/dist/modes/interactive/components/footer.js +45 -16
- package/dist/modes/interactive/interactive-mode.js +9 -1
- package/dist/modes/rpc/admit-external-message-command.d.ts +2 -2
- package/dist/modes/rpc/admit-external-message-command.js +2 -10
- package/dist/modes/rpc/connection-handler.js +10 -6
- package/dist/modes/rpc/connection-roles.d.ts +2 -0
- package/dist/modes/rpc/connection-roles.js +4 -0
- package/dist/modes/rpc/custom-capability.d.ts +2 -0
- package/dist/modes/rpc/custom-capability.js +2 -0
- package/dist/modes/rpc/delegated-drive-origin.d.ts +6 -6
- package/dist/modes/rpc/delegated-drive-origin.js +7 -8
- package/dist/modes/rpc/delegation-declaration.d.ts +2 -0
- package/dist/modes/rpc/delegation-declaration.js +5 -1
- package/dist/modes/rpc/host-capabilities.js +3 -1
- package/dist/modes/rpc/loopless-tool-handlers.d.ts +6 -0
- package/dist/modes/rpc/loopless-tool-handlers.js +14 -1
- package/docs/extensions.md +10 -0
- package/docs/settings.md +1 -0
- package/docs/usage.md +1 -1
- package/native/socket-secret/prebuilds/win32-arm64/senpi_socket_secret.win32-arm64.node +0 -0
- package/native/socket-secret/prebuilds/win32-x64/senpi_socket_secret.win32-x64.node +0 -0
- package/package.json +6 -6
- package/dist/bundle/chunks/chunk-DLAY7CIU.js +0 -2
- package/dist/bundle/chunks/chunk-XH2MWTXA.js +0 -11
- package/dist/bundle/chunks/openai-codex-responses-ABB2J43U.js +0 -10
|
@@ -4,6 +4,7 @@ import { slotsOfOwnKind } from "@earendil-works/pi-ai/auth/pool/kinds";
|
|
|
4
4
|
import { applyAccountOrder, rendezvousOrder } from "@earendil-works/pi-ai/auth/pool/select";
|
|
5
5
|
import { accountLabel } from "@earendil-works/pi-ai/auth/pool/slots";
|
|
6
6
|
import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
7
|
+
import { ANTHROPIC_SUBSCRIPTION_PROVIDER_ID } from "../../../core/extensions/builtin/anthropic-subscription/provider-id.js";
|
|
7
8
|
import { theme } from "../theme/theme.js";
|
|
8
9
|
import { planFooterLayout } from "./footer-layout.js";
|
|
9
10
|
import { buildRightLabel, colorRightSide } from "./footer-right-label.js";
|
|
@@ -39,25 +40,43 @@ export function formatTokens(count) {
|
|
|
39
40
|
return `${trim1(n / 1_000_000_000)}B`;
|
|
40
41
|
return `${Math.round(n / 1_000_000_000)}B`;
|
|
41
42
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
* engine uses, so the footer names the slot that will serve.
|
|
48
|
-
*/
|
|
49
|
-
export function accountFooterSuffix(credential, sessionId) {
|
|
43
|
+
function formatBlockedUntil(until) {
|
|
44
|
+
const date = new Date(until);
|
|
45
|
+
return `${String(date.getHours()).padStart(2, "0")}:${String(date.getMinutes()).padStart(2, "0")}`;
|
|
46
|
+
}
|
|
47
|
+
export function accountFooterSuffix(credential, sessionId, options = {}) {
|
|
50
48
|
const pooled = credential;
|
|
51
49
|
// Only slots the credential can serve with; a mixed pool's other-kind slot never does (senpi#3486).
|
|
52
50
|
const slots = credential ? slotsOfOwnKind(credential) : [];
|
|
53
51
|
if (slots.length < 2)
|
|
54
52
|
return "";
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const
|
|
60
|
-
|
|
53
|
+
const now = options.now ?? Date.now();
|
|
54
|
+
// Block state rides on the stored slots at runtime (SlotBlockState) but
|
|
55
|
+
// is not part of the CredentialSlot type, so read it through unknown.
|
|
56
|
+
const blockedUntilOf = (slot) => slot.blockedUntil;
|
|
57
|
+
const isBlocked = (slot) => {
|
|
58
|
+
const until = blockedUntilOf(slot);
|
|
59
|
+
return until !== undefined && until > now;
|
|
60
|
+
};
|
|
61
|
+
const ranked = applyAccountOrder(pooled?.order, rendezvousOrder(sessionId, slots, (input) => createHash("sha256").update(input).digest().readBigUInt64BE(0)));
|
|
62
|
+
const storedPinned = Object.entries(credential ?? {}).find(([key]) => key === "pinned")?.[1];
|
|
63
|
+
const pinnedName = options.pinnedAccount ?? storedPinned;
|
|
64
|
+
const pinnedSlot = pinnedName === undefined ? undefined : slots.find((slot) => slot.name === pinnedName);
|
|
65
|
+
if (pinnedSlot) {
|
|
66
|
+
if (!isBlocked(pinnedSlot))
|
|
67
|
+
return `@${footerAccountLabel(pinnedSlot)}`;
|
|
68
|
+
const until = blockedUntilOf(pinnedSlot);
|
|
69
|
+
const serving = ranked.find((slot) => slot !== pinnedSlot && !isBlocked(slot));
|
|
70
|
+
if (serving)
|
|
71
|
+
return `@${footerAccountLabel(serving)} (${footerAccountLabel(pinnedSlot)} blocked until ${formatBlockedUntil(until)})`;
|
|
72
|
+
return `@${footerAccountLabel(pinnedSlot)} (blocked until ${formatBlockedUntil(until)})`;
|
|
73
|
+
}
|
|
74
|
+
const winner = ranked.find((slot) => !isBlocked(slot)) ?? ranked[0];
|
|
75
|
+
if (winner === undefined)
|
|
76
|
+
return "";
|
|
77
|
+
if (isBlocked(winner))
|
|
78
|
+
return `@${footerAccountLabel(winner)} (blocked until ${formatBlockedUntil(blockedUntilOf(winner))})`;
|
|
79
|
+
return `@${footerAccountLabel(winner)}`;
|
|
61
80
|
}
|
|
62
81
|
/**
|
|
63
82
|
* Widest account label the provider segment carries. A label wider than this
|
|
@@ -90,11 +109,12 @@ export function formatCwdForFooter(cwd, home) {
|
|
|
90
109
|
* Computes token/context stats from session, gets git branch and extension statuses from provider.
|
|
91
110
|
*/
|
|
92
111
|
export class FooterComponent {
|
|
93
|
-
constructor(session, footerData) {
|
|
112
|
+
constructor(session, footerData, options = {}) {
|
|
94
113
|
this.autoCompactEnabled = true;
|
|
95
114
|
this.compactionDelegated = false;
|
|
96
115
|
this.session = session;
|
|
97
116
|
this.footerData = footerData;
|
|
117
|
+
this.resolveAccountPin = options.resolveAccountPin;
|
|
98
118
|
}
|
|
99
119
|
setSession(session) {
|
|
100
120
|
this.session = session;
|
|
@@ -201,7 +221,16 @@ export class FooterComponent {
|
|
|
201
221
|
let accountSuffix = "";
|
|
202
222
|
if (state.model) {
|
|
203
223
|
try {
|
|
204
|
-
|
|
224
|
+
const sessionId = this.session.sessionManager.getSessionId();
|
|
225
|
+
// The resolver carries the Anthropic CLI/settings pin; resolve
|
|
226
|
+
// it only when the current model uses the Anthropic
|
|
227
|
+
// subscription provider. A same-named slot of another pooled
|
|
228
|
+
// provider (e.g. openai-codex "default") keeps its own stored
|
|
229
|
+
// pin instead (senpi#6222 review).
|
|
230
|
+
const pinnedAccount = state.model.provider === ANTHROPIC_SUBSCRIPTION_PROVIDER_ID
|
|
231
|
+
? this.resolveAccountPin?.(sessionId)
|
|
232
|
+
: undefined;
|
|
233
|
+
accountSuffix = accountFooterSuffix(this.session.modelRegistry.authStorage.get(state.model.provider), sessionId, pinnedAccount === undefined ? undefined : { pinnedAccount });
|
|
205
234
|
}
|
|
206
235
|
catch {
|
|
207
236
|
// The footer must render even when credential storage is unreadable.
|
|
@@ -25,6 +25,8 @@ import { collectEntriesForBranchSummary } from "../../core/compaction/branch-sum
|
|
|
25
25
|
import { findExtensionStackMatches } from "../../core/crash-log.js";
|
|
26
26
|
import { AssistantEditError, assistantTextEquals } from "../../core/edited-assistant-message.js";
|
|
27
27
|
import { subscribeProviderAccountEvents } from "../../core/extensions/builtin/anthropic-subscription/account-events.js";
|
|
28
|
+
import { getSessionClaudeAccountPin, resolveClaudeAccountPin, } from "../../core/extensions/builtin/anthropic-subscription/account-command.js";
|
|
29
|
+
import { loadAnthropicSubscriptionProviderSettingsFromDisk } from "../../core/extensions/builtin/anthropic-subscription/settings.js";
|
|
28
30
|
import { formatAccountFailoverNotice } from "./account-failover-notice.js";
|
|
29
31
|
import { askUserAnswerDisplayText, formatUserMessage, showsPartialAnswers, } from "../../core/extensions/builtin/ask-user/format.js";
|
|
30
32
|
import { askUserRenderers } from "../../core/extensions/builtin/ask-user/render.js";
|
|
@@ -817,7 +819,13 @@ export class InteractiveMode {
|
|
|
817
819
|
this.footerDataProvider = new FooterDataProvider(this.sessionManager.getCwd());
|
|
818
820
|
this.footer = this.chrome
|
|
819
821
|
? this.chrome.createFooter(this.session, this.footerDataProvider)
|
|
820
|
-
: new FooterComponent(this.session, this.footerDataProvider
|
|
822
|
+
: new FooterComponent(this.session, this.footerDataProvider, {
|
|
823
|
+
// CLI > settings > stored pin, resolved per render so a
|
|
824
|
+
// mid-session /claude-account pin or settings change is
|
|
825
|
+
// picked up (senpi#5983); the stored pin stays the
|
|
826
|
+
// fallback inside accountFooterSuffix.
|
|
827
|
+
resolveAccountPin: (sessionId) => resolveClaudeAccountPin(getSessionClaudeAccountPin(sessionId), loadAnthropicSubscriptionProviderSettingsFromDisk(this.sessionManager.getCwd()).pinnedAccount, undefined),
|
|
828
|
+
});
|
|
821
829
|
this.footer.setAutoCompactEnabled(this.session.autoCompactionEnabled);
|
|
822
830
|
this.footerContainer = new Container();
|
|
823
831
|
this.footerContainer.addChild(this.footer);
|
|
@@ -20,8 +20,8 @@ export interface DeliveryPeer {
|
|
|
20
20
|
/** The remote device the connection speaks for (`ConnectionRoles.originOf`); absent on a plain local connection. */
|
|
21
21
|
readonly origin: RpcRemoteOrigin | undefined;
|
|
22
22
|
readonly connectionOrigin: ConnectionOrigin;
|
|
23
|
-
/** The
|
|
24
|
-
readonly
|
|
23
|
+
/** The origin the turn this delivery opens counts as (senpi#6342); the connection's origin when absent. */
|
|
24
|
+
readonly turnOrigin?: ConnectionOrigin;
|
|
25
25
|
}
|
|
26
26
|
export type AdmitExternalMessageAnswer = {
|
|
27
27
|
readonly ok: true;
|
|
@@ -9,17 +9,9 @@ function ledgerId(deliveryId, origin) {
|
|
|
9
9
|
? `rpc:local/${deliveryId}`
|
|
10
10
|
: `device:${encodeURIComponent(origin.deviceId)}/${deliveryId}`;
|
|
11
11
|
}
|
|
12
|
-
/**
|
|
13
|
-
* The turn a delivery opens: a steer joins the running turn; an agent's delivery on a delegated connection the
|
|
14
|
-
* forwarder granted `peer_agent` is a peer turn (senpi#6305), the record's sender kind being only a consistency check;
|
|
15
|
-
* anything else keeps the connection's origin.
|
|
16
|
-
*/
|
|
12
|
+
/** The turn a delivery opens: a steer joins the running turn; anything else counts as the connection's turn origin. */
|
|
17
13
|
function deliveryTurnMark(command, peer) {
|
|
18
|
-
|
|
19
|
-
return "join";
|
|
20
|
-
if (peer.connectionOrigin === "delegated" && peer.peerAgent === true && command.sender?.kind === "agent")
|
|
21
|
-
return "peer";
|
|
22
|
-
return peer.connectionOrigin;
|
|
14
|
+
return command.deliverAs === "steer" ? "join" : (peer.turnOrigin ?? peer.connectionOrigin);
|
|
23
15
|
}
|
|
24
16
|
export function admitExternalMessage(session, command, peer) {
|
|
25
17
|
const chain = admitInputChain(command.chainCount, session.settingsManager);
|
|
@@ -52,7 +52,8 @@ import { ConnectionQuestionBridge, degradeQuestion, sessionQuestionBridges } fro
|
|
|
52
52
|
import { remoteOriginDeclaration, remoteOriginField } from "./remote-origin.js";
|
|
53
53
|
import { advertisedCompactCapabilities, sessionDeclaration } from "./compact-negotiation.js";
|
|
54
54
|
import { commandAuthority } from "./command-authority.js";
|
|
55
|
-
import {
|
|
55
|
+
import { runInRecordScope } from "../../core/turn-origin.js";
|
|
56
|
+
import { recordTurnOrigin } from "./delegated-drive-origin.js";
|
|
56
57
|
import { ConnectionRoles, DELEGATION_INCARNATION_STALE, roleConnection } from "./connection-roles.js";
|
|
57
58
|
import { ACCOUNT_RESET_CAPABILITY, AUTH_KEY_VERIFY_CAPABILITY, AUTO_TITLE_SESSIONS_CAPABILITY, buildCustomUnsupportedRequest, CONTINUE_FROM_LEAF_CAPABILITY, DEFAULT_CUSTOM_EXTENSION_LABEL, EVAL_CELLS_CAPABILITY, EXTENSION_EVENTS_CAPABILITY, ENTRIES_TAIL_CAPABILITY, INTERACTIVE_USER_CAPABILITY, MEDIA_PLACEHOLDERS_CAPABILITY, PROVIDER_ACCOUNT_TEST_CAPABILITY, QUESTION_CAPABILITY, QUEUED_MESSAGE_REVISION_CAPABILITY, RESUME_SESSION_CAPABILITY, RETRY_FALLBACK_COMMAND_CAPABILITY, SESSION_HELD_CAPABILITY, TOOL_BARRIER_CAPABILITY, TASK_PARENT_CAPABILITY, } from "./custom-capability.js";
|
|
58
59
|
import { runAbort, runGetEffectStatus, runQueuedInputChange, runSetThinkingLevel, } from "./effect-commands.js";
|
|
@@ -209,6 +210,9 @@ export function createRpcConnectionHandler(runtimeHost, sink, options = {}) {
|
|
|
209
210
|
const currentConnectionId = () => roleConnection(options.clientInfo?.connectionId());
|
|
210
211
|
const hasRole = (role) => connectionRoles.has(currentConnectionId(), role);
|
|
211
212
|
const connectionOrigin = () => connectionRoles.isDelegated(currentConnectionId()) ? "delegated" : "local";
|
|
213
|
+
// senpi#6342: a turn follows this machine's own setting; only a device the forwarder did not state is the same account
|
|
214
|
+
// counts as delegated for the permission rule.
|
|
215
|
+
const turnOrigin = () => connectionOrigin() === "delegated" && !connectionRoles.sameAccount(currentConnectionId()) ? "delegated" : "local";
|
|
212
216
|
const routingSessionId = options.sessionId;
|
|
213
217
|
// True only while THIS connection's own command drives a replacement; the issuer
|
|
214
218
|
// already learns the new identity from its command response.
|
|
@@ -1009,7 +1013,6 @@ export function createRpcConnectionHandler(runtimeHost, sink, options = {}) {
|
|
|
1009
1013
|
// Handle a single command
|
|
1010
1014
|
const handleCommand = async (command, requestContext) => {
|
|
1011
1015
|
const id = command.id;
|
|
1012
|
-
noteDelegatedRecord(command, connectionOrigin(), session.sessionManager);
|
|
1013
1016
|
switch (command.type) {
|
|
1014
1017
|
case "get_protocol_info":
|
|
1015
1018
|
return {
|
|
@@ -1070,7 +1073,7 @@ export function createRpcConnectionHandler(runtimeHost, sink, options = {}) {
|
|
|
1070
1073
|
if (holdAutomaticInput(session, command, hasRole(INTERACTIVE_USER_CAPABILITY)))
|
|
1071
1074
|
return success(id, "prompt", { disposition: "handled", heldAsNotice: true });
|
|
1072
1075
|
turnRequested = true;
|
|
1073
|
-
const admission = handleClientInput(session, command, { output, promptCalls }, { interactiveUser: hasRole(INTERACTIVE_USER_CAPABILITY), connectionOrigin:
|
|
1076
|
+
const admission = handleClientInput(session, command, { output, promptCalls }, { interactiveUser: hasRole(INTERACTIVE_USER_CAPABILITY), connectionOrigin: turnOrigin() });
|
|
1074
1077
|
promptCalls.add(admission);
|
|
1075
1078
|
void admission.finally(() => promptCalls.delete(admission));
|
|
1076
1079
|
return undefined;
|
|
@@ -1140,7 +1143,7 @@ export function createRpcConnectionHandler(runtimeHost, sink, options = {}) {
|
|
|
1140
1143
|
if (holdAutomaticInput(session, command, hasRole(INTERACTIVE_USER_CAPABILITY)))
|
|
1141
1144
|
return success(id, command.type, { disposition: "handled", heldAsNotice: true });
|
|
1142
1145
|
turnRequested = true;
|
|
1143
|
-
await handleClientInput(session, command, { output, promptCalls }, { interactiveUser: hasRole(INTERACTIVE_USER_CAPABILITY), connectionOrigin:
|
|
1146
|
+
await handleClientInput(session, command, { output, promptCalls }, { interactiveUser: hasRole(INTERACTIVE_USER_CAPABILITY), connectionOrigin: turnOrigin() });
|
|
1144
1147
|
return undefined;
|
|
1145
1148
|
}
|
|
1146
1149
|
case "abort":
|
|
@@ -1242,7 +1245,7 @@ export function createRpcConnectionHandler(runtimeHost, sink, options = {}) {
|
|
|
1242
1245
|
const peer = {
|
|
1243
1246
|
origin: connectionRoles.originOf(currentConnectionId()),
|
|
1244
1247
|
connectionOrigin: connectionOrigin(),
|
|
1245
|
-
|
|
1248
|
+
turnOrigin: turnOrigin(),
|
|
1246
1249
|
};
|
|
1247
1250
|
const answer = admitExternalMessage(session, command, peer);
|
|
1248
1251
|
if (!answer.ok) {
|
|
@@ -1995,7 +1998,8 @@ export function createRpcConnectionHandler(runtimeHost, sink, options = {}) {
|
|
|
1995
1998
|
return;
|
|
1996
1999
|
}
|
|
1997
2000
|
try {
|
|
1998
|
-
|
|
2001
|
+
// senpi#6342: the record's own handling, and the first run it starts, run under the record's origin.
|
|
2002
|
+
const response = await runInRecordScope(recordTurnOrigin(command, turnOrigin()), () => handleCommand(command, requestContext));
|
|
1999
2003
|
if (response) {
|
|
2000
2004
|
output(response);
|
|
2001
2005
|
await waitForRpcBackpressure();
|
|
@@ -59,6 +59,8 @@ export declare class ConnectionRoles {
|
|
|
59
59
|
acceptsOrigin(connectionId: string, origin: RpcRemoteOrigin): boolean;
|
|
60
60
|
/** Records the remote device a local connection speaks for; a delegated connection's origin is never declared. */
|
|
61
61
|
declareOrigin(connectionId: string, origin: RpcRemoteOrigin): void;
|
|
62
|
+
/** Whether the forwarder stated this delegated connection's device is the host's own account (senpi#6342). */
|
|
63
|
+
sameAccount(connectionId: string | undefined): boolean;
|
|
62
64
|
/** The remote device this connection speaks for: a delegated connection's principal, else its declared origin. */
|
|
63
65
|
originOf(connectionId: string | undefined): RpcRemoteOrigin | undefined;
|
|
64
66
|
/** Whether the connection is delegated (a remote device's): on the host, retained by a worker, or carried to one. */
|
|
@@ -117,6 +117,10 @@ export class ConnectionRoles {
|
|
|
117
117
|
if (this.acceptsOrigin(connectionId, origin))
|
|
118
118
|
this.origins.set(connectionId, origin);
|
|
119
119
|
}
|
|
120
|
+
/** Whether the forwarder stated this delegated connection's device is the host's own account (senpi#6342). */
|
|
121
|
+
sameAccount(connectionId) {
|
|
122
|
+
return this.viewOf(roleConnection(connectionId))?.descriptor.principal.sameAccount === true;
|
|
123
|
+
}
|
|
120
124
|
/** The remote device this connection speaks for: a delegated connection's principal, else its declared origin. */
|
|
121
125
|
originOf(connectionId) {
|
|
122
126
|
const key = roleConnection(connectionId);
|
|
@@ -65,6 +65,8 @@ export declare const DELEGATED_CONNECTION_CAPABILITY = "delegated_connection_v1"
|
|
|
65
65
|
export declare const DELEGATION_ROLES_CAPABILITY = "delegation_roles_v1";
|
|
66
66
|
/** A host-mode delegation may carry the forwarder-granted `peer_agent` role (senpi#6305). */
|
|
67
67
|
export declare const PEER_AGENT_ROLE_CAPABILITY = "peer_agent_role_v1";
|
|
68
|
+
/** A delegation may state `principal.sameAccount`, so that device's turns follow this host's own setting (senpi#6342). */
|
|
69
|
+
export declare const SAME_ACCOUNT_DELEGATION_CAPABILITY = "same_account_delegation_v1";
|
|
68
70
|
/**
|
|
69
71
|
* Client opt-in (D11 S3-5): the connection renders the value-free `secret_input_notice` and `grant_select_notice` of a
|
|
70
72
|
* raiser-only dialog another connection presents, and their `*_notice_settled` records. It never answers them.
|
|
@@ -65,6 +65,8 @@ export const DELEGATED_CONNECTION_CAPABILITY = "delegated_connection_v1";
|
|
|
65
65
|
export const DELEGATION_ROLES_CAPABILITY = "delegation_roles_v1";
|
|
66
66
|
/** A host-mode delegation may carry the forwarder-granted `peer_agent` role (senpi#6305). */
|
|
67
67
|
export const PEER_AGENT_ROLE_CAPABILITY = "peer_agent_role_v1";
|
|
68
|
+
/** A delegation may state `principal.sameAccount`, so that device's turns follow this host's own setting (senpi#6342). */
|
|
69
|
+
export const SAME_ACCOUNT_DELEGATION_CAPABILITY = "same_account_delegation_v1";
|
|
68
70
|
/**
|
|
69
71
|
* Client opt-in (D11 S3-5): the connection renders the value-free `secret_input_notice` and `grant_select_notice` of a
|
|
70
72
|
* raiser-only dialog another connection presents, and their `*_notice_settled` records. It never answers them.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import { type ConnectionOrigin } from "../../core/turn-origin.ts";
|
|
1
|
+
import type { ConnectionOrigin } from "../../core/turn-origin.ts";
|
|
3
2
|
/**
|
|
4
|
-
*
|
|
5
|
-
* by the engine's own method-class table (`drive`, and `device_admin`, which grants more),
|
|
6
|
-
* commands, so a new run starter (`continue_from_leaf`, `wake`, `resume_for_run`, ...)
|
|
3
|
+
* The origin a record's own handling runs under (senpi#6342): delegated for a cross-account record that can act on the
|
|
4
|
+
* session's work, decided by the engine's own method-class table (`drive`, and `device_admin`, which grants more),
|
|
5
|
+
* never by a hand-kept list of commands, so a new run starter (`continue_from_leaf`, `wake`, `resume_for_run`, ...)
|
|
6
|
+
* cannot slip past the rule. Any other record runs as local.
|
|
7
7
|
*/
|
|
8
|
-
export declare function
|
|
8
|
+
export declare function recordTurnOrigin(command: object, origin: ConnectionOrigin): ConnectionOrigin;
|
|
9
9
|
//# sourceMappingURL=delegated-drive-origin.d.ts.map
|
|
@@ -1,16 +1,15 @@
|
|
|
1
|
-
import { recordDelegatedOrigin } from "../../core/turn-origin.js";
|
|
2
1
|
import { classifyRpcMethod } from "./method-classes.js";
|
|
3
2
|
const ACTING_CLASSES = new Set(["drive", "device_admin"]);
|
|
4
3
|
/**
|
|
5
|
-
*
|
|
6
|
-
* by the engine's own method-class table (`drive`, and `device_admin`, which grants more),
|
|
7
|
-
* commands, so a new run starter (`continue_from_leaf`, `wake`, `resume_for_run`, ...)
|
|
4
|
+
* The origin a record's own handling runs under (senpi#6342): delegated for a cross-account record that can act on the
|
|
5
|
+
* session's work, decided by the engine's own method-class table (`drive`, and `device_admin`, which grants more),
|
|
6
|
+
* never by a hand-kept list of commands, so a new run starter (`continue_from_leaf`, `wake`, `resume_for_run`, ...)
|
|
7
|
+
* cannot slip past the rule. Any other record runs as local.
|
|
8
8
|
*/
|
|
9
|
-
export function
|
|
9
|
+
export function recordTurnOrigin(command, origin) {
|
|
10
10
|
if (origin !== "delegated")
|
|
11
|
-
return;
|
|
11
|
+
return "local";
|
|
12
12
|
const methodClass = classifyRpcMethod(command);
|
|
13
|
-
|
|
14
|
-
recordDelegatedOrigin(sessionManager);
|
|
13
|
+
return methodClass !== undefined && ACTING_CLASSES.has(methodClass) ? "delegated" : "local";
|
|
15
14
|
}
|
|
16
15
|
//# sourceMappingURL=delegated-drive-origin.js.map
|
|
@@ -4,6 +4,8 @@ export interface Principal {
|
|
|
4
4
|
readonly deviceId: string;
|
|
5
5
|
readonly deviceName?: string;
|
|
6
6
|
readonly conn?: string;
|
|
7
|
+
/** The forwarder states this device is the host's own account (senpi#6342); fixed for the record's life. */
|
|
8
|
+
readonly sameAccount?: true;
|
|
7
9
|
}
|
|
8
10
|
export interface Declaration {
|
|
9
11
|
readonly principal: Principal;
|
|
@@ -22,6 +22,9 @@ export function parseDeclaration(record) {
|
|
|
22
22
|
return "principal.deviceName must be 1-256 characters";
|
|
23
23
|
if (conn !== undefined && !boundedString(conn, MAX_CONN_FIELD))
|
|
24
24
|
return "principal.conn must be 1-64 characters";
|
|
25
|
+
const sameAccount = Reflect.get(principal, "sameAccount");
|
|
26
|
+
if (sameAccount !== undefined && sameAccount !== true)
|
|
27
|
+
return "principal.sameAccount must be true when present";
|
|
25
28
|
const granted = Reflect.get(record, "granted");
|
|
26
29
|
if (!Array.isArray(granted))
|
|
27
30
|
return "granted must be an array of method classes";
|
|
@@ -40,12 +43,13 @@ export function parseDeclaration(record) {
|
|
|
40
43
|
deviceId: deviceId,
|
|
41
44
|
...(deviceName === undefined ? {} : { deviceName: deviceName }),
|
|
42
45
|
...(conn === undefined ? {} : { conn: conn }),
|
|
46
|
+
...(sameAccount === true ? { sameAccount: true } : {}),
|
|
43
47
|
},
|
|
44
48
|
granted: classes,
|
|
45
49
|
};
|
|
46
50
|
}
|
|
47
51
|
export function samePrincipal(a, b) {
|
|
48
|
-
return a.deviceId === b.deviceId && a.deviceName === b.deviceName && a.conn === b.conn;
|
|
52
|
+
return (a.deviceId === b.deviceId && a.deviceName === b.deviceName && a.conn === b.conn && a.sameAccount === b.sameAccount);
|
|
49
53
|
}
|
|
50
54
|
function parseRoles(value) {
|
|
51
55
|
if (value === undefined)
|
|
@@ -2,7 +2,7 @@ import { compactUpdatesEnabled } from "../../core/engine-socket-flag.js";
|
|
|
2
2
|
import { isHostOwnedToolRuntimeCapability, toolRuntimeCapabilities, } from "../../cli/tool-runtime.js";
|
|
3
3
|
import { ACCEPT_EDITS_PERMISSION_PRESET_CAPABILITY, AUTO_PERMISSION_PRESET_CAPABILITY, } from "../../core/extensions/builtin/permission-system/config.js";
|
|
4
4
|
import { DURABLE_CLIENT_MESSAGE_ID_CAPABILITY } from "./client-admission-record.js";
|
|
5
|
-
import { ACCOUNT_RESET_CAPABILITY, ATTENDED_CAPABILITY, AUTH_KEY_VERIFY_CAPABILITY, AUTO_TITLE_PER_SESSION_CAPABILITY, AUTO_TITLE_SESSIONS_CAPABILITY, BROWSER_ENGINE_CAPABILITY, CONTINUE_FROM_LEAF_CAPABILITY, DELEGATED_CONNECTION_CAPABILITY, DELEGATION_ROLES_CAPABILITY, DETACH_CONNECTION_CAPABILITY, DURABLE_SESSION_ID_CAPABILITY, PEER_AGENT_ROLE_CAPABILITY, ENTRIES_TAIL_CAPABILITY, EVAL_CELLS_CAPABILITY, MEDIA_PLACEHOLDERS_CAPABILITY, MOVED_PATH_GUARD_CAPABILITY, OBSERVE_READS_CAPABILITY, PROMPT_SURFACE_CAPABILITY, PROMPT_SURFACE_CHAT_CAPABILITY, PROVIDER_ACCOUNT_TEST_CAPABILITY, QUEUED_MESSAGE_REVISION_CAPABILITY, RESUME_SESSION_CAPABILITY, RETAIN_ON_DISCONNECT_CAPABILITY, RETRY_FALLBACK_PROFILE_CAPABILITY, SESSION_CONTEXT_CAPABILITY, SESSION_ENV_CAPABILITY, SESSION_HELD_CAPABILITY, SESSION_KIND_CAPABILITY, SESSION_RELOCATION_CAPABILITY, SESSION_STATUS_CAPABILITY, TOOL_BARRIER_CAPABILITY, WARM_CAPABILITY, PARK_CLAIM_CAPABILITY, } from "./custom-capability.js";
|
|
5
|
+
import { ACCOUNT_RESET_CAPABILITY, ATTENDED_CAPABILITY, AUTH_KEY_VERIFY_CAPABILITY, AUTO_TITLE_PER_SESSION_CAPABILITY, AUTO_TITLE_SESSIONS_CAPABILITY, BROWSER_ENGINE_CAPABILITY, CONTINUE_FROM_LEAF_CAPABILITY, DELEGATED_CONNECTION_CAPABILITY, DELEGATION_ROLES_CAPABILITY, DETACH_CONNECTION_CAPABILITY, DURABLE_SESSION_ID_CAPABILITY, PEER_AGENT_ROLE_CAPABILITY, SAME_ACCOUNT_DELEGATION_CAPABILITY, ENTRIES_TAIL_CAPABILITY, EVAL_CELLS_CAPABILITY, MEDIA_PLACEHOLDERS_CAPABILITY, MOVED_PATH_GUARD_CAPABILITY, OBSERVE_READS_CAPABILITY, PROMPT_SURFACE_CAPABILITY, PROMPT_SURFACE_CHAT_CAPABILITY, PROVIDER_ACCOUNT_TEST_CAPABILITY, QUEUED_MESSAGE_REVISION_CAPABILITY, RESUME_SESSION_CAPABILITY, RETAIN_ON_DISCONNECT_CAPABILITY, RETRY_FALLBACK_PROFILE_CAPABILITY, SESSION_CONTEXT_CAPABILITY, SESSION_ENV_CAPABILITY, SESSION_HELD_CAPABILITY, SESSION_KIND_CAPABILITY, SESSION_RELOCATION_CAPABILITY, SESSION_STATUS_CAPABILITY, TOOL_BARRIER_CAPABILITY, WARM_CAPABILITY, PARK_CLAIM_CAPABILITY, } from "./custom-capability.js";
|
|
6
6
|
import { EFFECT_LEDGER_CAPABILITY } from "./effect-record.js";
|
|
7
7
|
import { SESSION_MODEL_SCOPE_CAPABILITY } from "./rpc-session-command-types.js";
|
|
8
8
|
import { STEER_WHEN_IDLE_CAPABILITY, USER_ACTIVITY_CAPABILITY } from "./rpc-types.js";
|
|
@@ -72,6 +72,8 @@ export function multiSessionHostCapabilities(options) {
|
|
|
72
72
|
DELEGATION_ROLES_CAPABILITY,
|
|
73
73
|
// ...and may carry the forwarder-granted `peer_agent` role on such a connection (senpi#6305).
|
|
74
74
|
PEER_AGENT_ROLE_CAPABILITY,
|
|
75
|
+
// ...and lets the forwarder state a device is the host's own account, whose turns then ask nothing extra.
|
|
76
|
+
SAME_ACCOUNT_DELEGATION_CAPABILITY,
|
|
75
77
|
// Only a multi-session host keeps sessions loaded that a client can ask it to free.
|
|
76
78
|
UNLOAD_SESSION_CAPABILITY,
|
|
77
79
|
// Only a multi-session host owns a move ledger and the hosted-session list export checks against.
|
|
@@ -25,6 +25,12 @@ export type LooplessLoopHooks = (event: string, payload: JsonValue) => Promise<J
|
|
|
25
25
|
* (`errors`).
|
|
26
26
|
*/
|
|
27
27
|
export declare function extensionLoopHooks(runner: ExtensionRunner, twoSidedHome: TwoSidedHome, cwd: string, call: LoopHookCall): LooplessLoopHooks;
|
|
28
|
+
/**
|
|
29
|
+
* The user's loop hooks, which a hand runs for a hosted loop: its non-builtin extensions (C7(c), K5) and the hooks
|
|
30
|
+
* builtin, which runs the user's shell hooks (C8). Every other builtin is the loop's own machinery (model, compaction,
|
|
31
|
+
* todo, search): each side loads its own builtins, so those run where the loop runs, as S29 never forwards builtins.
|
|
32
|
+
*/
|
|
33
|
+
export declare const isUserLoopHook: (extensionPath: string) => boolean;
|
|
28
34
|
/** A message a loop hook sent during its call, for the loop to send with the same options. */
|
|
29
35
|
export interface SentLoopMessage {
|
|
30
36
|
readonly message: Parameters<LoopHookCallFacts["sendMessage"]>[0];
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
2
|
import { forwardToolEvents } from "../../core/extensions/two-sided-forwarding.js";
|
|
3
|
+
import { hookModelOf, hookRequestOutputOf, parseHookRequestInput } from "../../core/hand/hook-contract.js";
|
|
3
4
|
/**
|
|
4
5
|
* A loopless runtime's own extensions' `tool_call`/`tool_result` handlers, emitted as a session emits them. On a hand
|
|
5
6
|
* (a call that carries its nested requests), a two-sided extension's handlers are forwarded to the loop that owns the
|
|
@@ -78,6 +79,18 @@ export function extensionLoopHooks(runner, twoSidedHome, cwd, call) {
|
|
|
78
79
|
...(preview === undefined ? {} : { preview }),
|
|
79
80
|
})));
|
|
80
81
|
}
|
|
82
|
+
if (event === "before_provider_request") {
|
|
83
|
+
// The hosted loop's request hooks (CK21): the user's hooks get the projected input (payload and effective model,
|
|
84
|
+
// no headers), and the reply's `output` carries the payload they produced.
|
|
85
|
+
const input = parseHookRequestInput(payload.input);
|
|
86
|
+
const model = hookModelOf(input);
|
|
87
|
+
return call.run(sessionId, cwd, idle, (effects) => dispatchLoopHook(runner, call, effects, async () => ({
|
|
88
|
+
output: hookRequestOutputOf(input, await runner.emitBeforeProviderRequest(structuredClone(input.payload), undefined, undefined, {
|
|
89
|
+
only: isUserLoopHook,
|
|
90
|
+
...(model === undefined ? {} : { model }),
|
|
91
|
+
})),
|
|
92
|
+
})));
|
|
93
|
+
}
|
|
81
94
|
throw new Error(`no loop hooks run on a hand for ${event}`);
|
|
82
95
|
};
|
|
83
96
|
}
|
|
@@ -86,7 +99,7 @@ export function extensionLoopHooks(runner, twoSidedHome, cwd, call) {
|
|
|
86
99
|
* builtin, which runs the user's shell hooks (C8). Every other builtin is the loop's own machinery (model, compaction,
|
|
87
100
|
* todo, search): each side loads its own builtins, so those run where the loop runs, as S29 never forwards builtins.
|
|
88
101
|
*/
|
|
89
|
-
const isUserLoopHook = (extensionPath) => !extensionPath.startsWith("<builtin:") || extensionPath === "<builtin:hooks>";
|
|
102
|
+
export const isUserLoopHook = (extensionPath) => !extensionPath.startsWith("<builtin:") || extensionPath === "<builtin:hooks>";
|
|
90
103
|
const INPUT_SOURCES = ["interactive", "rpc", "extension"];
|
|
91
104
|
const INPUT_ATTRIBUTIONS = ["user", "agent"];
|
|
92
105
|
const STREAMING_BEHAVIORS = ["steer", "followUp"];
|
package/docs/extensions.md
CHANGED
|
@@ -1075,6 +1075,16 @@ pi.on("before_provider_request", (event, ctx) => {
|
|
|
1075
1075
|
|
|
1076
1076
|
This is mainly useful for debugging provider serialization and cache behavior.
|
|
1077
1077
|
|
|
1078
|
+
`before_provider_request`, `provider_stream_event` and `after_provider_response` handlers can declare the providers whose requests or streams they read or change:
|
|
1079
|
+
|
|
1080
|
+
```typescript
|
|
1081
|
+
pi.on("before_provider_request", (event) => ({ ...event.payload, metadata: { tag: "x" } }), {
|
|
1082
|
+
providers: ["anthropic"],
|
|
1083
|
+
});
|
|
1084
|
+
```
|
|
1085
|
+
|
|
1086
|
+
The declaration does not filter dispatch: the handler still receives every event. A device serving a hosted session advertises it, so the hosted side knows which providers' calls need that device's hooks. A handler without `providers` counts as touching every provider. The value must be a non-empty array of provider ids; anything else throws at registration, also for a handler whose `runs` side does not register it. Every other event ignores `providers`.
|
|
1087
|
+
|
|
1078
1088
|
#### after_provider_response
|
|
1079
1089
|
|
|
1080
1090
|
Fired after an HTTP response is received and before its stream body is consumed. Handlers run in extension load order.
|
package/docs/settings.md
CHANGED
|
@@ -214,6 +214,7 @@ Set `PI_SKIP_VERSION_CHECK=1` to disable the senpi version update check. Use `--
|
|
|
214
214
|
|---------|------|---------|-------------|
|
|
215
215
|
| `contextFiles.maxBytesPerFile` | number | `65536` | Maximum UTF-8 bytes loaded from each AGENTS.md / CLAUDE.md, including the truncation note and any closing code fence |
|
|
216
216
|
| `contextFiles.maxTotalBytes` | number | `131072` | Maximum combined UTF-8 bytes of loaded context files, including truncation notes and closing code fences |
|
|
217
|
+
| `contextFiles.globalPath` | string | unset | Global (user-scope) context file to load instead of the agent-dir `AGENTS.md`, e.g. `~/.agents/AGENTS.md`. Read from `~/.senpi/agent/settings.json` only; a project's `.senpi/settings.json` cannot set it. A leading `~` is expanded, a relative path resolves against `~/.senpi/agent`, and a directory runs the same `AGENTS.override.md` / `AGENTS.md` / `CLAUDE.md` candidate probe as the agent dir. Unset or blank keeps the agent-dir file; a path that is set but missing, unreadable, or a directory with no candidate falls back to the agent-dir probe and shows one warning naming the path. The loaded file keeps the same size limits |
|
|
217
218
|
|
|
218
219
|
Files receive budget nearest-first: the current directory, its ancestors, then the global file. A cut prefers a newline in the last quarter of the available content budget; otherwise it uses a UTF-8 character boundary. An open backtick or tilde fence is closed with the same marker before the truncation note; the closing marker's bytes count against the budget. Files with less than 1024 bytes of budget left are skipped when they would need truncation, with a warning distinguishing an undersized per-file limit from budget used by nearer files. Both limits accept positive numbers (rounded down); invalid values use the defaults.
|
|
219
220
|
|
package/docs/usage.md
CHANGED
|
@@ -129,7 +129,7 @@ To see where a long session's memory goes, start senpi with `SENPI_MEMORY_REPORT
|
|
|
129
129
|
|
|
130
130
|
Senpi loads `AGENTS.md` or `CLAUDE.md` at startup from:
|
|
131
131
|
|
|
132
|
-
- `~/.senpi/agent/AGENTS.md` for global instructions
|
|
132
|
+
- `~/.senpi/agent/AGENTS.md` for global instructions (set `contextFiles.globalPath` in settings to load a different file or directory instead, e.g. `~/.agents/AGENTS.md`)
|
|
133
133
|
- parent directories, walking up from the current working directory
|
|
134
134
|
- the current directory
|
|
135
135
|
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@code-yeongyu/senpi",
|
|
3
|
-
"version": "2026.11.10-
|
|
3
|
+
"version": "2026.11.10-3",
|
|
4
4
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"piConfig": {
|
|
@@ -92,9 +92,9 @@
|
|
|
92
92
|
},
|
|
93
93
|
"dependencies": {
|
|
94
94
|
"@earendil-works/chord": "0.99.1",
|
|
95
|
-
"@earendil-works/pi-agent-core": "npm:@code-yeongyu/senpi-agent-core@2026.11.10-
|
|
96
|
-
"@earendil-works/pi-ai": "npm:@code-yeongyu/senpi-ai@2026.11.10-
|
|
97
|
-
"@earendil-works/pi-tui": "npm:@code-yeongyu/senpi-tui@2026.11.10-
|
|
95
|
+
"@earendil-works/pi-agent-core": "npm:@code-yeongyu/senpi-agent-core@2026.11.10-3",
|
|
96
|
+
"@earendil-works/pi-ai": "npm:@code-yeongyu/senpi-ai@2026.11.10-3",
|
|
97
|
+
"@earendil-works/pi-tui": "npm:@code-yeongyu/senpi-tui@2026.11.10-3",
|
|
98
98
|
"@silvia-odwyer/photon-node": "0.3.4",
|
|
99
99
|
"ajv": "8.20.0",
|
|
100
100
|
"chalk": "6.0.0",
|
|
@@ -115,8 +115,8 @@
|
|
|
115
115
|
"yaml": "2.9.1",
|
|
116
116
|
"@anthropic-ai/claude-agent-sdk": "0.3.321",
|
|
117
117
|
"@anthropic-ai/sdk": "0.127.0",
|
|
118
|
-
"@code-yeongyu/senpi-codemode": "2026.11.10-
|
|
119
|
-
"@earendil-works/pi-pty": "npm:@code-yeongyu/senpi-pty@2026.11.10-
|
|
118
|
+
"@code-yeongyu/senpi-codemode": "2026.11.10-3",
|
|
119
|
+
"@earendil-works/pi-pty": "npm:@code-yeongyu/senpi-pty@2026.11.10-3",
|
|
120
120
|
"@modelcontextprotocol/sdk": "1.32.1",
|
|
121
121
|
"@mozilla/readability": "0.6.0",
|
|
122
122
|
"linkedom": "0.18.13",
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import { createRequire as __piCreateRequire } from "node:module"; const require = __piCreateRequire(import.meta.url); { const __piWorkerThreads = require("node:worker_threads"); if (typeof __piWorkerThreads.markAsUncloneable !== "function") { __piWorkerThreads.markAsUncloneable = () => {}; } }
|
|
2
|
-
var resolvedValues=new Set,redactionForms;function rememberResolvedReferenceValue(value){value.length<4||resolvedValues.has(value)||(resolvedValues.add(value),redactionForms=void 0)}function encodings(value){let form=new URLSearchParams([["",value]]).toString().slice(1);return[value,JSON.stringify(value).slice(1,-1),encodeURIComponent(value),encodeURI(value),form]}function forms(){return redactionForms??=[...new Set([...resolvedValues].flatMap(encodings))].filter(form=>form.length>=4).sort((left,right)=>right.length-left.length),redactionForms}function redactResolvedReferenceValues(text,replacement="<redacted>"){let redacted=text;for(let form of forms())redacted.includes(form)&&(redacted=redacted.split(form).join(replacement));return redacted}var PROTOCOL_TYPES=new Set(["text","image","audio","resource","resource_link","object","array","string","number","integer","boolean","null"]),SCHEMA_FORMATS=new Set(["date-time","date","time","duration","email","idn-email","hostname","idn-hostname","ipv4","ipv6","uri","uri-reference","iri","iri-reference","uri-template","uuid","json-pointer","relative-json-pointer","regex","byte","binary","password"]),MEDIA_TYPES=new Set(["text/plain","text/markdown","text/html","text/css","text/csv","text/javascript","text/xml","text/x-python","text/x-typescript","application/json","application/xml","application/yaml","application/javascript","application/typescript","application/pdf","application/zip","application/gzip","application/octet-stream","application/x-ndjson","image/png","image/jpeg","image/gif","image/webp","image/svg+xml","image/bmp","image/avif","image/heic","image/tiff","audio/wav","audio/mpeg","audio/mp3","audio/ogg","audio/webm","audio/flac","audio/aac","video/mp4","video/webm"]),SCHEMA_URLS=new Set(["http://json-schema.org/draft-04/schema#","http://json-schema.org/draft-06/schema#","http://json-schema.org/draft-07/schema#","http://json-schema.org/schema#","https://json-schema.org/draft/2019-09/schema","https://json-schema.org/draft/2020-12/schema"]),PROTOCOL_VALUES={type:PROTOCOL_TYPES,mimeType:MEDIA_TYPES,format:SCHEMA_FORMATS,jsonrpc:new Set(["2.0"]),role:new Set(["user","assistant"]),$schema:SCHEMA_URLS};function isProtocolValue(key,value){return Object.hasOwn(PROTOCOL_VALUES,key)&&PROTOCOL_VALUES[key]?.has(value)===!0}function redactResolvedReferenceData(value){if(resolvedValues.size===0)return value;let copies=new WeakMap,pending=[],copy=input=>{let source=typeof input=="object"&&input!==null&&typeof Reflect.get(input,"toJSON")=="function"?Reflect.get(input,"toJSON").call(input):input;if(typeof source=="string")return redactResolvedReferenceValues(source);if(typeof source!="object"||source===null)return source;let existing=copies.get(source);if(existing!==void 0)return existing;let target=Array.isArray(source)?new Array(source.length):{};return copies.set(source,target),pending.push({source,target}),target},result=copy(value);for(let{source,target}of pending)for(let key of Object.keys(source)){let child=Reflect.get(source,key);Object.defineProperty(target,key,{value:typeof child=="string"&&isProtocolValue(key,child)?child:copy(child),enumerable:!0,writable:!0,configurable:!0})}return result}export{rememberResolvedReferenceValue,redactResolvedReferenceValues,redactResolvedReferenceData};
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { createRequire as __piCreateRequire } from "node:module"; const require = __piCreateRequire(import.meta.url); { const __piWorkerThreads = require("node:worker_threads"); if (typeof __piWorkerThreads.markAsUncloneable !== "function") { __piWorkerThreads.markAsUncloneable = () => {}; } }
|
|
2
|
-
import{reportedCount}from"./chunk-GVSBCJFI.js";import{splitDeferredTools}from"./chunk-HK46JMSC.js";import{parseStreamingJson}from"./chunk-3W47LTXQ.js";import{appendGrammarToolInputJsonDelta,getGrammarToolInput,getJsonSchemaToolParameters,resolveGrammarConstrainedSampling,resolveJsonSchemaStrictSampling,shortHash,transformMessages}from"./chunk-UAOOH2V7.js";import{sanitizeSurrogates}from"./chunk-WDTJ6JKE.js";import{calculateCost,getCurrentTools,getDeclaredTools,getSystemMessageText,renderSystemMessageUpdate,resolveTranscript,resolveTranscriptTools,supportsConfigurationUpdate}from"./chunk-OH5TKHW7.js";import{enableAssistantCapture,pushAssistantProgress}from"./chunk-NDNQ23SK.js";function supportsOpenAIResponsesExplicitPromptCacheMode(model){let compat=model.compat;return!compat?.siwc&&compat?.supportsExplicitPromptCacheMode===!0}function isOpenAIResponsesPromptCacheModel(model){if(model.api!=="openai-responses"||!supportsOpenAIResponsesExplicitPromptCacheMode(model))return!1;try{return new URL(model.baseUrl||"https://api.openai.com/v1").hostname==="api.openai.com"}catch{return!1}}function findPromptCacheComparisonResponseId(model,messages){for(let index=messages.length-1;index>=0;index-=1){let message=messages[index];if(message?.role==="assistant"&&!(message.api!==model.api||message.provider!==model.provider||message.model!==model.id)&&!(message.stopReason==="error"||message.stopReason==="aborted")&&typeof message.responseId=="string"&&message.responseId.length>0)return message.responseId.startsWith("resp")?message.responseId:void 0}}function withPromptCacheComparison(options,comparisonResponseId){return comparisonResponseId===void 0?options:{...options,comparison_response_id:comparisonResponseId}}function finiteCount(value){return typeof value=="number"&&Number.isFinite(value)&&value>=0?value:void 0}function parsePromptCacheDiagnostics(raw){if(typeof raw!="object"||raw===null)return;let record=raw;if(typeof record.type!="string"||record.type.length===0)return;let diagnostics={type:record.type};typeof record.reason=="string"&&(diagnostics.reason=record.reason);let cacheMissedTokens=finiteCount(record.cache_missed_tokens);cacheMissedTokens!==void 0&&(diagnostics.cacheMissedTokens=cacheMissedTokens);let comparisonReusableTokens=finiteCount(record.comparison_reusable_tokens);return comparisonReusableTokens!==void 0&&(diagnostics.comparisonReusableTokens=comparisonReusableTokens),diagnostics}var CONTEXT_PROVENANCE_FIELD="__piContextProvenance";function isRecord(value){return typeof value=="object"&&value!==null&&!Array.isArray(value)}function getContextProvenance(value){if(!isRecord(value))return;let provenance=value[CONTEXT_PROVENANCE_FIELD];return isRecord(provenance)?provenance:void 0}function copyContextProvenance(source,target){let provenance=getContextProvenance(source);return provenance?Object.assign(target,{[CONTEXT_PROVENANCE_FIELD]:provenance}):target}function contextProvenanceFingerprint(value){if(!isRecord(value))return;let clone={...value};return delete clone[CONTEXT_PROVENANCE_FIELD],JSON.stringify(clone)}function formatResponsesCompletionStall(graceMs){return`Provider stream stalled after the last output item: response.completed timed out after ${graceMs}ms`}var ResponsesCompletionStallError=class extends Error{constructor(graceMs){super(formatResponsesCompletionStall(graceMs)),this.name="ResponsesCompletionStallError",this.graceMs=graceMs}},GRACE_EXPIRED=Symbol("responses-completion-grace-expired");function withResponsesCompletionGrace(source,graceMs=6e4){return{[Symbol.asyncIterator](){let iterator=source[Symbol.asyncIterator](),openItems=0,sawItemDone=!1,finished=!1,observe=event=>{event.type==="response.output_item.added"?openItems++:event.type==="response.output_item.done"&&(openItems=Math.max(0,openItems-1),sawItemDone=!0)},boundedNext=async()=>{let next=iterator.next();if(!sawItemDone||openItems>0||graceMs<=0)return next;let timer,deadline=new Promise(resolve=>{timer=setTimeout(()=>resolve(GRACE_EXPIRED),graceMs)});try{let winner=await Promise.race([next,deadline]);if(winner===GRACE_EXPIRED)throw finished=!0,iterator.return?.().catch(()=>{}),new ResponsesCompletionStallError(graceMs);return winner}finally{timer!==void 0&&clearTimeout(timer)}};return{async next(){if(finished)return{done:!0,value:void 0};let result=await boundedNext();return result.done?finished=!0:observe(result.value),result},async return(value){return finished=!0,await iterator.return?.(value),{done:!0,value:void 0}},async throw(error){if(finished=!0,iterator.throw)return iterator.throw(error);throw error}}}}}function encodeTextSignatureV1(id,phase){let payload={v:1,id};return phase&&(payload.phase=phase),JSON.stringify(payload)}function parseTextSignature(signature){if(signature){if(signature.startsWith("{"))try{let parsed=JSON.parse(signature);if(parsed.v===1&&typeof parsed.id=="string")return parsed.phase==="commentary"||parsed.phase==="final_answer"?{id:parsed.id,phase:parsed.phase}:{id:parsed.id}}catch{}return{id:signature}}}function parseReasoningSignature(signature){if(signature)try{let parsed=JSON.parse(signature);return parsed?.type==="reasoning"?parsed:void 0}catch{return}}function convertToolResultOutput(model,content){let textResult=content.filter(c=>c.type==="text").map(c=>c.text).join(`
|
|
3
|
-
`),images=content.filter(c=>c.type==="image"),hasText=textResult.length>0;if(images.length===0||!model.input.includes("image"))return sanitizeSurrogates(hasText?textResult:images.length>0?"(see attached image)":"(no tool output)");let output=[];hasText&&output.push({type:"input_text",text:sanitizeSurrogates(textResult)});for(let image of images)output.push({type:"input_image",detail:"auto",image_url:`data:${image.mimeType};base64,${image.data}`});return output}var CUSTOM_TOOL_CALL_ITEM_ID_SENTINEL="custom";function isResponseCustomToolCallItem(item){return item.type==="custom_tool_call"}function isFreeformTool(tool){return tool.freeform!==void 0}function isFreeformToolName(toolName,tools){return tools?.some(tool=>tool.name===toolName&&isFreeformTool(tool))??!1}function getFreeformToolInput(argumentsValue){return typeof argumentsValue.input=="string"?argumentsValue.input:JSON.stringify(argumentsValue)}function contextProvenanceForInput(message,seal){let provenance=getContextProvenance(message);if(!provenance)return;let fingerprint=contextProvenanceFingerprint(message);if(fingerprint===void 0)return;let stored=provenance.integrity;return stored===void 0&&seal?(provenance.integrity=fingerprint,provenance):stored===fingerprint?provenance:void 0}function withContextProvenance(item,message,seal){let provenance=contextProvenanceForInput(message,seal);return provenance&&Object.defineProperty(item,CONTEXT_PROVENANCE_FIELD,{value:provenance,enumerable:!1}),item}function resolveResponsesDeferredToolsMode(compat){if(compat?.supportsAdditionalTools)return"additional-tools";if(!compat?.siwc)return compat?.supportsToolSearch?"tool-search":void 0}function resolveResponsesToolPlacement(messages,supportsToolAdditions){let transcriptTools=resolveTranscriptTools(messages,supportsToolAdditions),{deferred}=splitDeferredTools({messages:[...messages],tools:getCurrentTools(messages)},supportsToolAdditions);return{requestTools:transcriptTools.requestTools.filter(tool=>!deferred.has(tool.name)),anchorsAdditions:transcriptTools.anchorsAdditions,deferred}}function convertResponsesMessages(model,context,allowedToolCallProviders,options){let normalizedContext=resolveTranscript(context,options?.supportsMidConvoSystemMessages),messages=[],deferredToolsMode=resolveResponsesDeferredToolsMode(options),toolPlacement=resolveResponsesToolPlacement(normalizedContext.messages,deferredToolsMode!==void 0),declaredTools=getDeclaredTools(normalizedContext.messages),loadedToolNames=new Set,normalizeIdPart=part=>{let sanitized=part.replace(/[^a-zA-Z0-9_-]/g,"_");return(sanitized.length>64?sanitized.slice(0,64):sanitized).replace(/_+$/,"")},buildForeignResponsesItemId=itemId=>{let normalized=`fc_${shortHash(itemId)}`;return normalized.length>64?normalized.slice(0,64):normalized},normalizeToolCallId=(id,_targetModel,source)=>{if(!id.includes("|"))return normalizeIdPart(id);let[callId,itemId]=id.split("|"),normalizedCallId=normalizeIdPart(callId);if(itemId===CUSTOM_TOOL_CALL_ITEM_ID_SENTINEL)return`${normalizedCallId}|${CUSTOM_TOOL_CALL_ITEM_ID_SENTINEL}`;if(!allowedToolCallProviders.has(model.provider))return normalizeIdPart(id);let normalizedItemId=source.provider!==model.provider||source.api!==model.api?buildForeignResponsesItemId(itemId):normalizeIdPart(itemId);return normalizedItemId.startsWith("fc_")||(normalizedItemId=normalizeIdPart(`fc_${normalizedItemId}`)),`${normalizedCallId}|${normalizedItemId}`},transformedMessages=transformMessages(normalizedContext.messages,model,normalizeToolCallId,{preserveThinking:options?.preserveThinking,preserveTextSignatures:options?.preserveTextSignatures}),appendSystemToolAdditions=(message,seed)=>{let tools=toolPlacement.anchorsAdditions?(message.toolsAdded??[]).filter(tool=>!loadedToolNames.has(tool.name)):[];if(tools.length===0)return;for(let tool of tools)loadedToolNames.add(tool.name);if(options?.supportsAdditionalTools){messages.push({type:"additional_tools",role:"developer",tools:convertResponsesTools(tools,options.toolOptions)});return}if(!options?.supportsToolSearch)return;let names=tools.map(tool=>tool.name),callId=`pi_tool_load_${shortHash(`${seed}:${names.join(",")}`)}`;messages.push({type:"tool_search_call",call_id:callId,execution:"client",status:"completed",arguments:{query:names.join(" "),limit:names.length}}),messages.push({type:"tool_search_output",call_id:callId,execution:"client",status:"completed",tools:convertResponsesTools(tools,{...options.toolOptions,toolSearchResult:!0})})},includeInitialSystemMessage=options?.includeSystemPrompt??!0,compat=model.compat,instructionRole=model.compat?.siwc||model.reasoning&&compat?.supportsDeveloperRole!==!1?"developer":"system",msgIndex=0,sourceIndex=0;for(let msg of transformedMessages){let isLeadingSystemMessage=sourceIndex++===0&&msg.role==="system";if(msg.role==="configurationUpdate"){if(!supportsConfigurationUpdate(model))continue;messages[messages.length-1]?.type==="configuration_update"?messages[messages.length-1]={type:"configuration_update",reasoning:{effort:msg.effort}}:messages.push({type:"configuration_update",reasoning:{effort:msg.effort}});continue}if(msg.role==="system"){if(isLeadingSystemMessage||appendSystemToolAdditions(msg,`system:${msgIndex}`),!isLeadingSystemMessage||includeInitialSystemMessage){let text=isLeadingSystemMessage?getSystemMessageText(msg):renderSystemMessageUpdate(msg);if(text.length>0&&isLeadingSystemMessage&&options?.systemPromptCacheBreakpoint===!0){let block={type:"input_text",text:sanitizeSurrogates(text),prompt_cache_breakpoint:{mode:"explicit"}};messages.push({role:instructionRole,content:[block]})}else text.length>0&&messages.push({role:instructionRole,content:sanitizeSurrogates(text)})}}else if(msg.role==="user")if(typeof msg.content=="string")messages.push(withContextProvenance({role:"user",content:[{type:"input_text",text:sanitizeSurrogates(msg.content)}]},msg,options?.sealContextProvenance));else{let content=msg.content.map(item=>item.type==="text"?{type:"input_text",text:sanitizeSurrogates(item.text)}:{type:"input_image",detail:"auto",image_url:`data:${item.mimeType};base64,${item.data}`});if(content.length===0)continue;messages.push(withContextProvenance({role:"user",content},msg,options?.sealContextProvenance))}else if(msg.role==="assistant"){let output=[],assistantMsg=msg,isSameProviderAndApi=assistantMsg.provider===model.provider&&assistantMsg.api===model.api,isSameModel=isSameProviderAndApi&&assistantMsg.model===model.id,isDifferentModel=isSameProviderAndApi&&assistantMsg.model!==model.id,textBlockIndex=0,pushAssistantText=(text,textSignature)=>{let parsedSignature=parseTextSignature(textSignature),fallbackMessageId=textBlockIndex===0?`msg_pi_${msgIndex}`:`msg_pi_${msgIndex}_${textBlockIndex}`;textBlockIndex++;let msgId=parsedSignature?.id;msgId?msgId.length>64&&(msgId=`msg_${shortHash(msgId)}`):msgId=fallbackMessageId,output.push({type:"message",role:"assistant",content:[{type:"output_text",text:sanitizeSurrogates(text),annotations:[]}],status:"completed",id:msgId,phase:parsedSignature?.phase})};for(let block of msg.content)if(block.type==="thinking"){let reasoningItem=parseReasoningSignature(block.thinkingSignature);reasoningItem?output.push(reasoningItem):block.thinkingSignature&&block.thinking.trim()!==""&&pushAssistantText(block.thinking)}else if(block.type==="providerNative"){let raw=block.raw;isSameModel&&block.subtype==="web_search_call"&&raw!==null&&typeof raw=="object"&&"type"in raw&&raw.type==="web_search_call"&&"id"in raw&&typeof raw.id=="string"&&"action"in raw&&"status"in raw&&(raw.status==="completed"||raw.status==="failed")&&output.push(raw)}else if(block.type==="text"){let textBlock=block;pushAssistantText(textBlock.text,textBlock.textSignature)}else if(block.type==="toolCall"){let toolCall=block,[callId,itemIdRaw]=toolCall.id.split("|"),customInputProperty=options?.grammarToolInputProperties?.get(toolCall.name),isPersistedFreeform=itemIdRaw===CUSTOM_TOOL_CALL_ITEM_ID_SENTINEL,isFreeform=isFreeformToolName(toolCall.name,declaredTools)||isPersistedFreeform,itemId=isPersistedFreeform?void 0:itemIdRaw,itemIdPrefix=customInputProperty===void 0?"fc_":"ctc_";(isDifferentModel||!itemId?.startsWith(itemIdPrefix))&&(itemId=void 0);let canReplayNamespace=isSameModel||toolPlacement.deferred.has(toolCall.name);customInputProperty!==void 0?output.push({type:"custom_tool_call",...itemId!==void 0?{id:itemId}:{},call_id:callId,name:toolCall.name,input:sanitizeSurrogates(getGrammarToolInput(toolCall.name,toolCall.arguments,customInputProperty)),...canReplayNamespace&&toolCall.namespace!==void 0?{namespace:toolCall.namespace}:{}}):isFreeform?output.push({type:"custom_tool_call",call_id:callId,name:toolCall.name,input:getFreeformToolInput(toolCall.arguments),...canReplayNamespace&&toolCall.namespace!==void 0?{namespace:toolCall.namespace}:{}}):output.push({type:"function_call",...itemId?.startsWith("fc_")?{id:itemId}:{},call_id:callId,name:toolCall.name,arguments:JSON.stringify(toolCall.arguments),...canReplayNamespace&&toolCall.namespace!==void 0?{namespace:toolCall.namespace}:{}})}if(output.length===0)continue;messages.push(...output.map(item=>withContextProvenance(item,msg,options?.sealContextProvenance)))}else if(msg.role==="toolResult"){let[callId,itemIdRaw]=msg.toolCallId.split("|"),output=convertToolResultOutput(model,msg.content),customInputProperty=options?.grammarToolInputProperties?.get(msg.toolName),isPersistedFreeform=itemIdRaw===CUSTOM_TOOL_CALL_ITEM_ID_SENTINEL;customInputProperty!==void 0?messages.push(withContextProvenance({type:"custom_tool_call_output",call_id:callId,output},msg,options?.sealContextProvenance)):isFreeformToolName(msg.toolName,declaredTools)||isPersistedFreeform?messages.push(withContextProvenance({type:"custom_tool_call_output",call_id:callId,name:msg.toolName,output},msg,options?.sealContextProvenance)):messages.push(withContextProvenance({type:"function_call_output",call_id:callId,output},msg,options?.sealContextProvenance));let deferredTools=[];for(let name of msg.addedToolNames??[]){let tool=toolPlacement.deferred.get(name);!tool||loadedToolNames.has(name)||(loadedToolNames.add(name),deferredTools.push(tool))}if(deferredTools.length>0&&deferredToolsMode==="additional-tools")messages.push(withContextProvenance({type:"additional_tools",role:"developer",tools:convertResponsesTools(deferredTools,options?.toolOptions)},msg,options?.sealContextProvenance));else if(deferredTools.length>0&&deferredToolsMode==="tool-search"){let names=deferredTools.map(tool=>tool.name),searchCallId=`pi_tool_load_${shortHash(`${msg.toolCallId}:${names.join(",")}`)}`;messages.push(withContextProvenance({type:"tool_search_call",call_id:searchCallId,execution:"client",status:"completed",arguments:{query:names.join(" "),limit:names.length}},msg,options?.sealContextProvenance)),messages.push(withContextProvenance({type:"tool_search_output",call_id:searchCallId,execution:"client",status:"completed",tools:convertResponsesTools(deferredTools,{...options?.toolOptions,toolSearchResult:!0})},msg,options?.sealContextProvenance))}}isLeadingSystemMessage||msgIndex++}return messages}function convertResponsesTools(tools,options){let defaultStrict=options?.strict===void 0?!1:options.strict,supportsStrictMode=options?.supportsStrictMode??!0,supportsOpenAIGrammarTools=options?.supportsOpenAIGrammarTools??!1;return[...tools].sort((a,b)=>a.name<b.name?-1:a.name>b.name?1:0).map(tool=>{let grammar=resolveGrammarConstrainedSampling(tool,supportsOpenAIGrammarTools);if(grammar)return{type:"custom",name:tool.name,description:tool.description,format:{type:"grammar",syntax:grammar.format,definition:grammar.definition},...options?.toolSearchResult?{defer_loading:!0}:{}};if(tool.freeform)return{type:"custom",name:tool.name,description:tool.description,format:tool.freeform,...options?.toolSearchResult?{defer_loading:!0}:{}};let strict=resolveJsonSchemaStrictSampling(tool,supportsStrictMode)??defaultStrict,functionTool={type:"function",name:tool.name,description:tool.description,parameters:getJsonSchemaToolParameters(tool,strict===!0),...options?.toolSearchResult?{defer_loading:!0}:{}};return supportsStrictMode&&(functionTool.strict=strict),functionTool})}function getCustomToolCallInput(block){let property=block.customInput?.property;if(property===void 0)return"";let value=block.arguments[property];return typeof value=="string"?value:""}function appendCustomToolCallInput(block,nextInput,close){let customInput=block.customInput;if(!customInput)return;let delta=appendGrammarToolInputJsonDelta(customInput.jsonBuffer,customInput.property,nextInput,close);return block.arguments={[customInput.property]:nextInput},delta}var MAX_NATIVE_IMAGE_BASE64_CHARS=24*1024*1024;function readNativeImageGenerationCall(value){if(!(typeof value!="object"||value===null||!("type"in value)||!("status"in value))&&!(value.type!=="image_generation_call"||typeof value.status!="string"))return{type:"image_generation_call",..."id"in value&&typeof value.id=="string"?{id:value.id}:{},status:value.status,..."result"in value&&(typeof value.result=="string"||value.result===null)?{result:value.result}:{},..."revised_prompt"in value&&typeof value.revised_prompt=="string"?{revised_prompt:value.revised_prompt}:{}}}function isValidBase64(value){return value.length>0&&value.length%4===0&&/^[A-Za-z0-9+/]*={0,2}$/.test(value)}function reconcileNativeImageGenerationCall(item){return item.status!=="completed"?{type:"image_generation_call",...item.id!==void 0?{id:item.id}:{},status:item.status}:typeof item.result!="string"||!isValidBase64(item.result)?{type:"image_generation_call",...item.id!==void 0?{id:item.id}:{},status:"malformed"}:{type:"image_generation_call",...item.id!==void 0?{id:item.id}:{},status:"completed",result:item.result,...item.revised_prompt?.trim()?{revised_prompt:item.revised_prompt}:{}}}async function processResponsesStream(openaiStream,output,stream,model,options){enableAssistantCapture(stream);let sawTerminalResponseEvent=!1,nativeImageBase64Chars=0,outputSlots=new Map,reasoningBlocksById=new Map,nativeImageCharsByOutputIndex=new Map,finalizedNativeImageOutputIndexes=new Set,applyMessagePhaseStopReason=item=>{item.type==="message"&&item.phase==="final_answer"&&(output.stopReason="stop")},getSlot=(outputIndex,type)=>{let slot=outputSlots.get(outputIndex);return slot?.type===type?slot:void 0},pushToolCallDelta=(slot,delta)=>{delta!==void 0&&stream.push({type:"toolcall_delta",contentIndex:slot.contentIndex,delta,partial:output})},createSlot=(outputIndex,item)=>{if(item.type==="reasoning"){let block2={type:"thinking",thinking:""};output.content.push(block2);let slot2={type:"thinking",block:block2,contentIndex:output.content.length-1};return outputSlots.set(outputIndex,slot2),stream.push({type:"thinking_start",contentIndex:slot2.contentIndex,partial:output}),slot2}if(item.type==="message"){applyMessagePhaseStopReason(item);let block2={type:"text",text:""};output.content.push(block2);let slot2={type:"text",block:block2,contentIndex:output.content.length-1};return outputSlots.set(outputIndex,slot2),stream.push({type:"text_start",contentIndex:slot2.contentIndex,partial:output}),slot2}if(item.type==="function_call"){let block2={type:"toolCall",id:`${item.call_id}|${item.id}`,name:item.name,arguments:{},...item.namespace!==void 0?{namespace:item.namespace}:{},partialJson:item.arguments||""};output.content.push(block2);let slot2={type:"toolCall",block:block2,contentIndex:output.content.length-1};return outputSlots.set(outputIndex,slot2),stream.push({type:"toolcall_start",contentIndex:slot2.contentIndex,partial:output},{rawArgumentPrefix:block2.partialJson}),slot2}if(isResponseCustomToolCallItem(item)){let inputProperty=options?.grammarToolInputProperties?.get(item.name)??"input",input=item.input||"",block2={type:"toolCall",id:`${item.call_id}|${item.id??CUSTOM_TOOL_CALL_ITEM_ID_SENTINEL}`,name:item.name,arguments:{[inputProperty]:input},...item.namespace!==void 0?{namespace:item.namespace}:{},customInput:{property:inputProperty,jsonBuffer:{input:"",started:!1,closed:!1}}};output.content.push(block2);let slot2={type:"toolCall",block:block2,contentIndex:output.content.length-1};return outputSlots.set(outputIndex,slot2),stream.push({type:"toolcall_start",contentIndex:slot2.contentIndex,partial:output}),slot2}let imageItem=readNativeImageGenerationCall(item),block={type:"providerNative",subtype:item.type,raw:imageItem?reconcileNativeImageGenerationCall(imageItem):item},slot={type:"providerNative",block,contentIndex:output.content.length};return imageItem&&reconcileNativeImageSlot(outputIndex,slot,imageItem),output.content.push(block),outputSlots.set(outputIndex,slot),pushAssistantProgress(stream,{type:"message_block_state",contentIndex:slot.contentIndex,phase:"insert",block}),slot},getOrCreateSlot=(outputIndex,item)=>outputSlots.get(outputIndex)??createSlot(outputIndex,item);function scrubNativeImageResults(){for(let block of output.content){if(block.type!=="providerNative"||block.subtype!=="image_generation_call")continue;let item=readNativeImageGenerationCall(block.raw);typeof item?.result=="string"&&(block.raw={type:"image_generation_call",...item.id!==void 0?{id:item.id}:{},status:"malformed"})}nativeImageBase64Chars=0,nativeImageCharsByOutputIndex.clear()}function reconcileNativeImageSlot(outputIndex,slot,item){let reconciled=reconcileNativeImageGenerationCall(item),previousChars=nativeImageCharsByOutputIndex.get(outputIndex)??0,nextChars=typeof reconciled.result=="string"?reconciled.result.length:0,nextTotal=nativeImageBase64Chars-previousChars+nextChars;if(nextTotal>MAX_NATIVE_IMAGE_BASE64_CHARS)throw scrubNativeImageResults(),new Error("Native image generation results exceed the 24 MiB base64 limit");nativeImageBase64Chars=nextTotal,nextChars>0?nativeImageCharsByOutputIndex.set(outputIndex,nextChars):nativeImageCharsByOutputIndex.delete(outputIndex),slot.block.subtype="image_generation_call",slot.block.raw=reconciled}let backfillNativeImageGenerationCalls=responseOutput=>{for(let[outputIndex,outputItem]of responseOutput.entries()){if(finalizedNativeImageOutputIndexes.has(outputIndex))continue;let imageItem=readNativeImageGenerationCall(outputItem);if(!imageItem)continue;let existingSlot=getSlot(outputIndex,"providerNative"),slot=existingSlot??createSlot(outputIndex,outputItem);existingSlot&&reconcileNativeImageSlot(outputIndex,existingSlot,imageItem),slot&&pushAssistantProgress(stream,{type:"message_block_state",contentIndex:slot.contentIndex,phase:"end",block:slot.block}),outputSlots.delete(outputIndex)}},backfillReasoningSignatures=responseOutput=>{for(let item of responseOutput){if(item.type!=="reasoning"||!item.encrypted_content)continue;let block=reasoningBlocksById.get(item.id);if(!block?.thinkingSignature)continue;let storedItem=parseReasoningSignature(block.thinkingSignature);!storedItem||storedItem.encrypted_content||(block.thinkingSignature=JSON.stringify({...storedItem,encrypted_content:item.encrypted_content}))}},finalizeResponse=response=>{sawTerminalResponseEvent=!0,backfillReasoningSignatures(response.output??[]),backfillNativeImageGenerationCalls(response.output??[]),response?.id&&(output.responseId=response.id);let promptCacheDiagnostics=parsePromptCacheDiagnostics(response?.prompt_cache_diagnostics);if(promptCacheDiagnostics&&(output.promptCacheDiagnostics=promptCacheDiagnostics),response?.usage){let inputDetails=response.usage.input_tokens_details,cachedTokens=inputDetails?.cached_tokens||0,cacheWriteTokens=inputDetails?.cache_write_tokens??inputDetails?.cache_creation_tokens??0,reasoning=reportedCount(response.usage.output_tokens_details?.reasoning_tokens);output.usage={input:Math.max(0,(response.usage.input_tokens||0)-cachedTokens-cacheWriteTokens),output:response.usage.output_tokens||0,cacheRead:cachedTokens,cacheWrite:cacheWriteTokens,...reasoning===void 0?{}:{reasoning},totalTokens:response.usage.total_tokens||0,cost:{input:0,output:0,cacheRead:0,cacheWrite:0,total:0}}}if(calculateCost(model,output.usage),options?.applyServiceTierPricing){let serviceTier=options.resolveServiceTier?options.resolveServiceTier(response?.service_tier,options.serviceTier):response?.service_tier??options.serviceTier;options.applyServiceTierPricing(output.usage,serviceTier)}let status=response?.status,incompleteDetails=response?.incomplete_details,incompleteReason=typeof incompleteDetails?.reason=="string"?incompleteDetails.reason:void 0;output.rawStopReason=incompleteReason?`${status}.${incompleteReason}`:status;let mappedStop=mapStopReason(status,incompleteReason);output.stopReason=mappedStop.stopReason,mappedStop.errorMessage===void 0?delete output.errorMessage:output.errorMessage=mappedStop.errorMessage,output.content.some(b=>b.type==="toolCall")&&output.stopReason==="stop"&&(output.stopReason="toolUse")};for await(let event of withResponsesCompletionGrace(openaiStream))if(await options?.onProviderStreamEvent?.(event,model),event.type==="response.created")output.responseId=event.response.id;else if(event.type==="response.output_item.added")createSlot(event.output_index,event.item);else if(event.type==="response.reasoning_summary_text.delta"){let slot=getSlot(event.output_index,"thinking");if(!slot)continue;slot.block.thinking+=event.delta,stream.push({type:"thinking_delta",contentIndex:slot.contentIndex,delta:event.delta,partial:output})}else if(event.type==="response.reasoning_summary_part.done"){let slot=getSlot(event.output_index,"thinking");if(!slot)continue;slot.block.thinking+=`
|
|
4
|
-
|
|
5
|
-
`,stream.push({type:"thinking_delta",contentIndex:slot.contentIndex,delta:`
|
|
6
|
-
|
|
7
|
-
`,partial:output})}else if(event.type==="response.reasoning_text.delta"){let slot=getSlot(event.output_index,"thinking");if(!slot)continue;slot.block.thinking+=event.delta,stream.push({type:"thinking_delta",contentIndex:slot.contentIndex,delta:event.delta,partial:output})}else if(event.type==="response.output_text.delta"){let slot=getSlot(event.output_index,"text");if(!slot)continue;slot.block.text+=event.delta,stream.push({type:"text_delta",contentIndex:slot.contentIndex,delta:event.delta,partial:output})}else if(event.type==="response.refusal.delta"){let slot=getSlot(event.output_index,"text");if(!slot)continue;slot.block.text+=event.delta,stream.push({type:"text_delta",contentIndex:slot.contentIndex,delta:event.delta,partial:output})}else if(event.type==="response.function_call_arguments.delta"){let slot=getSlot(event.output_index,"toolCall");if(!slot||slot.block.partialJson===void 0)continue;slot.block.partialJson+=event.delta,slot.block.arguments=parseStreamingJson(slot.block.partialJson),pushToolCallDelta(slot,event.delta)}else if(event.type==="response.function_call_arguments.done"){let slot=getSlot(event.output_index,"toolCall");if(!slot||slot.block.partialJson===void 0)continue;let previousPartialJson=slot.block.partialJson;if(slot.block.partialJson=event.arguments,slot.block.arguments=parseStreamingJson(slot.block.partialJson),event.arguments.startsWith(previousPartialJson)){let delta=event.arguments.slice(previousPartialJson.length);delta.length>0&&pushToolCallDelta(slot,delta)}else pushAssistantProgress(stream,{type:"message_tool_arguments_reset",contentIndex:slot.contentIndex,rawArgumentPrefix:event.arguments,parsedThrough:event.arguments.length})}else if(event.type==="response.custom_tool_call_input.delta"){let slot=getSlot(event.output_index,"toolCall");if(!slot?.block.customInput)continue;pushToolCallDelta(slot,appendCustomToolCallInput(slot.block,getCustomToolCallInput(slot.block)+event.delta,!1))}else if(event.type==="response.custom_tool_call_input.done"){let slot=getSlot(event.output_index,"toolCall");if(!slot?.block.customInput)continue;pushToolCallDelta(slot,appendCustomToolCallInput(slot.block,event.input,!0))}else if(event.type==="response.output_item.done"){let item=event.item;applyMessagePhaseStopReason(item);let slot=getOrCreateSlot(event.output_index,item),imageItem=readNativeImageGenerationCall(item);if(imageItem&&slot?.type==="providerNative")reconcileNativeImageSlot(event.output_index,slot,imageItem),pushAssistantProgress(stream,{type:"message_block_state",contentIndex:slot.contentIndex,phase:"end",block:slot.block}),finalizedNativeImageOutputIndexes.add(event.output_index),outputSlots.delete(event.output_index);else if(item.type==="reasoning"&&slot?.type==="thinking"){let summaryText=item.summary?.map(s=>s.text).join(`
|
|
8
|
-
|
|
9
|
-
`)||"",contentText=item.content?.map(c=>c.text).join(`
|
|
10
|
-
|
|
11
|
-
`)||"";slot.block.thinking=summaryText||contentText||slot.block.thinking,slot.block.thinkingSignature=JSON.stringify(item),reasoningBlocksById.set(item.id,slot.block),stream.push({type:"thinking_end",contentIndex:slot.contentIndex,content:slot.block.thinking,partial:output}),outputSlots.delete(event.output_index)}else if(item.type==="message"&&slot?.type==="text")slot.block.text=item.content?.map(c=>c.type==="output_text"?c.text:c.refusal).join("")||"",slot.block.textSignature=encodeTextSignatureV1(item.id,item.phase??void 0),stream.push({type:"text_end",contentIndex:slot.contentIndex,content:slot.block.text,partial:output}),outputSlots.delete(event.output_index);else if(item.type==="function_call"&&slot?.type==="toolCall"&&slot.block.partialJson!==void 0)slot.block.arguments=parseStreamingJson(item.arguments||slot.block.partialJson||"{}"),item.namespace!==void 0&&(slot.block.namespace=item.namespace),delete slot.block.partialJson,stream.push({type:"toolcall_end",contentIndex:slot.contentIndex,toolCall:slot.block,partial:output}),outputSlots.delete(event.output_index);else if(item.type==="custom_tool_call"&&slot?.type==="toolCall"&&slot.block.customInput)pushToolCallDelta(slot,appendCustomToolCallInput(slot.block,item.input??getCustomToolCallInput(slot.block),!0)),item.namespace!==void 0&&(slot.block.namespace=item.namespace),delete slot.block.customInput,stream.push({type:"toolcall_end",contentIndex:slot.contentIndex,toolCall:slot.block,partial:output}),outputSlots.delete(event.output_index);else if(isResponseCustomToolCallItem(item)&&slot?.type==="toolCall"){let input=typeof item.input=="string"?item.input:"";slot.block.arguments={input},delete slot.block.partialJson,stream.push({type:"toolcall_end",contentIndex:slot.contentIndex,toolCall:slot.block,partial:output}),outputSlots.delete(event.output_index)}else slot?.type==="providerNative"&&(slot.block.subtype=item.type,slot.block.raw=item,pushAssistantProgress(stream,{type:"message_block_state",contentIndex:slot.contentIndex,phase:"end",block:slot.block}),outputSlots.delete(event.output_index))}else if(event.type==="response.completed"||event.type==="response.incomplete"){if(finalizeResponse(event.response),options?.siwc&&event.type==="response.incomplete")throw new Error(`Response incomplete: ${event.response.incomplete_details?.reason??"unknown reason"}`)}else if(event.type==="error"){let errorEvent=event,code=errorEvent.error?.code??errorEvent.code??errorEvent.status,message=errorEvent.error?.message||errorEvent.message||"Unknown error",failure=new Error(code==null?message:`Error Code ${code}: ${message}`);throw options?.siwc?Object.assign(failure,{code:typeof code=="string"?code:void 0,status:errorEvent.status,param:errorEvent.error?.param??errorEvent.param}):failure}else if(event.type==="response.failed"){sawTerminalResponseEvent=!0,output.rawStopReason=event.response?.status;let error=event.response?.error,details=event.response?.incomplete_details,msg=error?`${error.code||"unknown"}: ${error.message||"no message"}`:details?.reason?`incomplete: ${details.reason}`:"Unknown error (no error details in response)",failure=new Error(msg);throw options?.siwc?Object.assign(failure,{code:error?.code,param:error&&"param"in error?error.param:void 0}):failure}let hasFinalizedToolCall=output.content.some(block=>block.type==="toolCall"&&!("partialJson"in block));if(!sawTerminalResponseEvent&&(options?.siwc||!hasFinalizedToolCall))throw new Error("OpenAI Responses stream ended before a terminal response event");if(output.stopReason==="toolUse")for(let block of output.content){if(block.type!=="toolCall")continue;let toolCall=block;if(toolCall.partialJson!==void 0||toolCall.customInput!==void 0)throw new Error(`OpenAI Responses stream completed with an unfinished tool call: ${toolCall.name} (${toolCall.id})`)}}function mapStopReason(status,incompleteReason){if(!status)return{stopReason:"stop"};switch(status){case"completed":return{stopReason:"stop"};case"incomplete":return incompleteReason==="max_output_tokens"?{stopReason:"length"}:{stopReason:"error",errorMessage:incompleteReason?`Response incomplete: ${incompleteReason}`:"Response incomplete without a provider reason"};case"failed":case"cancelled":return{stopReason:"error"};case"in_progress":case"queued":return{stopReason:"stop"};default:{let _exhaustive=status;throw new Error(`Unhandled stop reason: ${_exhaustive}`)}}}export{supportsOpenAIResponsesExplicitPromptCacheMode,isOpenAIResponsesPromptCacheModel,findPromptCacheComparisonResponseId,withPromptCacheComparison,CONTEXT_PROVENANCE_FIELD,getContextProvenance,copyContextProvenance,contextProvenanceFingerprint,resolveResponsesDeferredToolsMode,resolveResponsesToolPlacement,convertResponsesMessages,convertResponsesTools,processResponsesStream};
|