@psnext/slingcli 2.5.20260722-2 → 2.5.20260722-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.
Files changed (100) hide show
  1. package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.js +9 -7
  2. package/node_modules/@earendil-works/pi-agent-core/dist/agent.js +25 -23
  3. package/node_modules/@earendil-works/pi-agent-core/dist/harness/agent-harness.js +32 -19
  4. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/branch-summarization.js +6 -7
  5. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/compaction.js +75 -26
  6. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/utils.js +6 -17
  7. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/jsonl-storage.js +56 -4
  8. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/memory-storage.js +56 -4
  9. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/repo-utils.js +2 -2
  10. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/session.js +30 -14
  11. package/node_modules/@earendil-works/pi-agent-core/dist/index.js +4 -2
  12. package/node_modules/@earendil-works/pi-agent-core/dist/stream-fn.js +17 -0
  13. package/node_modules/@earendil-works/pi-agent-core/package.json +3 -3
  14. package/node_modules/@earendil-works/pi-ai/dist/api/openai-codex-responses.js +2 -7
  15. package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.js +14 -2
  16. package/node_modules/@earendil-works/pi-ai/dist/api/pi-messages.js +2 -4
  17. package/node_modules/@earendil-works/pi-ai/dist/auth/helpers.js +3 -2
  18. package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/kimi-coding.js +249 -0
  19. package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/load.js +5 -0
  20. package/node_modules/@earendil-works/pi-ai/dist/bun-oauth.js +2 -0
  21. package/node_modules/@earendil-works/pi-ai/dist/env-api-keys.js +2 -0
  22. package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.js +60 -0
  23. package/node_modules/@earendil-works/pi-ai/dist/index.js +2 -0
  24. package/node_modules/@earendil-works/pi-ai/dist/models.generated.js +4 -0
  25. package/node_modules/@earendil-works/pi-ai/dist/providers/all.js +8 -0
  26. package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.js +3 -2
  27. package/node_modules/@earendil-works/pi-ai/dist/providers/data/.manifest.json +1 -0
  28. package/node_modules/@earendil-works/pi-ai/dist/providers/data/cerebras.json +1 -1
  29. package/node_modules/@earendil-works/pi-ai/dist/providers/data/cloudflare-ai-gateway.json +1 -1
  30. package/node_modules/@earendil-works/pi-ai/dist/providers/data/cloudflare-workers-ai.json +1 -1
  31. package/node_modules/@earendil-works/pi-ai/dist/providers/data/fireworks.json +1 -1
  32. package/node_modules/@earendil-works/pi-ai/dist/providers/data/github-copilot.json +1 -1
  33. package/node_modules/@earendil-works/pi-ai/dist/providers/data/groq.json +1 -1
  34. package/node_modules/@earendil-works/pi-ai/dist/providers/data/huggingface.json +1 -1
  35. package/node_modules/@earendil-works/pi-ai/dist/providers/data/kimi-coding.json +1 -1
  36. package/node_modules/@earendil-works/pi-ai/dist/providers/data/moonshotai-cn.json +1 -1
  37. package/node_modules/@earendil-works/pi-ai/dist/providers/data/moonshotai.json +1 -1
  38. package/node_modules/@earendil-works/pi-ai/dist/providers/data/openai-codex.json +1 -1
  39. package/node_modules/@earendil-works/pi-ai/dist/providers/data/openai.json +1 -1
  40. package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode-go.json +1 -1
  41. package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode.json +1 -1
  42. package/node_modules/@earendil-works/pi-ai/dist/providers/data/openrouter.json +1 -1
  43. package/node_modules/@earendil-works/pi-ai/dist/providers/data/qwen-token-plan-cn.json +1 -0
  44. package/node_modules/@earendil-works/pi-ai/dist/providers/data/qwen-token-plan.json +1 -0
  45. package/node_modules/@earendil-works/pi-ai/dist/providers/data/together.json +1 -1
  46. package/node_modules/@earendil-works/pi-ai/dist/providers/data/xai.json +1 -1
  47. package/node_modules/@earendil-works/pi-ai/dist/providers/kimi-coding.js +10 -2
  48. package/node_modules/@earendil-works/pi-ai/dist/providers/opencode-go.js +2 -0
  49. package/node_modules/@earendil-works/pi-ai/dist/providers/qwen-token-plan-cn.js +15 -0
  50. package/node_modules/@earendil-works/pi-ai/dist/providers/qwen-token-plan-cn.models.js +5 -0
  51. package/node_modules/@earendil-works/pi-ai/dist/providers/qwen-token-plan.js +15 -0
  52. package/node_modules/@earendil-works/pi-ai/dist/providers/qwen-token-plan.models.js +5 -0
  53. package/node_modules/@earendil-works/pi-ai/dist/providers/radius-config.js +18 -14
  54. package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.js +3 -0
  55. package/node_modules/@earendil-works/pi-ai/dist/utils/retry.js +79 -0
  56. package/node_modules/@earendil-works/pi-ai/dist/utils/text.js +10 -0
  57. package/node_modules/@earendil-works/{pi-agent-core/dist/harness/session → pi-ai/dist/utils}/uuid.js +4 -8
  58. package/node_modules/@earendil-works/pi-ai/package.json +8 -5
  59. package/node_modules/@earendil-works/pi-coding-agent/dist/cli/args.js +2 -0
  60. package/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session.js +72 -57
  61. package/node_modules/@earendil-works/pi-coding-agent/dist/core/compaction/branch-summarization.js +8 -11
  62. package/node_modules/@earendil-works/pi-coding-agent/dist/core/compaction/compaction.js +63 -26
  63. package/node_modules/@earendil-works/pi-coding-agent/dist/core/compaction/utils.js +6 -17
  64. package/node_modules/@earendil-works/pi-coding-agent/dist/core/extensions/runner.js +5 -0
  65. package/node_modules/@earendil-works/pi-coding-agent/dist/core/model-resolver.js +2 -0
  66. package/node_modules/@earendil-works/pi-coding-agent/dist/core/model-runtime.js +18 -15
  67. package/node_modules/@earendil-works/pi-coding-agent/dist/core/project-trust.js +1 -1
  68. package/node_modules/@earendil-works/pi-coding-agent/dist/core/remote-catalog-provider.js +26 -7
  69. package/node_modules/@earendil-works/pi-coding-agent/dist/core/sdk.js +6 -2
  70. package/node_modules/@earendil-works/pi-coding-agent/dist/core/session-manager.js +100 -24
  71. package/node_modules/@earendil-works/pi-coding-agent/dist/core/usage-totals.js +52 -0
  72. package/node_modules/@earendil-works/pi-coding-agent/dist/extensions/llama/client.js +3 -1
  73. package/node_modules/@earendil-works/pi-coding-agent/dist/extensions/llama/index.js +3 -3
  74. package/node_modules/@earendil-works/pi-coding-agent/dist/index.js +1 -1
  75. package/node_modules/@earendil-works/pi-coding-agent/dist/main.js +5 -1
  76. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/footer.js +20 -21
  77. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/interactive-mode.js +69 -25
  78. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/rpc/rpc-client.js +7 -0
  79. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/rpc/rpc-mode.js +4 -0
  80. package/node_modules/@earendil-works/pi-coding-agent/npm-shrinkwrap.json +16 -31
  81. package/node_modules/@earendil-works/pi-coding-agent/package.json +4 -4
  82. package/node_modules/@earendil-works/{pi-orchestrator → pi-server}/dist/cli.js +5 -5
  83. package/node_modules/@earendil-works/{pi-orchestrator → pi-server}/dist/config.js +9 -9
  84. package/node_modules/@earendil-works/{pi-orchestrator → pi-server}/dist/ipc/client.js +1 -1
  85. package/node_modules/@earendil-works/{pi-orchestrator → pi-server}/dist/ipc/server.js +1 -1
  86. package/node_modules/@earendil-works/{pi-orchestrator → pi-server}/dist/radius.js +8 -8
  87. package/node_modules/@earendil-works/{pi-orchestrator → pi-server}/dist/rpc-process.js +1 -1
  88. package/node_modules/@earendil-works/{pi-orchestrator → pi-server}/dist/serve.js +3 -3
  89. package/node_modules/@earendil-works/{pi-orchestrator → pi-server}/dist/storage.js +7 -7
  90. package/node_modules/@earendil-works/{pi-orchestrator → pi-server}/dist/supervisor.js +2 -2
  91. package/node_modules/@earendil-works/{pi-orchestrator → pi-server}/package.json +9 -6
  92. package/node_modules/@earendil-works/pi-tui/dist/components/editor.js +16 -10
  93. package/node_modules/@earendil-works/pi-tui/dist/tui.js +4 -2
  94. package/node_modules/@earendil-works/pi-tui/package.json +1 -1
  95. package/package.json +7 -7
  96. package/slingshot/index.js +253 -250
  97. /package/node_modules/@earendil-works/{pi-orchestrator → pi-server}/dist/handler.js +0 -0
  98. /package/node_modules/@earendil-works/{pi-orchestrator → pi-server}/dist/index.js +0 -0
  99. /package/node_modules/@earendil-works/{pi-orchestrator → pi-server}/dist/ipc/protocol.js +0 -0
  100. /package/node_modules/@earendil-works/{pi-orchestrator → pi-server}/dist/types.js +0 -0
@@ -1,6 +1,6 @@
1
+ import { uuidv7 } from "@earendil-works/pi-ai";
1
2
  import { SessionError, toError } from "../types.js";
2
3
  import { getFileSystemResultOrThrow } from "./repo-utils.js";
3
- import { uuidv7 } from "./uuid.js";
4
4
  function updateLabelCache(labelsById, entry) {
5
5
  if (entry.type !== "label")
6
6
  return;
@@ -216,15 +216,65 @@ export class JsonlSessionStorage {
216
216
  async getLabel(id) {
217
217
  return this.labelsById.get(id);
218
218
  }
219
- async getPathToRoot(leafId) {
219
+ async getSessionName() {
220
+ const entries = await this.findEntries("session_info");
221
+ return entries[entries.length - 1]?.name?.trim() || undefined;
222
+ }
223
+ async getSessionStats() {
224
+ let messageCount = 0;
225
+ let cachedTokens = 0;
226
+ let uncachedTokens = 0;
227
+ let totalTokens = 0;
228
+ let costTotal = 0;
229
+ for (const entry of this.entries) {
230
+ if (entry.type === "message") {
231
+ messageCount += 1;
232
+ }
233
+ const usage = entry.type === "message"
234
+ ? entry.message.role === "assistant"
235
+ ? entry.message.usage
236
+ : undefined
237
+ : entry.type === "compaction" || entry.type === "branch_summary"
238
+ ? entry.usage
239
+ : undefined;
240
+ if (!usage ||
241
+ typeof usage.input !== "number" ||
242
+ typeof usage.output !== "number" ||
243
+ typeof usage.cacheRead !== "number" ||
244
+ typeof usage.cacheWrite !== "number" ||
245
+ typeof usage.cost?.total !== "number") {
246
+ continue;
247
+ }
248
+ cachedTokens += usage.cacheRead;
249
+ uncachedTokens += usage.input + usage.cacheWrite;
250
+ totalTokens += usage.input + usage.output + usage.cacheRead + usage.cacheWrite;
251
+ costTotal += usage.cost.total;
252
+ }
253
+ return {
254
+ messageCount,
255
+ cachedTokens,
256
+ uncachedTokens,
257
+ totalTokens,
258
+ costTotal,
259
+ };
260
+ }
261
+ async getPathToRootOrCompaction(leafId) {
220
262
  if (leafId === null)
221
263
  return [];
222
264
  const path = [];
265
+ let stopAtEntryId = null;
223
266
  let current = this.byId.get(leafId);
224
267
  if (!current)
225
268
  throw new SessionError("not_found", `Entry ${leafId} not found`);
226
269
  while (current) {
227
270
  path.unshift(current);
271
+ if (stopAtEntryId !== null && current.id === stopAtEntryId)
272
+ break;
273
+ if (current.type === "compaction") {
274
+ if (current.retainedTail)
275
+ break;
276
+ stopAtEntryId = current.firstKeptEntryId ?? null;
277
+ }
228
278
  if (!current.parentId)
229
279
  break;
230
280
  const parent = this.byId.get(current.parentId);
@@ -234,8 +284,10 @@ export class JsonlSessionStorage {
234
284
  }
235
285
  return path;
236
286
  }
237
- async getEntries() {
238
- return [...this.entries];
287
+ async getEntries(options) {
288
+ const start = options?.afterEntrySeq ?? 0;
289
+ const end = options?.limit === undefined ? undefined : start + options.limit;
290
+ return this.entries.slice(start, end);
239
291
  }
240
292
  }
241
293
  //# sourceMappingURL=jsonl-storage.js.map
@@ -1,5 +1,5 @@
1
+ import { uuidv7 } from "@earendil-works/pi-ai";
1
2
  import { SessionError, } from "../types.js";
2
- import { uuidv7 } from "./uuid.js";
3
3
  function updateLabelCache(labelsById, entry) {
4
4
  if (entry.type !== "label")
5
5
  return;
@@ -91,15 +91,65 @@ export class InMemorySessionStorage {
91
91
  async getLabel(id) {
92
92
  return this.labelsById.get(id);
93
93
  }
94
- async getPathToRoot(leafId) {
94
+ async getSessionName() {
95
+ const entries = await this.findEntries("session_info");
96
+ return entries[entries.length - 1]?.name?.trim() || undefined;
97
+ }
98
+ async getSessionStats() {
99
+ let messageCount = 0;
100
+ let cachedTokens = 0;
101
+ let uncachedTokens = 0;
102
+ let totalTokens = 0;
103
+ let costTotal = 0;
104
+ for (const entry of this.entries) {
105
+ if (entry.type === "message") {
106
+ messageCount += 1;
107
+ }
108
+ const usage = entry.type === "message"
109
+ ? entry.message.role === "assistant"
110
+ ? entry.message.usage
111
+ : undefined
112
+ : entry.type === "compaction" || entry.type === "branch_summary"
113
+ ? entry.usage
114
+ : undefined;
115
+ if (!usage ||
116
+ typeof usage.input !== "number" ||
117
+ typeof usage.output !== "number" ||
118
+ typeof usage.cacheRead !== "number" ||
119
+ typeof usage.cacheWrite !== "number" ||
120
+ typeof usage.cost?.total !== "number") {
121
+ continue;
122
+ }
123
+ cachedTokens += usage.cacheRead;
124
+ uncachedTokens += usage.input + usage.cacheWrite;
125
+ totalTokens += usage.input + usage.output + usage.cacheRead + usage.cacheWrite;
126
+ costTotal += usage.cost.total;
127
+ }
128
+ return {
129
+ messageCount,
130
+ cachedTokens,
131
+ uncachedTokens,
132
+ totalTokens,
133
+ costTotal,
134
+ };
135
+ }
136
+ async getPathToRootOrCompaction(leafId) {
95
137
  if (leafId === null)
96
138
  return [];
97
139
  const path = [];
140
+ let stopAtEntryId = null;
98
141
  let current = this.byId.get(leafId);
99
142
  if (!current)
100
143
  throw new SessionError("not_found", `Entry ${leafId} not found`);
101
144
  while (current) {
102
145
  path.unshift(current);
146
+ if (stopAtEntryId !== null && current.id === stopAtEntryId)
147
+ break;
148
+ if (current.type === "compaction") {
149
+ if (current.retainedTail)
150
+ break;
151
+ stopAtEntryId = current.firstKeptEntryId ?? null;
152
+ }
103
153
  if (!current.parentId)
104
154
  break;
105
155
  const parent = this.byId.get(current.parentId);
@@ -109,8 +159,10 @@ export class InMemorySessionStorage {
109
159
  }
110
160
  return path;
111
161
  }
112
- async getEntries() {
113
- return [...this.entries];
162
+ async getEntries(options) {
163
+ const start = options?.afterEntrySeq ?? 0;
164
+ const end = options?.limit === undefined ? undefined : start + options.limit;
165
+ return this.entries.slice(start, end);
114
166
  }
115
167
  }
116
168
  //# sourceMappingURL=memory-storage.js.map
@@ -1,6 +1,6 @@
1
+ import { uuidv7 } from "@earendil-works/pi-ai";
1
2
  import { SessionError, } from "../types.js";
2
3
  import { Session } from "./session.js";
3
- import { uuidv7 } from "./uuid.js";
4
4
  export function createSessionId() {
5
5
  return uuidv7();
6
6
  }
@@ -34,6 +34,6 @@ export async function getEntriesToFork(storage, options) {
34
34
  }
35
35
  effectiveLeafId = target.parentId;
36
36
  }
37
- return storage.getPathToRoot(effectiveLeafId);
37
+ return storage.getPathToRootOrCompaction(effectiveLeafId);
38
38
  }
39
39
  //# sourceMappingURL=repo-utils.js.map
@@ -32,13 +32,21 @@ export function defaultContextEntryTransform(pathEntries) {
32
32
  }
33
33
  const entries = [compaction];
34
34
  const compactionIdx = pathEntries.findIndex((entry) => entry.type === "compaction" && entry.id === compaction.id);
35
- let foundFirstKept = false;
36
- for (let i = 0; i < compactionIdx; i++) {
37
- const entry = pathEntries[i];
38
- if (entry.id === compaction.firstKeptEntryId)
39
- foundFirstKept = true;
40
- if (foundFirstKept)
41
- entries.push(entry);
35
+ if (compaction.retainedTail) {
36
+ for (let i = compactionIdx + 1; i < pathEntries.length; i++) {
37
+ entries.push(pathEntries[i]);
38
+ }
39
+ return entries;
40
+ }
41
+ if (compaction.firstKeptEntryId) {
42
+ let foundFirstKept = false;
43
+ for (let i = 0; i < compactionIdx; i++) {
44
+ const entry = pathEntries[i];
45
+ if (entry.id === compaction.firstKeptEntryId)
46
+ foundFirstKept = true;
47
+ if (foundFirstKept)
48
+ entries.push(entry);
49
+ }
42
50
  }
43
51
  for (let i = compactionIdx + 1; i < pathEntries.length; i++) {
44
52
  entries.push(pathEntries[i]);
@@ -62,7 +70,10 @@ export function sessionEntryToContextMessages(entry, index, entries, options = {
62
70
  ];
63
71
  }
64
72
  if (entry.type === "compaction") {
65
- return [createCompactionSummaryMessage(entry.summary, entry.tokensBefore, entry.timestamp)];
73
+ return [
74
+ createCompactionSummaryMessage(entry.summary, entry.tokensBefore, entry.timestamp),
75
+ ...(entry.retainedTail ?? []),
76
+ ];
66
77
  }
67
78
  if (entry.type === "branch_summary" && entry.summary) {
68
79
  return [createBranchSummaryMessage(entry.summary, entry.fromId, entry.timestamp)];
@@ -97,12 +108,12 @@ export class Session {
97
108
  getEntry(id) {
98
109
  return this.storage.getEntry(id);
99
110
  }
100
- getEntries() {
101
- return this.storage.getEntries();
111
+ getEntries(options) {
112
+ return this.storage.getEntries(options);
102
113
  }
103
114
  async getBranch(fromId) {
104
115
  const leafId = fromId ?? (await this.storage.getLeafId());
105
- return this.storage.getPathToRoot(leafId);
116
+ return this.storage.getPathToRootOrCompaction(leafId);
106
117
  }
107
118
  async buildContextEntries(options = {}) {
108
119
  return buildContextEntries(await this.getBranch(), this.mergeContextBuildOptions(options));
@@ -122,9 +133,11 @@ export class Session {
122
133
  getLabel(id) {
123
134
  return this.storage.getLabel(id);
124
135
  }
136
+ getSessionStats() {
137
+ return this.storage.getSessionStats();
138
+ }
125
139
  async getSessionName() {
126
- const entries = await this.storage.findEntries("session_info");
127
- return entries[entries.length - 1]?.name?.trim() || undefined;
140
+ return this.storage.getSessionName();
128
141
  }
129
142
  async appendTypedEntry(entry) {
130
143
  await this.storage.appendEntry(entry);
@@ -167,7 +180,7 @@ export class Session {
167
180
  activeToolNames: [...activeToolNames],
168
181
  });
169
182
  }
170
- async appendCompaction(summary, firstKeptEntryId, tokensBefore, details, fromHook) {
183
+ async appendCompaction(summary, firstKeptEntryId, tokensBefore, details, fromHook, usage, retainedTail) {
171
184
  return this.appendTypedEntry({
172
185
  type: "compaction",
173
186
  id: await this.storage.createEntryId(),
@@ -176,7 +189,9 @@ export class Session {
176
189
  summary,
177
190
  firstKeptEntryId,
178
191
  tokensBefore,
192
+ retainedTail,
179
193
  details,
194
+ usage,
180
195
  fromHook,
181
196
  });
182
197
  }
@@ -240,6 +255,7 @@ export class Session {
240
255
  fromId: entryId ?? "root",
241
256
  summary: summary.summary,
242
257
  details: summary.details,
258
+ usage: summary.usage,
243
259
  fromHook: summary.fromHook,
244
260
  });
245
261
  }
@@ -1,10 +1,11 @@
1
1
  // Core Agent
2
+ export { uuidv7 } from "@earendil-works/pi-ai";
2
3
  export * from "./agent.js";
3
4
  // Loop functions
4
5
  export * from "./agent-loop.js";
5
6
  export * from "./harness/agent-harness.js";
6
7
  export { collectEntriesForBranchSummary, generateBranchSummary, prepareBranchEntries, } from "./harness/compaction/branch-summarization.js";
7
- export { calculateContextTokens, compact, DEFAULT_COMPACTION_SETTINGS, estimateContextTokens, estimateTokens, findCutPoint, findTurnStartIndex, generateSummary, getLastAssistantUsage, prepareCompaction, serializeConversation, shouldCompact, } from "./harness/compaction/compaction.js";
8
+ export { calculateContextTokens, compact, DEFAULT_COMPACTION_SETTINGS, estimateContextTokens, estimateTokens, findCutPoint, findTurnStartIndex, generateSummary, generateSummaryWithUsage, getLastAssistantUsage, prepareCompaction, serializeConversation, shouldCompact, } from "./harness/compaction/compaction.js";
8
9
  export * from "./harness/messages.js";
9
10
  export * from "./harness/prompt-templates.js";
10
11
  export * from "./harness/session/jsonl-repo.js";
@@ -13,7 +14,6 @@ export * from "./harness/session/memory-repo.js";
13
14
  export * from "./harness/session/memory-storage.js";
14
15
  export * from "./harness/session/repo-utils.js";
15
16
  export * from "./harness/session/session.js";
16
- export { uuidv7 } from "./harness/session/uuid.js";
17
17
  export * from "./harness/skills.js";
18
18
  export * from "./harness/system-prompt.js";
19
19
  // Harness
@@ -22,6 +22,8 @@ export * from "./harness/utils/shell-output.js";
22
22
  export * from "./harness/utils/truncate.js";
23
23
  // Proxy utilities
24
24
  export * from "./proxy.js";
25
+ // Stream defaults
26
+ export { setDefaultStreamFn } from "./stream-fn.js";
25
27
  // Types
26
28
  export * from "./types.js";
27
29
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,17 @@
1
+ let defaultStreamFn;
2
+ /**
3
+ * Configure the fallback used by Agent and low-level loops when callers omit streamFn.
4
+ *
5
+ * Hosts that provide a default model runtime can install its stream function here
6
+ * without making pi-agent-core depend on a provider catalog or compatibility layer.
7
+ */
8
+ export function setDefaultStreamFn(streamFn) {
9
+ defaultStreamFn = streamFn;
10
+ }
11
+ export function getDefaultStreamFn() {
12
+ if (!defaultStreamFn) {
13
+ throw new Error("No default stream function configured. Pass streamFn explicitly or call setDefaultStreamFn().");
14
+ }
15
+ return defaultStreamFn;
16
+ }
17
+ //# sourceMappingURL=stream-fn.js.map
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@earendil-works/pi-agent-core",
3
- "version": "0.80.10",
3
+ "version": "0.81.1",
4
4
  "description": "General-purpose agent with transport abstraction, state management, and attachment support",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -26,10 +26,10 @@
26
26
  "test": "vitest --run",
27
27
  "test:harness": "vitest --run --config vitest.harness.config.ts",
28
28
  "coverage:harness": "vitest --run --config vitest.harness.config.ts --coverage",
29
- "prepublishOnly": "npm run clean && npm run build"
29
+ "prepublishOnly": "npm run build"
30
30
  },
31
31
  "dependencies": {
32
- "@earendil-works/pi-ai": "^0.80.10",
32
+ "@earendil-works/pi-ai": "^0.81.1",
33
33
  "ignore": "7.0.5",
34
34
  "typebox": "1.1.38",
35
35
  "yaml": "2.9.0"
@@ -15,6 +15,7 @@ import { formatProviderError, normalizeProviderError } from "../utils/error-body
15
15
  import { AssistantMessageEventStream } from "../utils/event-stream.js";
16
16
  import { headersToRecord } from "../utils/headers.js";
17
17
  import { resolveHttpProxyUrlForTarget } from "../utils/node-http-proxy.js";
18
+ import { uuidv7 } from "../utils/uuid.js";
18
19
  import { clampOpenAIPromptCacheKey } from "./openai-prompt-cache.js";
19
20
  import { convertResponsesMessages, convertResponsesTools, processResponsesStream } from "./openai-responses-shared.js";
20
21
  import { buildBaseOptions } from "./simple-options.js";
@@ -162,7 +163,7 @@ export const stream = (model, context, options) => {
162
163
  body = nextBody;
163
164
  }
164
165
  const codexSessionId = clampOpenAIPromptCacheKey(options?.sessionId);
165
- const websocketRequestId = codexSessionId || createCodexRequestId();
166
+ const websocketRequestId = codexSessionId || uuidv7();
166
167
  const sseHeaders = buildSSEHeaders(model.headers, options?.headers, accountId, apiKey, codexSessionId);
167
168
  const websocketHeaders = buildWebSocketHeaders(model.headers, options?.headers, accountId, apiKey, websocketRequestId);
168
169
  const bodyJson = JSON.stringify(body);
@@ -1179,12 +1180,6 @@ function extractAccountId(token) {
1179
1180
  throw new Error("Failed to extract accountId from token");
1180
1181
  }
1181
1182
  }
1182
- function createCodexRequestId() {
1183
- if (typeof globalThis.crypto?.randomUUID === "function") {
1184
- return globalThis.crypto.randomUUID();
1185
- }
1186
- return `codex_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
1187
- }
1188
1183
  function buildBaseCodexHeaders(initHeaders, additionalHeaders, accountId, token) {
1189
1184
  const headers = new Headers(initHeaders);
1190
1185
  for (const [key, value] of Object.entries(additionalHeaders || {})) {
@@ -2,6 +2,7 @@ import OpenAI from "openai";
2
2
  import { calculateCost, clampThinkingLevel } from "../models.js";
3
3
  import { formatProviderError, normalizeProviderError } from "../utils/error-body.js";
4
4
  import { AssistantMessageEventStream } from "../utils/event-stream.js";
5
+ import { shortHash } from "../utils/hash.js";
5
6
  import { headersToRecord } from "../utils/headers.js";
6
7
  import { parseStreamingJson } from "../utils/json-parse.js";
7
8
  import { getProviderEnvValue } from "../utils/provider-env.js";
@@ -699,10 +700,21 @@ export function convertMessages(model, context, compat) {
699
700
  // Format: {call_id}|{id} where {id} can be 400+ chars with special chars (+, /, =)
700
701
  // These come from providers like github-copilot, openai-codex, opencode
701
702
  // Extract just the call_id part and normalize it
703
+ // Multiple tool calls in the same turn can share call_id but differ by item_id.
704
+ // Preserve item-level uniqueness when replaying into Chat Completions, which
705
+ // requires distinct tool call ids.
702
706
  if (id.includes("|")) {
703
- const [callId] = id.split("|");
704
707
  // Sanitize to allowed chars and truncate to 40 chars (OpenAI limit)
705
- return callId.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 40);
708
+ const separatorIndex = id.indexOf("|");
709
+ const callId = id.slice(0, separatorIndex).replace(/[^a-zA-Z0-9_-]/g, "_");
710
+ const itemId = id.slice(separatorIndex + 1).replace(/[^a-zA-Z0-9_-]/g, "_");
711
+ const combinedId = itemId.length > 0 ? `${callId}_${itemId}` : callId;
712
+ if (combinedId.length <= 40) {
713
+ return combinedId;
714
+ }
715
+ const hash = shortHash(id).slice(0, 8);
716
+ const prefix = callId.slice(0, Math.max(1, 40 - hash.length - 1));
717
+ return `${prefix}_${hash}`;
706
718
  }
707
719
  if (model.provider === "openai")
708
720
  return id.length > 40 ? id.slice(0, 40) : id;
@@ -23,13 +23,11 @@ export class PiMessagesResponseError extends Error {
23
23
  this.diagnosticDetails = diagnosticDetails;
24
24
  }
25
25
  }
26
- function isRecord(value) {
27
- return typeof value === "object" && value !== null && !Array.isArray(value);
28
- }
29
26
  function parsePiMessagesErrorBody(body) {
30
27
  try {
31
28
  const parsed = JSON.parse(body);
32
- return isRecord(parsed) && isRecord(parsed.error) ? parsed : undefined;
29
+ const error = parsed?.error;
30
+ return parsed && typeof error === "object" && error !== null && !Array.isArray(error) ? parsed : undefined;
33
31
  }
34
32
  catch {
35
33
  return undefined;
@@ -12,8 +12,9 @@ export function envApiKeyAuth(name, envVars) {
12
12
  return { type: "api_key", key };
13
13
  },
14
14
  resolve: async ({ ctx, credential }) => {
15
- if (credential?.key)
16
- return { auth: { apiKey: credential.key }, source: "stored credential" };
15
+ if (credential?.key) {
16
+ return { auth: { apiKey: credential.key }, env: credential.env, source: "stored credential" };
17
+ }
17
18
  for (const envVar of envVars) {
18
19
  const value = await ctx.env(envVar);
19
20
  if (value)