@psnext/slingcli 2.5.20260623-1 → 2.5.20260629-1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/compaction.js +5 -2
  2. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/session.js +2 -1
  3. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/shell-output.js +1 -1
  4. package/node_modules/@earendil-works/pi-agent-core/package.json +2 -2
  5. package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.js +51 -47
  6. package/node_modules/@earendil-works/pi-ai/dist/api/azure-openai-responses.js +9 -3
  7. package/node_modules/@earendil-works/pi-ai/dist/api/bedrock-converse-stream.js +9 -6
  8. package/node_modules/@earendil-works/pi-ai/dist/api/cloudflare.js +0 -18
  9. package/node_modules/@earendil-works/pi-ai/dist/api/google-generative-ai.js +6 -3
  10. package/node_modules/@earendil-works/pi-ai/dist/api/google-vertex.js +6 -3
  11. package/node_modules/@earendil-works/pi-ai/dist/api/mistral-conversations.js +1 -1
  12. package/node_modules/@earendil-works/pi-ai/dist/api/openai-codex-responses.js +15 -25
  13. package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.js +30 -26
  14. package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses-shared.js +189 -204
  15. package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses.js +24 -21
  16. package/node_modules/@earendil-works/pi-ai/dist/api/openrouter-images.js +2 -5
  17. package/node_modules/@earendil-works/pi-ai/dist/api/simple-options.js +11 -2
  18. package/node_modules/@earendil-works/pi-ai/dist/auth/helpers.js +2 -2
  19. package/node_modules/@earendil-works/pi-ai/dist/auth/resolve.js +19 -4
  20. package/node_modules/@earendil-works/pi-ai/dist/compat.js +81 -7
  21. package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.js +45 -0
  22. package/node_modules/@earendil-works/pi-ai/dist/images-models.js +7 -3
  23. package/node_modules/@earendil-works/pi-ai/dist/index.js +1 -0
  24. package/node_modules/@earendil-works/pi-ai/dist/legacy-api-aliases.js +49 -0
  25. package/node_modules/@earendil-works/pi-ai/dist/models.js +7 -3
  26. package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.models.js +25 -8
  27. package/node_modules/@earendil-works/pi-ai/dist/providers/ant-ling.models.js +3 -3
  28. package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.models.js +0 -34
  29. package/node_modules/@earendil-works/pi-ai/dist/providers/cerebras.models.js +2 -0
  30. package/node_modules/@earendil-works/pi-ai/dist/providers/cloudflare-ai-gateway.js +2 -2
  31. package/node_modules/@earendil-works/pi-ai/dist/providers/cloudflare-ai-gateway.models.js +21 -9
  32. package/node_modules/@earendil-works/pi-ai/dist/providers/cloudflare-auth.js +85 -0
  33. package/node_modules/@earendil-works/pi-ai/dist/providers/cloudflare-workers-ai.js +2 -2
  34. package/node_modules/@earendil-works/pi-ai/dist/providers/cloudflare-workers-ai.models.js +13 -13
  35. package/node_modules/@earendil-works/pi-ai/dist/providers/deepseek.models.js +2 -2
  36. package/node_modules/@earendil-works/pi-ai/dist/providers/faux.js +1 -20
  37. package/node_modules/@earendil-works/pi-ai/dist/providers/fireworks.models.js +19 -1
  38. package/node_modules/@earendil-works/pi-ai/dist/providers/huggingface.models.js +72 -0
  39. package/node_modules/@earendil-works/pi-ai/dist/providers/minimax-cn.models.js +4 -4
  40. package/node_modules/@earendil-works/pi-ai/dist/providers/minimax.models.js +4 -4
  41. package/node_modules/@earendil-works/pi-ai/dist/providers/nvidia.models.js +19 -0
  42. package/node_modules/@earendil-works/pi-ai/dist/providers/opencode-go.models.js +11 -11
  43. package/node_modules/@earendil-works/pi-ai/dist/providers/opencode.models.js +15 -15
  44. package/node_modules/@earendil-works/pi-ai/dist/providers/openrouter.models.js +310 -39
  45. package/node_modules/@earendil-works/pi-ai/dist/providers/together.models.js +37 -18
  46. package/node_modules/@earendil-works/pi-ai/dist/providers/vercel-ai-gateway.models.js +349 -77
  47. package/node_modules/@earendil-works/pi-ai/dist/providers/xai.models.js +7 -0
  48. package/node_modules/@earendil-works/pi-ai/dist/providers/zai-coding-cn.models.js +6 -6
  49. package/node_modules/@earendil-works/pi-ai/dist/providers/zai.models.js +6 -6
  50. package/node_modules/@earendil-works/pi-ai/dist/utils/estimate.js +95 -0
  51. package/node_modules/@earendil-works/pi-ai/dist/utils/headers.js +10 -0
  52. package/node_modules/@earendil-works/pi-ai/dist/utils/oauth/openai-codex.js +2 -2
  53. package/node_modules/@earendil-works/pi-ai/dist/utils/retry.js +86 -0
  54. package/node_modules/@earendil-works/pi-ai/package.json +1 -33
  55. package/node_modules/@earendil-works/pi-coding-agent/dist/cli/file-processor.js +13 -25
  56. package/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session.js +15 -30
  57. package/node_modules/@earendil-works/pi-coding-agent/dist/core/compaction/compaction.js +6 -3
  58. package/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/loader.js +5 -1
  59. package/node_modules/@earendil-works/pi-coding-agent/dist/core/model-registry.js +2 -4
  60. package/node_modules/@earendil-works/pi-coding-agent/dist/core/model-resolver.js +1 -1
  61. package/node_modules/@earendil-works/pi-coding-agent/dist/core/resource-loader.js +2 -0
  62. package/node_modules/@earendil-works/pi-coding-agent/dist/core/session-manager.js +9 -5
  63. package/node_modules/@earendil-works/pi-coding-agent/dist/core/settings-manager.js +11 -0
  64. package/node_modules/@earendil-works/pi-coding-agent/dist/core/timings.js +24 -14
  65. package/node_modules/@earendil-works/pi-coding-agent/dist/core/tools/read.js +12 -25
  66. package/node_modules/@earendil-works/pi-coding-agent/dist/main.js +20 -4
  67. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/assistant-message.js +9 -9
  68. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/extension-editor.js +12 -3
  69. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/session-selector.js +14 -3
  70. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/user-message.js +1 -1
  71. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/interactive-mode.js +37 -25
  72. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/rpc/rpc-client.js +14 -0
  73. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/rpc/rpc-mode.js +16 -0
  74. package/node_modules/@earendil-works/pi-coding-agent/dist/rpc-entry.js +10 -0
  75. package/node_modules/@earendil-works/pi-coding-agent/dist/utils/image-convert.js +21 -15
  76. package/node_modules/@earendil-works/pi-coding-agent/dist/utils/image-process.js +83 -0
  77. package/node_modules/@earendil-works/pi-coding-agent/dist/utils/mime.js +41 -0
  78. package/node_modules/@earendil-works/pi-coding-agent/npm-shrinkwrap.json +12 -12
  79. package/node_modules/@earendil-works/pi-coding-agent/package.json +8 -5
  80. package/node_modules/@earendil-works/pi-orchestrator/dist/cli.js +136 -0
  81. package/node_modules/@earendil-works/pi-orchestrator/dist/config.js +55 -0
  82. package/node_modules/@earendil-works/pi-orchestrator/dist/handler.js +105 -0
  83. package/node_modules/@earendil-works/pi-orchestrator/dist/index.js +11 -0
  84. package/node_modules/@earendil-works/pi-orchestrator/dist/ipc/client.js +56 -0
  85. package/node_modules/@earendil-works/pi-orchestrator/dist/ipc/protocol.js +12 -0
  86. package/node_modules/@earendil-works/pi-orchestrator/dist/ipc/server.js +145 -0
  87. package/node_modules/@earendil-works/pi-orchestrator/dist/radius.js +365 -0
  88. package/node_modules/@earendil-works/pi-orchestrator/dist/rpc-process.js +168 -0
  89. package/node_modules/@earendil-works/pi-orchestrator/dist/serve.js +70 -0
  90. package/node_modules/@earendil-works/pi-orchestrator/dist/storage.js +58 -0
  91. package/node_modules/@earendil-works/pi-orchestrator/dist/supervisor.js +290 -0
  92. package/node_modules/@earendil-works/pi-orchestrator/dist/types.js +2 -0
  93. package/node_modules/@earendil-works/pi-orchestrator/package.json +45 -0
  94. package/node_modules/@earendil-works/pi-tui/dist/components/markdown.js +3 -0
  95. package/node_modules/@earendil-works/pi-tui/package.json +1 -1
  96. package/node_modules/@types/node/README.md +1 -1
  97. package/node_modules/@types/node/diagnostics_channel.d.ts +17 -20
  98. package/node_modules/@types/node/package.json +2 -2
  99. package/node_modules/brace-expansion/dist/commonjs/index.js +24 -14
  100. package/node_modules/brace-expansion/dist/commonjs/index.js.map +1 -1
  101. package/node_modules/brace-expansion/dist/esm/index.js +24 -14
  102. package/node_modules/brace-expansion/dist/esm/index.js.map +1 -1
  103. package/node_modules/brace-expansion/package.json +2 -2
  104. package/node_modules/google-auth-library/build/src/auth/googleauth.js +1 -1
  105. package/node_modules/google-auth-library/package.json +4 -4
  106. package/package.json +7 -6
  107. package/slingshot/index.js +302 -304
  108. package/node_modules/@earendil-works/pi-ai/dist/api-registry.js +0 -44
@@ -7,6 +7,7 @@ export const XAI_MODELS = {
7
7
  api: "openai-completions",
8
8
  provider: "xai",
9
9
  baseUrl: "https://api.x.ai/v1",
10
+ compat: { "supportsStore": false, "supportsDeveloperRole": false, "supportsReasoningEffort": false },
10
11
  reasoning: false,
11
12
  input: ["text"],
12
13
  cost: {
@@ -24,6 +25,7 @@ export const XAI_MODELS = {
24
25
  api: "openai-completions",
25
26
  provider: "xai",
26
27
  baseUrl: "https://api.x.ai/v1",
28
+ compat: { "supportsStore": false, "supportsDeveloperRole": false, "supportsReasoningEffort": false },
27
29
  reasoning: false,
28
30
  input: ["text"],
29
31
  cost: {
@@ -41,6 +43,7 @@ export const XAI_MODELS = {
41
43
  api: "openai-completions",
42
44
  provider: "xai",
43
45
  baseUrl: "https://api.x.ai/v1",
46
+ compat: { "supportsStore": false, "supportsDeveloperRole": false, "supportsReasoningEffort": false },
44
47
  reasoning: false,
45
48
  input: ["text", "image"],
46
49
  cost: {
@@ -58,6 +61,7 @@ export const XAI_MODELS = {
58
61
  api: "openai-completions",
59
62
  provider: "xai",
60
63
  baseUrl: "https://api.x.ai/v1",
64
+ compat: { "supportsStore": false, "supportsDeveloperRole": false, "supportsReasoningEffort": false },
61
65
  reasoning: true,
62
66
  input: ["text", "image"],
63
67
  cost: {
@@ -75,6 +79,7 @@ export const XAI_MODELS = {
75
79
  api: "openai-completions",
76
80
  provider: "xai",
77
81
  baseUrl: "https://api.x.ai/v1",
82
+ compat: { "supportsStore": false, "supportsDeveloperRole": false, "supportsReasoningEffort": false },
78
83
  reasoning: true,
79
84
  input: ["text", "image"],
80
85
  cost: {
@@ -92,6 +97,7 @@ export const XAI_MODELS = {
92
97
  api: "openai-completions",
93
98
  provider: "xai",
94
99
  baseUrl: "https://api.x.ai/v1",
100
+ compat: { "supportsStore": false, "supportsDeveloperRole": false, "supportsReasoningEffort": false },
95
101
  reasoning: true,
96
102
  input: ["text", "image"],
97
103
  cost: {
@@ -109,6 +115,7 @@ export const XAI_MODELS = {
109
115
  api: "openai-completions",
110
116
  provider: "xai",
111
117
  baseUrl: "https://api.x.ai/v1",
118
+ compat: { "supportsStore": false, "supportsDeveloperRole": false, "supportsReasoningEffort": false },
112
119
  reasoning: false,
113
120
  input: ["text"],
114
121
  cost: {
@@ -7,7 +7,7 @@ export const ZAI_CODING_CN_MODELS = {
7
7
  api: "openai-completions",
8
8
  provider: "zai-coding-cn",
9
9
  baseUrl: "https://open.bigmodel.cn/api/coding/paas/v4",
10
- compat: { "supportsDeveloperRole": false, "thinkingFormat": "zai" },
10
+ compat: { "supportsStore": false, "supportsDeveloperRole": false, "supportsReasoningEffort": false, "thinkingFormat": "zai" },
11
11
  reasoning: true,
12
12
  input: ["text"],
13
13
  cost: {
@@ -25,7 +25,7 @@ export const ZAI_CODING_CN_MODELS = {
25
25
  api: "openai-completions",
26
26
  provider: "zai-coding-cn",
27
27
  baseUrl: "https://open.bigmodel.cn/api/coding/paas/v4",
28
- compat: { "supportsDeveloperRole": false, "thinkingFormat": "zai", "zaiToolStream": true },
28
+ compat: { "supportsStore": false, "supportsDeveloperRole": false, "supportsReasoningEffort": false, "thinkingFormat": "zai", "zaiToolStream": true },
29
29
  reasoning: true,
30
30
  input: ["text"],
31
31
  cost: {
@@ -43,7 +43,7 @@ export const ZAI_CODING_CN_MODELS = {
43
43
  api: "openai-completions",
44
44
  provider: "zai-coding-cn",
45
45
  baseUrl: "https://open.bigmodel.cn/api/coding/paas/v4",
46
- compat: { "supportsDeveloperRole": false, "thinkingFormat": "zai", "zaiToolStream": true },
46
+ compat: { "supportsStore": false, "supportsDeveloperRole": false, "supportsReasoningEffort": false, "thinkingFormat": "zai", "zaiToolStream": true },
47
47
  reasoning: true,
48
48
  input: ["text"],
49
49
  cost: {
@@ -61,7 +61,7 @@ export const ZAI_CODING_CN_MODELS = {
61
61
  api: "openai-completions",
62
62
  provider: "zai-coding-cn",
63
63
  baseUrl: "https://open.bigmodel.cn/api/coding/paas/v4",
64
- compat: { "supportsDeveloperRole": false, "thinkingFormat": "zai", "zaiToolStream": true },
64
+ compat: { "supportsStore": false, "supportsDeveloperRole": false, "supportsReasoningEffort": false, "thinkingFormat": "zai", "zaiToolStream": true },
65
65
  reasoning: true,
66
66
  input: ["text"],
67
67
  cost: {
@@ -79,7 +79,7 @@ export const ZAI_CODING_CN_MODELS = {
79
79
  api: "openai-completions",
80
80
  provider: "zai-coding-cn",
81
81
  baseUrl: "https://open.bigmodel.cn/api/coding/paas/v4",
82
- compat: { "supportsDeveloperRole": false, "thinkingFormat": "zai", "supportsReasoningEffort": true, "zaiToolStream": true },
82
+ compat: { "supportsStore": false, "supportsDeveloperRole": false, "supportsReasoningEffort": true, "thinkingFormat": "zai", "zaiToolStream": true },
83
83
  reasoning: true,
84
84
  thinkingLevelMap: { "minimal": null, "low": "high", "medium": "high", "high": "high", "xhigh": "max" },
85
85
  input: ["text"],
@@ -98,7 +98,7 @@ export const ZAI_CODING_CN_MODELS = {
98
98
  api: "openai-completions",
99
99
  provider: "zai-coding-cn",
100
100
  baseUrl: "https://open.bigmodel.cn/api/coding/paas/v4",
101
- compat: { "supportsDeveloperRole": false, "thinkingFormat": "zai", "zaiToolStream": true },
101
+ compat: { "supportsStore": false, "supportsDeveloperRole": false, "supportsReasoningEffort": false, "thinkingFormat": "zai", "zaiToolStream": true },
102
102
  reasoning: true,
103
103
  input: ["text", "image"],
104
104
  cost: {
@@ -7,7 +7,7 @@ export const ZAI_MODELS = {
7
7
  api: "openai-completions",
8
8
  provider: "zai",
9
9
  baseUrl: "https://api.z.ai/api/coding/paas/v4",
10
- compat: { "supportsDeveloperRole": false, "thinkingFormat": "zai" },
10
+ compat: { "supportsStore": false, "supportsDeveloperRole": false, "supportsReasoningEffort": false, "thinkingFormat": "zai" },
11
11
  reasoning: true,
12
12
  input: ["text"],
13
13
  cost: {
@@ -25,7 +25,7 @@ export const ZAI_MODELS = {
25
25
  api: "openai-completions",
26
26
  provider: "zai",
27
27
  baseUrl: "https://api.z.ai/api/coding/paas/v4",
28
- compat: { "supportsDeveloperRole": false, "thinkingFormat": "zai", "zaiToolStream": true },
28
+ compat: { "supportsStore": false, "supportsDeveloperRole": false, "supportsReasoningEffort": false, "thinkingFormat": "zai", "zaiToolStream": true },
29
29
  reasoning: true,
30
30
  input: ["text"],
31
31
  cost: {
@@ -43,7 +43,7 @@ export const ZAI_MODELS = {
43
43
  api: "openai-completions",
44
44
  provider: "zai",
45
45
  baseUrl: "https://api.z.ai/api/coding/paas/v4",
46
- compat: { "supportsDeveloperRole": false, "thinkingFormat": "zai", "zaiToolStream": true },
46
+ compat: { "supportsStore": false, "supportsDeveloperRole": false, "supportsReasoningEffort": false, "thinkingFormat": "zai", "zaiToolStream": true },
47
47
  reasoning: true,
48
48
  input: ["text"],
49
49
  cost: {
@@ -61,7 +61,7 @@ export const ZAI_MODELS = {
61
61
  api: "openai-completions",
62
62
  provider: "zai",
63
63
  baseUrl: "https://api.z.ai/api/coding/paas/v4",
64
- compat: { "supportsDeveloperRole": false, "thinkingFormat": "zai", "zaiToolStream": true },
64
+ compat: { "supportsStore": false, "supportsDeveloperRole": false, "supportsReasoningEffort": false, "thinkingFormat": "zai", "zaiToolStream": true },
65
65
  reasoning: true,
66
66
  input: ["text"],
67
67
  cost: {
@@ -79,7 +79,7 @@ export const ZAI_MODELS = {
79
79
  api: "openai-completions",
80
80
  provider: "zai",
81
81
  baseUrl: "https://api.z.ai/api/coding/paas/v4",
82
- compat: { "supportsDeveloperRole": false, "thinkingFormat": "zai", "supportsReasoningEffort": true, "zaiToolStream": true },
82
+ compat: { "supportsStore": false, "supportsDeveloperRole": false, "supportsReasoningEffort": true, "thinkingFormat": "zai", "zaiToolStream": true },
83
83
  reasoning: true,
84
84
  thinkingLevelMap: { "minimal": null, "low": "high", "medium": "high", "high": "high", "xhigh": "max" },
85
85
  input: ["text"],
@@ -98,7 +98,7 @@ export const ZAI_MODELS = {
98
98
  api: "openai-completions",
99
99
  provider: "zai",
100
100
  baseUrl: "https://api.z.ai/api/coding/paas/v4",
101
- compat: { "supportsDeveloperRole": false, "thinkingFormat": "zai", "zaiToolStream": true },
101
+ compat: { "supportsStore": false, "supportsDeveloperRole": false, "supportsReasoningEffort": false, "thinkingFormat": "zai", "zaiToolStream": true },
102
102
  reasoning: true,
103
103
  input: ["text", "image"],
104
104
  cost: {
@@ -0,0 +1,95 @@
1
+ const CHARS_PER_TOKEN = 4;
2
+ const ESTIMATED_IMAGE_CHARS = 4800;
3
+ export function calculateContextTokens(usage) {
4
+ return usage.totalTokens || usage.input + usage.output + usage.cacheRead + usage.cacheWrite;
5
+ }
6
+ function safeJsonStringify(value) {
7
+ try {
8
+ return JSON.stringify(value) ?? "undefined";
9
+ }
10
+ catch {
11
+ return "[unserializable]";
12
+ }
13
+ }
14
+ function estimateTextAndImageContentChars(content) {
15
+ if (typeof content === "string")
16
+ return content.length;
17
+ let chars = 0;
18
+ for (const block of content)
19
+ chars += block.type === "text" ? block.text.length : ESTIMATED_IMAGE_CHARS;
20
+ return chars;
21
+ }
22
+ export function estimateTextTokens(text) {
23
+ return Math.ceil(text.length / CHARS_PER_TOKEN);
24
+ }
25
+ export function estimateTextAndImageContentTokens(content) {
26
+ return Math.ceil(estimateTextAndImageContentChars(content) / CHARS_PER_TOKEN);
27
+ }
28
+ export function estimateMessageTokens(message) {
29
+ let chars = 0;
30
+ if (message.role === "user")
31
+ return estimateTextAndImageContentTokens(message.content);
32
+ if (message.role === "toolResult")
33
+ return estimateTextAndImageContentTokens(message.content);
34
+ for (const block of message.content) {
35
+ if (block.type === "text") {
36
+ chars += block.text.length;
37
+ }
38
+ else if (block.type === "thinking") {
39
+ chars += block.thinking.length;
40
+ }
41
+ else {
42
+ chars += block.name.length + safeJsonStringify(block.arguments).length;
43
+ }
44
+ }
45
+ return Math.ceil(chars / CHARS_PER_TOKEN);
46
+ }
47
+ function getLastAssistantUsageInfo(messages) {
48
+ for (let i = messages.length - 1; i >= 0; i--) {
49
+ const message = messages[i];
50
+ if (message.role !== "assistant")
51
+ continue;
52
+ const assistant = message;
53
+ if (assistant.stopReason === "aborted" || assistant.stopReason === "error")
54
+ continue;
55
+ if (calculateContextTokens(assistant.usage) > 0)
56
+ return { usage: assistant.usage, index: i };
57
+ }
58
+ return undefined;
59
+ }
60
+ function estimateMessages(messages) {
61
+ const usageInfo = getLastAssistantUsageInfo(messages);
62
+ if (usageInfo) {
63
+ const usageTokens = calculateContextTokens(usageInfo.usage);
64
+ let trailingTokens = 0;
65
+ for (let i = usageInfo.index + 1; i < messages.length; i++) {
66
+ trailingTokens += estimateMessageTokens(messages[i]);
67
+ }
68
+ return { tokens: usageTokens + trailingTokens, usageTokens, trailingTokens, lastUsageIndex: usageInfo.index };
69
+ }
70
+ let tokens = 0;
71
+ for (const message of messages)
72
+ tokens += estimateMessageTokens(message);
73
+ return { tokens, usageTokens: 0, trailingTokens: tokens, lastUsageIndex: null };
74
+ }
75
+ function isMessageArray(value) {
76
+ return Array.isArray(value);
77
+ }
78
+ export function estimateContextTokens(context) {
79
+ if (isMessageArray(context))
80
+ return estimateMessages(context);
81
+ const estimate = estimateMessages(context.messages);
82
+ if (estimate.lastUsageIndex !== null)
83
+ return estimate;
84
+ let prefixTokens = context.systemPrompt ? estimateTextTokens(context.systemPrompt) : 0;
85
+ if (context.tools && context.tools.length > 0) {
86
+ prefixTokens += estimateTextTokens(safeJsonStringify(context.tools));
87
+ }
88
+ return {
89
+ tokens: estimate.tokens + prefixTokens,
90
+ usageTokens: estimate.usageTokens,
91
+ trailingTokens: estimate.trailingTokens + prefixTokens,
92
+ lastUsageIndex: estimate.lastUsageIndex,
93
+ };
94
+ }
95
+ //# sourceMappingURL=estimate.js.map
@@ -5,4 +5,14 @@ export function headersToRecord(headers) {
5
5
  }
6
6
  return result;
7
7
  }
8
+ export function providerHeadersToRecord(headers) {
9
+ if (!headers)
10
+ return undefined;
11
+ const result = {};
12
+ for (const [key, value] of Object.entries(headers)) {
13
+ if (value !== null)
14
+ result[key] = value;
15
+ }
16
+ return Object.keys(result).length > 0 ? result : undefined;
17
+ }
8
18
  //# sourceMappingURL=headers.js.map
@@ -323,9 +323,9 @@ function getAccountId(accessToken) {
323
323
  return typeof accountId === "string" && accountId.length > 0 ? accountId : null;
324
324
  }
325
325
  function credentialsFromToken(token) {
326
- const accountId = getAccountId(token.access) || "unknown";
326
+ const accountId = getAccountId(token.access);
327
327
  if (!accountId) {
328
- console.log("Failed to extract accountId from token");
328
+ throw new Error("Failed to extract accountId from token");
329
329
  }
330
330
  return {
331
331
  access: token.access,
@@ -0,0 +1,86 @@
1
+ function buildProviderErrorPattern(patterns) {
2
+ return new RegExp(patterns.join("|"), "i");
3
+ }
4
+ const NON_RETRYABLE_PROVIDER_LIMIT_ERROR_PATTERN = buildProviderErrorPattern([
5
+ // OpenCode Go/free-tier limits returned as 429 JSON error types by OpenCode's
6
+ // Zen API. These are subscription/account limits, not transient throttles.
7
+ "GoUsageLimitError",
8
+ "FreeUsageLimitError",
9
+ // OpenCode Go subscription-limit text asks users to enable available-balance
10
+ // usage after rolling/weekly/monthly limits are reached.
11
+ "Monthly usage limit reached",
12
+ "available balance",
13
+ // Generic quota/budget/billing exhaustion. `insufficient_quota` is OpenAI's
14
+ // quota/billing error code; the other strings cover common gateway wording.
15
+ "insufficient_quota",
16
+ "out of budget",
17
+ "quota exceeded",
18
+ "billing",
19
+ ]);
20
+ const RETRYABLE_PROVIDER_ERROR_PATTERN = buildProviderErrorPattern([
21
+ // Generic provider load, HTTP status, and server-side transient failures.
22
+ "overloaded",
23
+ "rate.?limit",
24
+ "too many requests",
25
+ "429",
26
+ "500",
27
+ "502",
28
+ "503",
29
+ "504",
30
+ "service.?unavailable",
31
+ "server.?error",
32
+ "internal.?error",
33
+ // Wrapper/provider text for transient upstream failures, including OpenRouter
34
+ // "Provider returned error" responses (#2264).
35
+ "provider.?returned.?error",
36
+ // Network, proxy, and fetch transport failures. This includes OpenAI Codex
37
+ // raw-fetch failures such as "upstream connect", "connection refused", and
38
+ // "reset before headers" (#733), plus OpenRouter connection drops (#3317).
39
+ "network.?error",
40
+ "connection.?error",
41
+ "connection.?refused",
42
+ "connection.?lost",
43
+ "other side closed",
44
+ "fetch failed",
45
+ "upstream.?connect",
46
+ "reset before headers",
47
+ "socket hang up",
48
+ "timed? out",
49
+ "timeout",
50
+ "terminated",
51
+ // WebSocket transports can report close/error text instead of HTTP/fetch text.
52
+ "websocket.?closed",
53
+ "websocket.?error",
54
+ // Premature stream endings from SDKs and transports. Anthropic can throw
55
+ // "stream ended without ..." and "Anthropic stream ended before message_stop"
56
+ // (#4433); Bedrock/Smithy can throw an HTTP/2 no-response error (#3594).
57
+ "ended without",
58
+ "stream ended before message_stop",
59
+ "http2 request did not get a response",
60
+ // Provider-requested retry delay cap failures should flow through the outer
61
+ // retry policy so callers can surface/abort the backoff (#1123).
62
+ "retry delay",
63
+ // Explicit retry guidance emitted mid-stream by OpenAI Responses and Bedrock
64
+ // stream exceptions (#6019).
65
+ "you can retry your request",
66
+ "try your request again",
67
+ "please retry your request",
68
+ ]);
69
+ /**
70
+ * Classifies whether a failed assistant message looks like a transient provider
71
+ * or transport error, so callers can decide if the last assistant turn should be
72
+ * restarted.
73
+ *
74
+ * This does not implement retry policy. Callers should first handle context
75
+ * overflow separately, then apply their own retry budget, backoff, and reporting
76
+ * before restarting the assistant turn.
77
+ */
78
+ export function isRetryableAssistantError(message) {
79
+ if (message.stopReason !== "error" || !message.errorMessage)
80
+ return false;
81
+ const errorMessage = message.errorMessage;
82
+ if (NON_RETRYABLE_PROVIDER_LIMIT_ERROR_PATTERN.test(errorMessage))
83
+ return false;
84
+ return RETRYABLE_PROVIDER_ERROR_PATTERN.test(errorMessage);
85
+ }
86
+ //# sourceMappingURL=retry.js.map
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@earendil-works/pi-ai",
3
- "version": "0.79.10",
3
+ "version": "0.80.2",
4
4
  "description": "Unified LLM API with automatic model discovery and provider configuration",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -27,38 +27,6 @@
27
27
  "types": "./dist/api/*.d.ts",
28
28
  "import": "./dist/api/*.js"
29
29
  },
30
- "./anthropic": {
31
- "types": "./dist/api/anthropic-messages.d.ts",
32
- "import": "./dist/api/anthropic-messages.js"
33
- },
34
- "./azure-openai-responses": {
35
- "types": "./dist/api/azure-openai-responses.d.ts",
36
- "import": "./dist/api/azure-openai-responses.js"
37
- },
38
- "./google": {
39
- "types": "./dist/api/google-generative-ai.d.ts",
40
- "import": "./dist/api/google-generative-ai.js"
41
- },
42
- "./google-vertex": {
43
- "types": "./dist/api/google-vertex.d.ts",
44
- "import": "./dist/api/google-vertex.js"
45
- },
46
- "./mistral": {
47
- "types": "./dist/api/mistral-conversations.d.ts",
48
- "import": "./dist/api/mistral-conversations.js"
49
- },
50
- "./openai-codex-responses": {
51
- "types": "./dist/api/openai-codex-responses.d.ts",
52
- "import": "./dist/api/openai-codex-responses.js"
53
- },
54
- "./openai-completions": {
55
- "types": "./dist/api/openai-completions.d.ts",
56
- "import": "./dist/api/openai-completions.js"
57
- },
58
- "./openai-responses": {
59
- "types": "./dist/api/openai-responses.d.ts",
60
- "import": "./dist/api/openai-responses.js"
61
- },
62
30
  "./oauth": {
63
31
  "types": "./dist/oauth.d.ts",
64
32
  "import": "./dist/oauth.js"
@@ -5,7 +5,7 @@ import { access, readFile, stat } from "node:fs/promises";
5
5
  import chalk from "chalk";
6
6
  import { resolve } from "path";
7
7
  import { resolveReadPath } from "../core/tools/path-utils.js";
8
- import { formatDimensionNote, resizeImage } from "../utils/image-resize.js";
8
+ import { processImage } from "../utils/image-process.js";
9
9
  import { detectSupportedImageMimeTypeFromFile } from "../utils/mime.js";
10
10
  /** Process @file arguments into text content and image attachments */
11
11
  export async function processFileArguments(fileArgs, options) {
@@ -33,32 +33,20 @@ export async function processFileArguments(fileArgs, options) {
33
33
  if (mimeType) {
34
34
  // Handle image file
35
35
  const content = await readFile(absolutePath);
36
- let attachment;
37
- let dimensionNote;
38
- if (autoResizeImages) {
39
- const resized = await resizeImage(content, mimeType);
40
- if (!resized) {
41
- text += `<file name="${absolutePath}">[Image omitted: could not be resized below the inline image size limit.]</file>\n`;
42
- continue;
43
- }
44
- dimensionNote = formatDimensionNote(resized);
45
- attachment = {
46
- type: "image",
47
- mimeType: resized.mimeType,
48
- data: resized.data,
49
- };
50
- }
51
- else {
52
- attachment = {
53
- type: "image",
54
- mimeType,
55
- data: content.toString("base64"),
56
- };
36
+ const processed = await processImage(content, mimeType, { autoResizeImages });
37
+ if (!processed.ok) {
38
+ text += `<file name="${absolutePath}">${processed.message}</file>\n`;
39
+ continue;
57
40
  }
41
+ const attachment = {
42
+ type: "image",
43
+ mimeType: processed.mimeType,
44
+ data: processed.data,
45
+ };
58
46
  images.push(attachment);
59
- // Add text reference to image with optional dimension note
60
- if (dimensionNote) {
61
- text += `<file name="${absolutePath}">${dimensionNote}</file>\n`;
47
+ // Add text reference to image with optional processing hints
48
+ if (processed.hints.length > 0) {
49
+ text += `<file name="${absolutePath}">${processed.hints.join("\n")}</file>\n`;
62
50
  }
63
51
  else {
64
52
  text += `<file name="${absolutePath}"></file>\n`;
@@ -14,7 +14,7 @@
14
14
  */
15
15
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
16
16
  import { basename, dirname } from "node:path";
17
- import { clampThinkingLevel, cleanupSessionResources, getSupportedThinkingLevels, isContextOverflow, modelsAreEqual, resetApiProviders, streamSimple, } from "@earendil-works/pi-ai/compat";
17
+ import { clampThinkingLevel, cleanupSessionResources, getSupportedThinkingLevels, isContextOverflow, isRetryableAssistantError, modelsAreEqual, resetApiProviders, streamSimple, } from "@earendil-works/pi-ai/compat";
18
18
  import { getThemeByName, theme } from "../modes/interactive/theme/theme.js";
19
19
  import { stripFrontmatter } from "../utils/frontmatter.js";
20
20
  import { resolvePath } from "../utils/paths.js";
@@ -769,18 +769,11 @@ export class AgentSession {
769
769
  }
770
770
  throw new Error(formatNoApiKeyFoundMessage(this.model.provider));
771
771
  }
772
- // Check if we need to compact before sending (catches aborted responses)
772
+ // Check if we need to compact before sending (catches aborted responses).
773
+ // The user's new prompt is sent below, so do not call agent.continue() here.
773
774
  const lastAssistant = this._findLastAssistantMessage();
774
- if (lastAssistant && (await this._checkCompaction(lastAssistant, false))) {
775
- try {
776
- await this.agent.continue();
777
- while (await this._handlePostAgentRun()) {
778
- await this.agent.continue();
779
- }
780
- }
781
- finally {
782
- this._flushPendingBashMessages();
783
- }
775
+ if (lastAssistant) {
776
+ await this._checkCompaction(lastAssistant, false);
784
777
  }
785
778
  // Build messages array (custom message if any, then user message)
786
779
  messages = [];
@@ -1459,10 +1452,12 @@ export class AgentSession {
1459
1452
  return await this._runAutoCompaction("overflow", willRetry);
1460
1453
  }
1461
1454
  // Case 2: Threshold - context is getting large
1462
- // For error messages (no usage data), estimate from last successful response.
1463
- // This ensures sessions that hit persistent API errors (e.g. 529) can still compact.
1455
+ // For error messages or all-zero usage messages, estimate from the last valid response.
1456
+ // This ensures sessions that hit persistent API errors (e.g. 529) or malformed zero-usage
1457
+ // responses can still compact and do not reset context accounting.
1464
1458
  let contextTokens;
1465
- if (assistantMessage.stopReason === "error") {
1459
+ const directContextTokens = assistantMessage.usage ? calculateContextTokens(assistantMessage.usage) : 0;
1460
+ if (assistantMessage.stopReason === "error" || directContextTokens === 0) {
1466
1461
  const messages = this.agent.state.messages;
1467
1462
  const estimate = estimateContextTokens(messages);
1468
1463
  if (estimate.lastUsageIndex === null)
@@ -1479,7 +1474,7 @@ export class AgentSession {
1479
1474
  contextTokens = estimate.tokens;
1480
1475
  }
1481
1476
  else {
1482
- contextTokens = calculateContextTokens(assistantMessage.usage);
1477
+ contextTokens = directContextTokens;
1483
1478
  }
1484
1479
  if (shouldCompact(contextTokens, contextWindow, settings)) {
1485
1480
  return await this._runAutoCompaction("threshold", false);
@@ -1968,25 +1963,15 @@ export class AgentSession {
1968
1963
  // =========================================================================
1969
1964
  // Auto-Retry
1970
1965
  // =========================================================================
1971
- _isNonRetryableProviderLimitError(errorMessage) {
1972
- return /GoUsageLimitError|FreeUsageLimitError|Monthly usage limit reached|available balance|insufficient_quota|out of budget|quota exceeded|billing/i.test(errorMessage);
1973
- }
1974
1966
  /**
1975
1967
  * Check if an error is retryable (overloaded, rate limit, server errors).
1976
1968
  * Context overflow errors are NOT retryable (handled by compaction instead).
1977
1969
  */
1978
1970
  _isRetryableError(message) {
1979
- if (message.stopReason !== "error" || !message.errorMessage)
1980
- return false;
1981
- // Context overflow is handled by compaction, not retry
1982
- const contextWindow = this.model?.contextWindow ?? 0;
1983
- if (isContextOverflow(message, contextWindow))
1984
- return false;
1985
- const err = message.errorMessage;
1986
- if (this._isNonRetryableProviderLimitError(err))
1971
+ // Context overflow is handled by compaction, not retry.
1972
+ if (isContextOverflow(message, this.model?.contextWindow ?? 0))
1987
1973
  return false;
1988
- // Match: overloaded_error, provider returned error, rate limit, 429, 500, 502, 503, 504, service unavailable, network/connection errors (including connection lost), WebSocket transport closes/errors, fetch failed, premature stream endings, HTTP/2 closed before response, terminated, retry delay exceeded
1989
- return /overloaded|provider.?returned.?error|rate.?limit|too many requests|429|500|502|503|504|service.?unavailable|server.?error|internal.?error|network.?error|connection.?error|connection.?refused|connection.?lost|websocket.?closed|websocket.?error|other side closed|fetch failed|upstream.?connect|reset before headers|socket hang up|ended without|stream ended before message_stop|http2 request did not get a response|timed? out|timeout|terminated|retry delay/i.test(err);
1974
+ return isRetryableAssistantError(message);
1990
1975
  }
1991
1976
  /**
1992
1977
  * Prepare a retryable error for continuation with exponential backoff.
@@ -2424,8 +2409,8 @@ export class AgentSession {
2424
2409
  const contextTokens = calculateContextTokens(assistant.usage);
2425
2410
  if (contextTokens > 0) {
2426
2411
  hasPostCompactionUsage = true;
2412
+ break;
2427
2413
  }
2428
- break;
2429
2414
  }
2430
2415
  }
2431
2416
  }
@@ -80,19 +80,22 @@ export function calculateContextTokens(usage) {
80
80
  }
81
81
  /**
82
82
  * Get usage from an assistant message if available.
83
- * Skips aborted and error messages as they don't have valid usage data.
83
+ * Skips aborted, error, and all-zero usage messages as they don't have valid usage data.
84
84
  */
85
85
  function getAssistantUsage(msg) {
86
86
  if (msg.role === "assistant" && "usage" in msg) {
87
87
  const assistantMsg = msg;
88
- if (assistantMsg.stopReason !== "aborted" && assistantMsg.stopReason !== "error" && assistantMsg.usage) {
88
+ if (assistantMsg.stopReason !== "aborted" &&
89
+ assistantMsg.stopReason !== "error" &&
90
+ assistantMsg.usage &&
91
+ calculateContextTokens(assistantMsg.usage) > 0) {
89
92
  return assistantMsg.usage;
90
93
  }
91
94
  }
92
95
  return undefined;
93
96
  }
94
97
  /**
95
- * Find the last non-aborted assistant message usage from session entries.
98
+ * Find the last valid assistant message usage from session entries.
96
99
  */
97
100
  export function getLastAssistantUsage(entries) {
98
101
  for (let i = entries.length - 1; i >= 0; i--) {
@@ -25,6 +25,7 @@ import { resolvePath } from "../../utils/paths.js";
25
25
  import { createEventBus } from "../event-bus.js";
26
26
  import { execCommand } from "../exec.js";
27
27
  import { createSyntheticSourceInfo } from "../source-info.js";
28
+ import { time } from "../timings.js";
28
29
  /** Modules available to extensions via virtualModules (for compiled Bun binary) */
29
30
  const VIRTUAL_MODULES = {
30
31
  typebox: _bundledTypebox,
@@ -153,7 +154,7 @@ export function createExtensionRuntime() {
153
154
  invalidate: (message) => {
154
155
  state.staleMessage ??=
155
156
  message ??
156
- "This extension ctx is stale after session replacement or reload. Do not use a captured sling or command ctx after ctx.newSession(), ctx.fork(), ctx.switchSession(), or ctx.reload(). For newSession, fork, and switchSession, move post-replacement work into withSession and use the ctx passed to withSession. For reload, do not use the old ctx after await ctx.reload().";
157
+ "This extension ctx is stale after session replacement or reload. Do not use a captured pi or command ctx after ctx.newSession(), ctx.fork(), ctx.switchSession(), or ctx.reload(). For newSession, fork, and switchSession, move post-replacement work into withSession and use the ctx passed to withSession. For reload, do not use the old ctx after await ctx.reload().";
157
158
  },
158
159
  // Pre-bind: queue registrations so bindCore() can flush them once the
159
160
  // model registry is available. bindCore() replaces both with direct calls.
@@ -340,12 +341,14 @@ async function loadExtension(extensionPath, cwd, eventBus, runtime, cacheToken)
340
341
  const resolvedPath = resolvePath(extensionPath, cwd, { normalizeUnicodeSpaces: true });
341
342
  try {
342
343
  const factory = await loadExtensionModule(resolvedPath, cacheToken);
344
+ time(`${extensionPath} module import`, "extensions");
343
345
  if (!factory) {
344
346
  return { extension: null, error: `Extension does not export a valid factory function: ${extensionPath}` };
345
347
  }
346
348
  const extension = createExtension(extensionPath, resolvedPath);
347
349
  const api = createExtensionAPI(extension, runtime, cwd, eventBus);
348
350
  await factory(api);
351
+ time(`${extensionPath} factory`, "extensions");
349
352
  return { extension, error: null };
350
353
  }
351
354
  catch (err) {
@@ -361,6 +364,7 @@ export async function loadExtensionFromFactory(factory, cwd, eventBus, runtime,
361
364
  const resolvedCwd = resolvePath(cwd);
362
365
  const api = createExtensionAPI(extension, runtime, resolvedCwd, eventBus);
363
366
  await factory(api);
367
+ time(`${extensionPath} factory`, "extensions");
364
368
  return extension;
365
369
  }
366
370
  /**
@@ -402,13 +402,11 @@ export class ModelRegistry {
402
402
  }
403
403
  }
404
404
  else if (!isBuiltIn) {
405
- // Non-built-in providers with custom models require endpoint + auth.
405
+ // Non-built-in providers with custom models require an endpoint.
406
+ // Auth can come from auth.json, --api-key, or provider request config.
406
407
  if (!providerConfig.baseUrl) {
407
408
  throw new Error(`Provider ${providerName}: "baseUrl" is required when defining custom models.`);
408
409
  }
409
- if (!providerConfig.apiKey) {
410
- throw new Error(`Provider ${providerName}: "apiKey" is required when defining custom models.`);
411
- }
412
410
  }
413
411
  // Built-in providers with custom models: baseUrl/apiKey/api are optional,
414
412
  // inherited from built-in models. Auth comes from env vars / auth storage.