@herbertgao/pi-extensions 2026.9.10 → 2026.9.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/README.md +1 -0
- package/THIRD_PARTY_NOTICES.md +26 -0
- package/node_modules/@herbertgao/pi-bark/package.json +2 -2
- package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +2 -2
- package/node_modules/@herbertgao/pi-cc-extensions/README.md +2 -2
- package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -2
- package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +6 -0
- package/node_modules/@herbertgao/pi-subagents/package.json +2 -2
- package/node_modules/@herbertgao/resume-from/package.json +1 -1
- package/node_modules/@herbertgao/sol-pi/README.md +3 -3
- package/node_modules/@herbertgao/sol-pi/THIRD_PARTY_NOTICES.md +4 -4
- package/node_modules/@herbertgao/sol-pi/agents-install.md +4 -4
- package/node_modules/@herbertgao/sol-pi/docs/compatibility.md +6 -6
- package/node_modules/@herbertgao/sol-pi/package.json +2 -2
- package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +2 -2
- package/node_modules/@narumitw/pi-btw/dist/index.ts +39 -89
- package/node_modules/@narumitw/pi-btw/dist/index.ts.map +3 -3
- package/node_modules/@narumitw/pi-btw/package.json +4 -4
- package/node_modules/@narumitw/pi-btw/src/btw.ts +28 -87
- package/node_modules/@narumitw/pi-btw/src/main-tree-picker.ts +8 -0
- package/node_modules/@narumitw/pi-btw/src/side-thread.ts +40 -37
- package/node_modules/@narumitw/pi-caffeinate/README.md +21 -66
- package/node_modules/@narumitw/pi-caffeinate/dist/index.ts +10 -41
- package/node_modules/@narumitw/pi-caffeinate/dist/index.ts.map +2 -2
- package/node_modules/@narumitw/pi-caffeinate/package.json +50 -51
- package/node_modules/@narumitw/pi-caffeinate/src/caffeinate.ts +637 -663
- package/node_modules/@narumitw/pi-caffeinate/src/dbus-inhibit.ts +114 -120
- package/node_modules/@narumitw/pi-caffeinate/src/inhibitor-process.ts +29 -29
- package/node_modules/@narumitw/pi-caffeinate/src/inhibitors.ts +108 -126
- package/node_modules/@narumitw/pi-caffeinate/src/settings.ts +124 -128
- package/node_modules/pi-mcp-adapter/CHANGELOG.md +62 -0
- package/node_modules/pi-mcp-adapter/README.md +75 -7
- package/node_modules/pi-mcp-adapter/bearer-command-resolver.ts +193 -0
- package/node_modules/pi-mcp-adapter/cli.js +56 -5
- package/node_modules/pi-mcp-adapter/commands.ts +96 -5
- package/node_modules/pi-mcp-adapter/config.ts +73 -6
- package/node_modules/pi-mcp-adapter/direct-tool-surface.ts +22 -28
- package/node_modules/pi-mcp-adapter/direct-tools.ts +13 -5
- package/node_modules/pi-mcp-adapter/dist/abort.d.ts +2 -0
- package/node_modules/pi-mcp-adapter/dist/abort.js +36 -0
- package/node_modules/pi-mcp-adapter/dist/abort.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/bearer-command-resolver.d.ts +6 -0
- package/node_modules/pi-mcp-adapter/dist/bearer-command-resolver.js +194 -0
- package/node_modules/pi-mcp-adapter/dist/bearer-command-resolver.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/config.d.ts +5 -0
- package/node_modules/pi-mcp-adapter/dist/config.js +75 -6
- package/node_modules/pi-mcp-adapter/dist/config.js.map +1 -1
- package/node_modules/pi-mcp-adapter/dist/consent-manager.d.ts +18 -0
- package/node_modules/pi-mcp-adapter/dist/consent-manager.js +88 -0
- package/node_modules/pi-mcp-adapter/dist/consent-manager.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/elicitation-handler.d.ts +17 -0
- package/node_modules/pi-mcp-adapter/dist/elicitation-handler.js +316 -0
- package/node_modules/pi-mcp-adapter/dist/elicitation-handler.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/errors.d.ts +131 -0
- package/node_modules/pi-mcp-adapter/dist/errors.js +278 -0
- package/node_modules/pi-mcp-adapter/dist/errors.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/http-ca.d.ts +8 -0
- package/node_modules/pi-mcp-adapter/dist/http-ca.js +88 -0
- package/node_modules/pi-mcp-adapter/dist/http-ca.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/jev-client.d.ts +15 -0
- package/node_modules/pi-mcp-adapter/dist/jev-client.js +340 -0
- package/node_modules/pi-mcp-adapter/dist/jev-client.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/jev-contracts.d.ts +77 -0
- package/node_modules/pi-mcp-adapter/dist/jev-contracts.js +2 -0
- package/node_modules/pi-mcp-adapter/dist/jev-contracts.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/jev-key-store.d.ts +22 -0
- package/node_modules/pi-mcp-adapter/dist/jev-key-store.js +85 -0
- package/node_modules/pi-mcp-adapter/dist/jev-key-store.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/json-schema-validator.d.ts +2 -0
- package/node_modules/pi-mcp-adapter/dist/json-schema-validator.js +55 -0
- package/node_modules/pi-mcp-adapter/dist/json-schema-validator.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/lifecycle.d.ts +56 -0
- package/node_modules/pi-mcp-adapter/dist/lifecycle.js +410 -0
- package/node_modules/pi-mcp-adapter/dist/lifecycle.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/logger.d.ts +51 -0
- package/node_modules/pi-mcp-adapter/dist/logger.js +131 -0
- package/node_modules/pi-mcp-adapter/dist/logger.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-auth-fetch.d.ts +26 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-auth-fetch.js +103 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-auth-fetch.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-auth-flow.d.ts +118 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-auth-flow.js +1098 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-auth-flow.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-auth.d.ts +168 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-auth.js +1117 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-auth.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-bearer-store.js +7 -140
- package/node_modules/pi-mcp-adapter/dist/mcp-bearer-store.js.map +1 -1
- package/node_modules/pi-mcp-adapter/dist/mcp-callback-server.d.ts +53 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-callback-server.js +441 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-callback-server.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-oauth-provider.d.ts +148 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-oauth-provider.js +689 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-oauth-provider.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-probe.d.ts +6 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-probe.js +166 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-probe.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-tasks.d.ts +102 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-tasks.js +369 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-tasks.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-trace.d.ts +95 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-trace.js +242 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-trace.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/metadata-cache.js +2 -11
- package/node_modules/pi-mcp-adapter/dist/metadata-cache.js.map +1 -1
- package/node_modules/pi-mcp-adapter/dist/npx-resolver.d.ts +6 -0
- package/node_modules/pi-mcp-adapter/dist/npx-resolver.js +505 -0
- package/node_modules/pi-mcp-adapter/dist/npx-resolver.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/request-headers-command.d.ts +10 -0
- package/node_modules/pi-mcp-adapter/dist/request-headers-command.js +312 -0
- package/node_modules/pi-mcp-adapter/dist/request-headers-command.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/runtime-owner.d.ts +13 -0
- package/node_modules/pi-mcp-adapter/dist/runtime-owner.js +97 -0
- package/node_modules/pi-mcp-adapter/dist/runtime-owner.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/sampling-handler.d.ts +17 -0
- package/node_modules/pi-mcp-adapter/dist/sampling-handler.js +203 -0
- package/node_modules/pi-mcp-adapter/dist/sampling-handler.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/secure-keyring.d.ts +11 -0
- package/node_modules/pi-mcp-adapter/dist/secure-keyring.js +172 -0
- package/node_modules/pi-mcp-adapter/dist/secure-keyring.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/server-manager.d.ts +157 -0
- package/node_modules/pi-mcp-adapter/dist/server-manager.js +1743 -0
- package/node_modules/pi-mcp-adapter/dist/server-manager.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/session-approvals.d.ts +34 -0
- package/node_modules/pi-mcp-adapter/dist/session-approvals.js +140 -0
- package/node_modules/pi-mcp-adapter/dist/session-approvals.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/session-recovery.d.ts +26 -0
- package/node_modules/pi-mcp-adapter/dist/session-recovery.js +142 -0
- package/node_modules/pi-mcp-adapter/dist/session-recovery.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/state.d.ts +73 -0
- package/node_modules/pi-mcp-adapter/dist/state.js +2 -0
- package/node_modules/pi-mcp-adapter/dist/state.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/types.d.ts +36 -0
- package/node_modules/pi-mcp-adapter/dist/types.js +18 -0
- package/node_modules/pi-mcp-adapter/dist/types.js.map +1 -1
- package/node_modules/pi-mcp-adapter/dist/ui-resource-handler.d.ts +17 -0
- package/node_modules/pi-mcp-adapter/dist/ui-resource-handler.js +219 -0
- package/node_modules/pi-mcp-adapter/dist/ui-resource-handler.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/unix-socket-transport.d.ts +15 -0
- package/node_modules/pi-mcp-adapter/dist/unix-socket-transport.js +85 -0
- package/node_modules/pi-mcp-adapter/dist/unix-socket-transport.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/utils.d.ts +1 -0
- package/node_modules/pi-mcp-adapter/dist/utils.js +13 -0
- package/node_modules/pi-mcp-adapter/dist/utils.js.map +1 -1
- package/node_modules/pi-mcp-adapter/elicitation-handler.ts +29 -19
- package/node_modules/pi-mcp-adapter/examples/jev-accessibility-loop.mjs +85 -0
- package/node_modules/pi-mcp-adapter/examples/jev-semantic-filter.mjs +34 -0
- package/node_modules/pi-mcp-adapter/index.ts +129 -6
- package/node_modules/pi-mcp-adapter/init.ts +6 -19
- package/node_modules/pi-mcp-adapter/jev-client.ts +273 -0
- package/node_modules/pi-mcp-adapter/jev-contracts.ts +53 -0
- package/node_modules/pi-mcp-adapter/jev-key-store.ts +79 -0
- package/node_modules/pi-mcp-adapter/lifecycle.ts +14 -3
- package/node_modules/pi-mcp-adapter/mcp-bearer-store.ts +7 -142
- package/node_modules/pi-mcp-adapter/mcp-code.ts +119 -11
- package/node_modules/pi-mcp-adapter/mcp-output-guard.ts +4 -0
- package/node_modules/pi-mcp-adapter/mcp-references.ts +5 -3
- package/node_modules/pi-mcp-adapter/mcp-script-worker.mjs +5 -0
- package/node_modules/pi-mcp-adapter/mcp-tasks.ts +467 -0
- package/node_modules/pi-mcp-adapter/metadata-cache.ts +2 -13
- package/node_modules/pi-mcp-adapter/namespace-tools.ts +1 -1
- package/node_modules/pi-mcp-adapter/package.json +17 -9
- package/node_modules/pi-mcp-adapter/proxy-modes.ts +319 -166
- package/node_modules/pi-mcp-adapter/request-headers-command.ts +6 -3
- package/node_modules/pi-mcp-adapter/search-ranking.ts +38 -7
- package/node_modules/pi-mcp-adapter/secure-keyring.ts +170 -0
- package/node_modules/pi-mcp-adapter/semantic-search.ts +186 -0
- package/node_modules/pi-mcp-adapter/server-manager.ts +293 -50
- package/node_modules/pi-mcp-adapter/session-approvals.ts +14 -0
- package/node_modules/pi-mcp-adapter/skills/mcp-scripting/SKILL.md +2 -0
- package/node_modules/pi-mcp-adapter/state.ts +3 -1
- package/node_modules/pi-mcp-adapter/tool-approval.ts +26 -4
- package/node_modules/pi-mcp-adapter/tool-metadata.ts +6 -14
- package/node_modules/pi-mcp-adapter/tool-registrar.ts +8 -6
- package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +4 -1
- package/node_modules/pi-mcp-adapter/types.ts +53 -0
- package/node_modules/pi-mcp-adapter/ui-server.ts +17 -9
- package/node_modules/pi-mcp-adapter/utils.ts +16 -0
- package/node_modules/pi-multi-account/CHANGELOG.md +1209 -0
- package/node_modules/pi-multi-account/CONTRIBUTING.md +61 -0
- package/node_modules/pi-multi-account/LICENSE +21 -0
- package/node_modules/pi-multi-account/README.md +197 -0
- package/node_modules/pi-multi-account/SECURITY.md +27 -0
- package/node_modules/pi-multi-account/auth-file-transaction.ts +56 -0
- package/node_modules/pi-multi-account/child-usability.ts +233 -0
- package/node_modules/pi-multi-account/compaction-summary.ts +32 -0
- package/node_modules/pi-multi-account/completion-route-planner.ts +224 -0
- package/node_modules/pi-multi-account/context-guard.ts +420 -0
- package/node_modules/pi-multi-account/cursor/LICENSE +21 -0
- package/node_modules/pi-multi-account/cursor/NOTICE +2 -0
- package/node_modules/pi-multi-account/cursor/auth.ts +165 -0
- package/node_modules/pi-multi-account/cursor/bridge-handle.ts +155 -0
- package/node_modules/pi-multi-account/cursor/conversation-registry.ts +104 -0
- package/node_modules/pi-multi-account/cursor/cursor-models-raw.json +611 -0
- package/node_modules/pi-multi-account/cursor/cursor-shared.ts +192 -0
- package/node_modules/pi-multi-account/cursor/h2-bridge.mjs +175 -0
- package/node_modules/pi-multi-account/cursor/index.ts +572 -0
- package/node_modules/pi-multi-account/cursor/message-parsing.ts +323 -0
- package/node_modules/pi-multi-account/cursor/prompt-usage.ts +53 -0
- package/node_modules/pi-multi-account/cursor/proto/agent_pb.ts +15294 -0
- package/node_modules/pi-multi-account/cursor/proxy.ts +2510 -0
- package/node_modules/pi-multi-account/cursor/session-lifecycle.ts +40 -0
- package/node_modules/pi-multi-account/cursor/sse-keepalive.ts +24 -0
- package/node_modules/pi-multi-account/cursor/stream-lifecycle.ts +193 -0
- package/node_modules/pi-multi-account/cursor/upstream-watchdog.ts +88 -0
- package/node_modules/pi-multi-account/cursor-bridge.ts +240 -0
- package/node_modules/pi-multi-account/cursor-model-name.ts +12 -0
- package/node_modules/pi-multi-account/index.ts +11825 -0
- package/node_modules/pi-multi-account/model-catalog.ts +354 -0
- package/node_modules/pi-multi-account/package.json +101 -0
- package/node_modules/pi-multi-account/pi-contract.ts +281 -0
- package/node_modules/pi-multi-account/provider-payload-stream.ts +44 -0
- package/node_modules/pi-multi-account/provider-priority.ts +189 -0
- package/node_modules/pi-multi-account/slot-proxy-auth.ts +167 -0
- package/node_modules/pi-multi-account/slot-proxy.ts +344 -0
- package/node_modules/pi-multi-account/state-file-transaction.ts +67 -0
- package/node_modules/pi-multi-account/usage.ts +1099 -0
- package/node_modules/pi-typesafe/README.md +6 -2
- package/node_modules/pi-typesafe/dist/client.d.ts +11 -0
- package/node_modules/pi-typesafe/dist/client.js +45 -10
- package/node_modules/pi-typesafe/dist/index.d.ts +2 -2
- package/node_modules/pi-typesafe/dist/index.js +1 -1
- package/node_modules/pi-typesafe/package.json +2 -2
- package/node_modules/pi-web-access/CHANGELOG.md +36 -0
- package/node_modules/pi-web-access/README.md +75 -18
- package/node_modules/pi-web-access/anysearch.ts +4 -15
- package/node_modules/pi-web-access/bocha.ts +3 -22
- package/node_modules/pi-web-access/brave.ts +3 -21
- package/node_modules/pi-web-access/brightdata.ts +5 -32
- package/node_modules/pi-web-access/content-find.ts +168 -53
- package/node_modules/pi-web-access/curator-page.ts +4 -1
- package/node_modules/pi-web-access/curator-run.ts +2 -1
- package/node_modules/pi-web-access/curator-server.ts +1 -0
- package/node_modules/pi-web-access/dist/index.js +24620 -0
- package/node_modules/pi-web-access/domain-filter-normalization.ts +14 -0
- package/node_modules/pi-web-access/duckduckgo.ts +3 -21
- package/node_modules/pi-web-access/extract.ts +3 -1
- package/node_modules/pi-web-access/firecrawl.ts +5 -29
- package/node_modules/pi-web-access/gemini-search.ts +81 -32
- package/node_modules/pi-web-access/index.ts +149 -148
- package/node_modules/pi-web-access/jina-search.ts +4 -15
- package/node_modules/pi-web-access/kagi.ts +4 -13
- package/node_modules/pi-web-access/kimi-search.ts +5 -30
- package/node_modules/pi-web-access/mistral-search.ts +1 -15
- package/node_modules/pi-web-access/ollama.ts +2 -7
- package/node_modules/pi-web-access/openai-search.ts +174 -36
- package/node_modules/pi-web-access/opencode-session-headers.ts +24 -0
- package/node_modules/pi-web-access/package.json +10 -4
- package/node_modules/pi-web-access/page-query.ts +10 -2
- package/node_modules/pi-web-access/parallel.ts +1 -15
- package/node_modules/pi-web-access/pdf-extract.ts +3 -0
- package/node_modules/pi-web-access/querit.ts +5 -29
- package/node_modules/pi-web-access/search-answer-formatting.ts +11 -0
- package/node_modules/pi-web-access/search-result-count-normalization.ts +4 -0
- package/node_modules/pi-web-access/search1api.ts +5 -29
- package/node_modules/pi-web-access/searchinfinity.ts +5 -29
- package/node_modules/pi-web-access/searxng.ts +3 -21
- package/node_modules/pi-web-access/serpapi.ts +5 -28
- package/node_modules/pi-web-access/serpbase.ts +3 -22
- package/node_modules/pi-web-access/serpdive.ts +3 -21
- package/node_modules/pi-web-access/serper.ts +5 -28
- package/node_modules/pi-web-access/serply.ts +197 -0
- package/node_modules/pi-web-access/source-check.ts +11 -47
- package/node_modules/pi-web-access/summary-review.ts +7 -3
- package/node_modules/pi-web-access/tavily.ts +3 -21
- package/node_modules/pi-web-access/tinyfish.ts +5 -29
- package/node_modules/pi-web-access/utils.ts +9 -1
- package/node_modules/pi-web-access/valyu.ts +5 -28
- package/node_modules/pi-web-access/xai-search.ts +1 -15
- package/node_modules/pi-web-access/xcrawl.ts +5 -32
- package/package.json +23 -16
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turning Pi's OpenAI-shaped message list into the turn structure Cursor wants.
|
|
3
|
+
*
|
|
4
|
+
* This is the module that matters whenever we cannot resume Cursor's own server-side
|
|
5
|
+
* conversation and have to rebuild it from Pi's transcript — which happens on a restored
|
|
6
|
+
* session, on a fresh process, and (since compaction stopped being a no-op) after every
|
|
7
|
+
* compaction. Everything the model will know about the work so far has to survive this
|
|
8
|
+
* function; whatever it drops is gone.
|
|
9
|
+
*
|
|
10
|
+
* It lives apart from proxy.ts because proxy.ts imports generated protobuf code with
|
|
11
|
+
* non-erasable `enum`s, which Node's type stripping refuses to load — so nothing in proxy.ts
|
|
12
|
+
* can be unit tested. This file has no such dependency and is tested directly.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
export interface ContentPart {
|
|
16
|
+
type: string;
|
|
17
|
+
text?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface OpenAIToolCall {
|
|
21
|
+
id: string;
|
|
22
|
+
type: "function";
|
|
23
|
+
function: { name: string; arguments: string };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface OpenAIMessage {
|
|
27
|
+
role: "system" | "user" | "assistant" | "tool";
|
|
28
|
+
content: string | null | ContentPart[];
|
|
29
|
+
tool_call_id?: string;
|
|
30
|
+
tool_calls?: OpenAIToolCall[];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface ParsedToolResult {
|
|
34
|
+
content: string;
|
|
35
|
+
isError: boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface ParsedAssistantTextStep {
|
|
39
|
+
kind: "assistantText";
|
|
40
|
+
text: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface ParsedToolCallStep {
|
|
44
|
+
kind: "toolCall";
|
|
45
|
+
toolCallId: string;
|
|
46
|
+
toolName: string;
|
|
47
|
+
arguments: Record<string, unknown>;
|
|
48
|
+
result?: ParsedToolResult;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export type ParsedTurnStep = ParsedAssistantTextStep | ParsedToolCallStep;
|
|
52
|
+
|
|
53
|
+
export interface ParsedTurn {
|
|
54
|
+
userText: string;
|
|
55
|
+
steps: ParsedTurnStep[];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface ToolResultInfo {
|
|
59
|
+
toolCallId: string;
|
|
60
|
+
content: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface ParsedMessages {
|
|
64
|
+
systemPrompt: string;
|
|
65
|
+
userText: string;
|
|
66
|
+
/** Completed turns, ready to be replayed as conversation history. */
|
|
67
|
+
turns: ParsedTurn[];
|
|
68
|
+
toolResults: ToolResultInfo[];
|
|
69
|
+
/**
|
|
70
|
+
* The turn that is still in flight: the user asked something, the assistant called tools,
|
|
71
|
+
* and their results have just come back. On the live path those results are fed straight
|
|
72
|
+
* into the open stream, so this is unused. On the rebuild path it is the only record that
|
|
73
|
+
* the work happened at all — without it the model is handed the original question again
|
|
74
|
+
* with no idea it already ran the tools, or a naked blob of tool output with no question
|
|
75
|
+
* attached to it.
|
|
76
|
+
*/
|
|
77
|
+
pendingTurn?: ParsedTurn;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function textContent(content: OpenAIMessage["content"]): string {
|
|
81
|
+
if (content == null) return "";
|
|
82
|
+
if (typeof content === "string") return content;
|
|
83
|
+
return content.filter((p) => p.type === "text" && p.text).map((p) => p.text!).join("\n");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function parseToolCallArguments(raw: string): Record<string, unknown> {
|
|
87
|
+
try {
|
|
88
|
+
const parsed = JSON.parse(raw);
|
|
89
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
90
|
+
return parsed as Record<string, unknown>;
|
|
91
|
+
}
|
|
92
|
+
return { value: parsed };
|
|
93
|
+
} catch {
|
|
94
|
+
return raw ? { __raw: raw } : {};
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function isToolCallStep(step: ParsedTurnStep): step is ParsedToolCallStep {
|
|
99
|
+
return step.kind === "toolCall";
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function stripTurnRuntimeState(turn: ParsedTurn & {
|
|
103
|
+
toolCallById?: Map<string, ParsedToolCallStep>;
|
|
104
|
+
sawToolResult?: boolean;
|
|
105
|
+
sawAssistantAfterToolResult?: boolean;
|
|
106
|
+
}): ParsedTurn {
|
|
107
|
+
return { userText: turn.userText, steps: turn.steps };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function parseMessages(
|
|
111
|
+
messages: OpenAIMessage[],
|
|
112
|
+
debug?: (event: string, data: Record<string, unknown>) => void,
|
|
113
|
+
): ParsedMessages {
|
|
114
|
+
let systemPrompt = "You are a helpful assistant.";
|
|
115
|
+
const turns: ParsedTurn[] = [];
|
|
116
|
+
|
|
117
|
+
debug?.("parse_messages.start", { messages });
|
|
118
|
+
|
|
119
|
+
const systemParts = messages.filter((m) => m.role === "system").map((m) => textContent(m.content));
|
|
120
|
+
if (systemParts.length > 0) systemPrompt = systemParts.join("\n");
|
|
121
|
+
|
|
122
|
+
const nonSystem = messages.filter((m) => m.role !== "system");
|
|
123
|
+
let currentTurn: (ParsedTurn & {
|
|
124
|
+
toolCallById: Map<string, ParsedToolCallStep>;
|
|
125
|
+
sawToolResult: boolean;
|
|
126
|
+
sawAssistantAfterToolResult: boolean;
|
|
127
|
+
}) | null = null;
|
|
128
|
+
|
|
129
|
+
const finalizeCurrentTurn = () => {
|
|
130
|
+
if (!currentTurn) return;
|
|
131
|
+
turns.push(stripTurnRuntimeState(currentTurn));
|
|
132
|
+
currentTurn = null;
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
for (const msg of nonSystem) {
|
|
136
|
+
if (msg.role === "user") {
|
|
137
|
+
finalizeCurrentTurn();
|
|
138
|
+
currentTurn = {
|
|
139
|
+
userText: textContent(msg.content),
|
|
140
|
+
steps: [],
|
|
141
|
+
toolCallById: new Map(),
|
|
142
|
+
sawToolResult: false,
|
|
143
|
+
sawAssistantAfterToolResult: false,
|
|
144
|
+
};
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (!currentTurn) continue;
|
|
149
|
+
|
|
150
|
+
if (msg.role === "assistant") {
|
|
151
|
+
const text = textContent(msg.content);
|
|
152
|
+
if (text) {
|
|
153
|
+
if (currentTurn.sawToolResult) currentTurn.sawAssistantAfterToolResult = true;
|
|
154
|
+
currentTurn.steps.push({ kind: "assistantText", text });
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
for (const toolCall of msg.tool_calls ?? []) {
|
|
158
|
+
const step: ParsedToolCallStep = {
|
|
159
|
+
kind: "toolCall",
|
|
160
|
+
toolCallId: toolCall.id,
|
|
161
|
+
toolName: toolCall.function.name,
|
|
162
|
+
arguments: parseToolCallArguments(toolCall.function.arguments),
|
|
163
|
+
};
|
|
164
|
+
currentTurn.steps.push(step);
|
|
165
|
+
currentTurn.toolCallById.set(step.toolCallId, step);
|
|
166
|
+
}
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (msg.role === "tool") {
|
|
171
|
+
const toolCallId = msg.tool_call_id ?? "";
|
|
172
|
+
const content = textContent(msg.content);
|
|
173
|
+
const existing = toolCallId ? currentTurn.toolCallById.get(toolCallId) : undefined;
|
|
174
|
+
if (existing) {
|
|
175
|
+
existing.result = { content, isError: false };
|
|
176
|
+
} else {
|
|
177
|
+
const step: ParsedToolCallStep = {
|
|
178
|
+
kind: "toolCall",
|
|
179
|
+
toolCallId,
|
|
180
|
+
toolName: "",
|
|
181
|
+
arguments: {},
|
|
182
|
+
result: { content, isError: false },
|
|
183
|
+
};
|
|
184
|
+
currentTurn.steps.push(step);
|
|
185
|
+
if (toolCallId) currentTurn.toolCallById.set(toolCallId, step);
|
|
186
|
+
}
|
|
187
|
+
currentTurn.sawToolResult = true;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
let userText = "";
|
|
192
|
+
let toolResults: ToolResultInfo[] = [];
|
|
193
|
+
let pendingTurn: ParsedTurn | undefined;
|
|
194
|
+
|
|
195
|
+
if (currentTurn) {
|
|
196
|
+
const toolCallSteps = currentTurn.steps.filter(isToolCallStep);
|
|
197
|
+
const hasAnyToolResults = toolCallSteps.some((step) => step.result);
|
|
198
|
+
const lastStep = currentTurn.steps.at(-1);
|
|
199
|
+
const isToolContinuation = lastStep?.kind === "toolCall";
|
|
200
|
+
|
|
201
|
+
if (currentTurn.steps.length === 0 || isToolContinuation) {
|
|
202
|
+
userText = currentTurn.userText;
|
|
203
|
+
if (hasAnyToolResults) {
|
|
204
|
+
toolResults = toolCallSteps
|
|
205
|
+
.filter((step) => step.result)
|
|
206
|
+
.map((step) => ({ toolCallId: step.toolCallId, content: step.result!.content }));
|
|
207
|
+
}
|
|
208
|
+
// Keep the whole in-flight turn, not just its results. The live path ignores this;
|
|
209
|
+
// the rebuild path cannot reconstruct the session without it.
|
|
210
|
+
if (currentTurn.steps.length > 0) {
|
|
211
|
+
pendingTurn = stripTurnRuntimeState(currentTurn);
|
|
212
|
+
}
|
|
213
|
+
} else {
|
|
214
|
+
turns.push(stripTurnRuntimeState(currentTurn));
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const parsed: ParsedMessages = { systemPrompt, userText, turns, toolResults, pendingTurn };
|
|
219
|
+
debug?.("parse_messages.end", parsed as unknown as Record<string, unknown>);
|
|
220
|
+
return parsed;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Rebuilding a conversation Cursor has no memory of.
|
|
225
|
+
*
|
|
226
|
+
* There are three ways to put words in front of the model, and they are not equally reliable:
|
|
227
|
+
*
|
|
228
|
+
* - the request's own user message travels inline in the request, but Cursor stops resuming
|
|
229
|
+
* tool calls when that action grows past roughly 50–60 KiB;
|
|
230
|
+
* - the root system prompt travels through a blob Cursor reliably fetches before the turn;
|
|
231
|
+
* - history turns travel as sha256 blob IDs. The words themselves stay on our side, and
|
|
232
|
+
* Cursor has to come back over the KV channel and ask for each blob. If it does not ask,
|
|
233
|
+
* or asks for something we do not hold, nothing surfaces and nothing complains: a missing
|
|
234
|
+
* blob is answered with an empty result.
|
|
235
|
+
*
|
|
236
|
+
* Replaying the restored session as history therefore put the compaction summary somewhere the
|
|
237
|
+
* model could not read it — present in Pi's chat, invisible to Cursor. Rendering all of it into
|
|
238
|
+
* the action fixed that omission but created a deterministic tool-resume stall on real sessions.
|
|
239
|
+
* The rebuild now keeps the action short and renders prior turns into the fetched system blob.
|
|
240
|
+
*/
|
|
241
|
+
|
|
242
|
+
const CONTEXT_OPEN = "=== RESTORED SESSION CONTEXT ===";
|
|
243
|
+
const CONTEXT_CLOSE = "=== END RESTORED SESSION CONTEXT ===";
|
|
244
|
+
|
|
245
|
+
function renderStep(step: ParsedTurnStep): string {
|
|
246
|
+
if (step.kind === "assistantText") return `Assistant: ${step.text}`;
|
|
247
|
+
const args = JSON.stringify(step.arguments ?? {});
|
|
248
|
+
const call = `Assistant called ${step.toolName || "tool"}(${args})`;
|
|
249
|
+
if (!step.result) return `${call}\n -> (no result recorded)`;
|
|
250
|
+
const label = step.result.isError ? "error" : "result";
|
|
251
|
+
return `${call}\n -> ${label}: ${step.result.content}`;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/** A faithful plain-text transcript of everything that already happened. */
|
|
255
|
+
export function renderTurnsAsText(turns: ParsedTurn[]): string {
|
|
256
|
+
const blocks: string[] = [];
|
|
257
|
+
for (const turn of turns) {
|
|
258
|
+
const lines: string[] = [];
|
|
259
|
+
if (turn.userText.trim()) lines.push(`User: ${turn.userText}`);
|
|
260
|
+
for (const step of turn.steps) lines.push(renderStep(step));
|
|
261
|
+
if (lines.length > 0) blocks.push(lines.join("\n"));
|
|
262
|
+
}
|
|
263
|
+
return blocks.join("\n\n");
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/** The turns to replay as Cursor conversation history. Empty on the rebuild path — see above. */
|
|
267
|
+
export function historyForRebuild(_parsed: Pick<ParsedMessages, "turns" | "pendingTurn">): ParsedTurn[] {
|
|
268
|
+
return [];
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/** Every turn that already happened, in order, including the one still in flight. */
|
|
272
|
+
export function allPriorTurns(parsed: Pick<ParsedMessages, "turns" | "pendingTurn">): ParsedTurn[] {
|
|
273
|
+
return parsed.pendingTurn ? [...parsed.turns, parsed.pendingTurn] : [...parsed.turns];
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const CONTINUATION_INSTRUCTION =
|
|
277
|
+
"Continue the work above from where it stopped. Do not repeat steps that are already done, "
|
|
278
|
+
+ "and do not ask the user to repeat themselves — everything you need is in the context above.";
|
|
279
|
+
|
|
280
|
+
export const CONTINUATION_PROMPT = CONTINUATION_INSTRUCTION;
|
|
281
|
+
|
|
282
|
+
/** Put restored history in Cursor's fetched root prompt, never in its size-sensitive action. */
|
|
283
|
+
export function systemPromptForRebuild(
|
|
284
|
+
systemPrompt: string,
|
|
285
|
+
parsed: Pick<ParsedMessages, "turns" | "pendingTurn">,
|
|
286
|
+
options: { hasCheckpoint: boolean },
|
|
287
|
+
): string {
|
|
288
|
+
if (options.hasCheckpoint) return systemPrompt;
|
|
289
|
+
const transcript = renderTurnsAsText(allPriorTurns(parsed));
|
|
290
|
+
if (!transcript) return systemPrompt;
|
|
291
|
+
return `${systemPrompt}\n\n${CONTEXT_OPEN}\n${transcript}\n${CONTEXT_CLOSE}`;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* What to send as the request's user message.
|
|
296
|
+
*
|
|
297
|
+
* Rebuild path: restored history is in the system blob, so this stays a normal short question
|
|
298
|
+
* or continuation instruction. Checkpoint path: Cursor already holds the history, but its
|
|
299
|
+
* checkpoint has the assistant's tool calls without their results (the stream died before
|
|
300
|
+
* delivery), so the results travel here instead.
|
|
301
|
+
*/
|
|
302
|
+
export function requestActionText(
|
|
303
|
+
parsed: Pick<ParsedMessages, "userText" | "toolResults" | "turns" | "pendingTurn">,
|
|
304
|
+
options: { hasCheckpoint: boolean },
|
|
305
|
+
): string {
|
|
306
|
+
if (options.hasCheckpoint) {
|
|
307
|
+
if (!parsed.pendingTurn) return parsed.userText;
|
|
308
|
+
const results = parsed.toolResults.map((r) => r.content).join("\n").trim();
|
|
309
|
+
return results ? `${CONTINUATION_INSTRUCTION}\n\n${results}` : CONTINUATION_INSTRUCTION;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const prior = allPriorTurns(parsed);
|
|
313
|
+
const transcript = renderTurnsAsText(prior);
|
|
314
|
+
if (!transcript) return parsed.userText;
|
|
315
|
+
return parsed.pendingTurn ? CONTINUATION_INSTRUCTION : parsed.userText;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/** @deprecated Use {@link requestActionText}. */
|
|
319
|
+
export function actionTextForRebuild(
|
|
320
|
+
parsed: Pick<ParsedMessages, "userText" | "toolResults" | "turns" | "pendingTurn">,
|
|
321
|
+
): string {
|
|
322
|
+
return requestActionText(parsed, { hasCheckpoint: false });
|
|
323
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cursor often omits tokenDetails on tool-call turns. Estimate prompt size from
|
|
3
|
+
* the OpenAI-shaped request so Pi's auto-compact / footer are not stuck at 0.
|
|
4
|
+
*/
|
|
5
|
+
export function estimatePromptTokens(messages: unknown, tools?: unknown): number {
|
|
6
|
+
const parts: string[] = [];
|
|
7
|
+
try {
|
|
8
|
+
if (messages !== undefined) parts.push(JSON.stringify(messages));
|
|
9
|
+
} catch {
|
|
10
|
+
/* ignore unserializable messages */
|
|
11
|
+
}
|
|
12
|
+
try {
|
|
13
|
+
if (tools !== undefined) parts.push(JSON.stringify(tools));
|
|
14
|
+
} catch {
|
|
15
|
+
/* ignore unserializable tools */
|
|
16
|
+
}
|
|
17
|
+
return Math.max(1, Math.ceil(parts.join("").length / 4));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* What Pi needs here is "how big was the request I just sent", because that is what it compares
|
|
22
|
+
* against the model's context window. Cursor's `usedTokens` answers a different question: how
|
|
23
|
+
* big is the conversation Cursor is holding on its side. The two agree until Pi compacts — after
|
|
24
|
+
* that Pi's request is small while Cursor's number stays at the old size, and Pi reads its own
|
|
25
|
+
* context as being over the window on every single turn.
|
|
26
|
+
*
|
|
27
|
+
* Real numbers from a session, one reply after a completed compaction:
|
|
28
|
+
* request ≈ 53 000 tokens, Cursor answered 208 632, Pi compacted again three minutes later and
|
|
29
|
+
* lost its summary doing it.
|
|
30
|
+
*
|
|
31
|
+
* So the measured request wins, and Cursor's counter is only a fallback for when we could not
|
|
32
|
+
* measure anything at all.
|
|
33
|
+
*/
|
|
34
|
+
export function resolveCursorUsage(state: {
|
|
35
|
+
outputTokens: number;
|
|
36
|
+
totalTokens: number;
|
|
37
|
+
promptTokenEstimate?: number;
|
|
38
|
+
}): { prompt_tokens: number; completion_tokens: number; total_tokens: number } {
|
|
39
|
+
const completion_tokens = Math.max(0, state.outputTokens);
|
|
40
|
+
const measured = Math.max(0, state.promptTokenEstimate ?? 0);
|
|
41
|
+
if (measured > 0) {
|
|
42
|
+
return {
|
|
43
|
+
prompt_tokens: measured,
|
|
44
|
+
completion_tokens,
|
|
45
|
+
total_tokens: measured + completion_tokens,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
if (state.totalTokens > 0) {
|
|
49
|
+
const prompt_tokens = Math.max(0, state.totalTokens - completion_tokens);
|
|
50
|
+
return { prompt_tokens, completion_tokens, total_tokens: state.totalTokens };
|
|
51
|
+
}
|
|
52
|
+
return { prompt_tokens: 0, completion_tokens, total_tokens: completion_tokens };
|
|
53
|
+
}
|