@oh-my-pi/pi-agent-core 16.2.0 → 16.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/dist/types/types.d.ts +22 -0
- package/package.json +7 -7
- package/src/agent-loop.ts +7 -2
- package/src/compaction/compaction.ts +1 -1
- package/src/compaction/openai.ts +1 -1
- package/src/proxy.ts +23 -17
- package/src/types.ts +21 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [16.2.2] - 2026-06-27
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Added optional AgentTool.matcherPaths(args) and AgentTool.matcherEntries(args) hooks to allow tools to surface target file paths and isolate file evaluations for path-scoped stream matchers (e.g., when handling multi-file payloads or embedded paths in streamed arguments).
|
|
10
|
+
|
|
11
|
+
### Removed
|
|
12
|
+
|
|
13
|
+
- Removed support for Pi dialect integration.
|
|
14
|
+
|
|
5
15
|
## [16.2.0] - 2026-06-27
|
|
6
16
|
|
|
7
17
|
### Added
|
package/dist/types/types.d.ts
CHANGED
|
@@ -545,6 +545,28 @@ export interface AgentTool<TParameters extends TSchema = TSchema, TDetails = any
|
|
|
545
545
|
* matching.
|
|
546
546
|
*/
|
|
547
547
|
matcherDigest?: (args: unknown) => string | undefined;
|
|
548
|
+
/**
|
|
549
|
+
* Surface the target file paths a (potentially partial) streamed call would
|
|
550
|
+
* touch, so path-scoped stream matchers (e.g. TTSR `tool:edit(*.ts)` globs)
|
|
551
|
+
* can match without a top-level `path`/`paths` argument. Used for tools whose
|
|
552
|
+
* wire grammar embeds paths inside the streamed payload (hashline section
|
|
553
|
+
* headers, apply_patch envelope markers). Return `undefined` (or an empty
|
|
554
|
+
* array) to fall back to the caller's top-level argument scan.
|
|
555
|
+
*/
|
|
556
|
+
matcherPaths?: (args: unknown) => readonly string[] | undefined;
|
|
557
|
+
/**
|
|
558
|
+
* Per-file projection of a (potentially partial) streamed call, pairing each
|
|
559
|
+
* touched file path with the digest of only the lines added to that file.
|
|
560
|
+
* Path-scoped stream matchers (TTSR) evaluate each entry in isolation, so a
|
|
561
|
+
* scoped rule like `tool:edit(*.ts)` never fires on text that actually
|
|
562
|
+
* belongs to a sibling Markdown hunk in a multi-file payload. Takes
|
|
563
|
+
* precedence over {@link matcherDigest} + {@link matcherPaths} when present;
|
|
564
|
+
* returns `undefined` (or empty) to fall back to the combined hooks.
|
|
565
|
+
*/
|
|
566
|
+
matcherEntries?: (args: unknown) => readonly {
|
|
567
|
+
path: string;
|
|
568
|
+
digest: string;
|
|
569
|
+
}[] | undefined;
|
|
548
570
|
/** Capability tier declaration used by approval gates. Omitted means "exec". */
|
|
549
571
|
approval?: ToolApproval;
|
|
550
572
|
/** Lines appended after the standard approval prompt header. */
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-agent-core",
|
|
4
|
-
"version": "16.2.
|
|
4
|
+
"version": "16.2.2",
|
|
5
5
|
"description": "General-purpose agent with transport abstraction, state management, and attachment support",
|
|
6
6
|
"homepage": "https://omp.sh",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -35,12 +35,12 @@
|
|
|
35
35
|
"fmt": "biome format --write ."
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@oh-my-pi/pi-ai": "16.2.
|
|
39
|
-
"@oh-my-pi/pi-catalog": "16.2.
|
|
40
|
-
"@oh-my-pi/pi-natives": "16.2.
|
|
41
|
-
"@oh-my-pi/pi-utils": "16.2.
|
|
42
|
-
"@oh-my-pi/pi-wire": "16.2.
|
|
43
|
-
"@oh-my-pi/snapcompact": "16.2.
|
|
38
|
+
"@oh-my-pi/pi-ai": "16.2.2",
|
|
39
|
+
"@oh-my-pi/pi-catalog": "16.2.2",
|
|
40
|
+
"@oh-my-pi/pi-natives": "16.2.2",
|
|
41
|
+
"@oh-my-pi/pi-utils": "16.2.2",
|
|
42
|
+
"@oh-my-pi/pi-wire": "16.2.2",
|
|
43
|
+
"@oh-my-pi/snapcompact": "16.2.2",
|
|
44
44
|
"@opentelemetry/api": "^1.9.1"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
package/src/agent-loop.ts
CHANGED
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
renderToolExamples,
|
|
26
26
|
wrapInbandToolStream,
|
|
27
27
|
} from "@oh-my-pi/pi-ai/dialect";
|
|
28
|
+
import * as AIError from "@oh-my-pi/pi-ai/error";
|
|
28
29
|
import {
|
|
29
30
|
createHarmonyAuditEvent,
|
|
30
31
|
detectHarmonyLeakInAssistantMessage,
|
|
@@ -134,7 +135,6 @@ export function resolveOwnedDialectFromEnv(value: string | undefined): Dialect |
|
|
|
134
135
|
case "anthropic":
|
|
135
136
|
case "deepseek":
|
|
136
137
|
case "harmony":
|
|
137
|
-
case "pi":
|
|
138
138
|
case "qwen3":
|
|
139
139
|
case "gemini":
|
|
140
140
|
case "gemma":
|
|
@@ -1557,8 +1557,12 @@ function emitAbortedAssistantMessage(
|
|
|
1557
1557
|
requestSignal: AbortSignal | undefined,
|
|
1558
1558
|
): AssistantMessage {
|
|
1559
1559
|
const errorMessage = abortReasonText(requestSignal);
|
|
1560
|
+
const errorId =
|
|
1561
|
+
errorMessage === "Request was aborted"
|
|
1562
|
+
? AIError.create(AIError.Flag.Abort)
|
|
1563
|
+
: AIError.classify(requestSignal?.reason) || undefined;
|
|
1560
1564
|
const base: AssistantMessage = partialMessage
|
|
1561
|
-
? { ...partialMessage, stopReason: "aborted", errorMessage }
|
|
1565
|
+
? { ...partialMessage, stopReason: "aborted", errorMessage, errorId }
|
|
1562
1566
|
: {
|
|
1563
1567
|
role: "assistant",
|
|
1564
1568
|
content: [],
|
|
@@ -1575,6 +1579,7 @@ function emitAbortedAssistantMessage(
|
|
|
1575
1579
|
},
|
|
1576
1580
|
stopReason: "aborted",
|
|
1577
1581
|
errorMessage,
|
|
1582
|
+
errorId,
|
|
1578
1583
|
timestamp: Date.now(),
|
|
1579
1584
|
};
|
|
1580
1585
|
// Only tool calls that reached `toolcall_end` survive abort/error replay. A
|
|
@@ -14,12 +14,12 @@ import {
|
|
|
14
14
|
type Message,
|
|
15
15
|
type MessageAttribution,
|
|
16
16
|
type Model,
|
|
17
|
-
ProviderHttpError,
|
|
18
17
|
type SimpleStreamOptions,
|
|
19
18
|
type Tool,
|
|
20
19
|
type Usage,
|
|
21
20
|
withAuth,
|
|
22
21
|
} from "@oh-my-pi/pi-ai";
|
|
22
|
+
import { ProviderHttpError } from "@oh-my-pi/pi-ai/error";
|
|
23
23
|
import { preferredDialect } from "@oh-my-pi/pi-catalog/identity";
|
|
24
24
|
import { clampThinkingLevelForModel } from "@oh-my-pi/pi-catalog/model-thinking";
|
|
25
25
|
import { logger, prompt } from "@oh-my-pi/pi-utils";
|
package/src/compaction/openai.ts
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* with `{ summary, shortSummary? }`.
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
import { ProviderHttpError } from "@oh-my-pi/pi-ai/
|
|
15
|
+
import { ProviderHttpError } from "@oh-my-pi/pi-ai/error";
|
|
16
16
|
import { parseAzureDeploymentNameMap, parseTextSignature } from "@oh-my-pi/pi-ai/providers/openai-shared";
|
|
17
17
|
import { transformMessages } from "@oh-my-pi/pi-ai/providers/transform-messages";
|
|
18
18
|
import type { Api, AssistantMessage, FetchImpl, Message, Model } from "@oh-my-pi/pi-ai/types";
|
package/src/proxy.ts
CHANGED
|
@@ -13,6 +13,12 @@ import {
|
|
|
13
13
|
type StopReason,
|
|
14
14
|
type ToolCall,
|
|
15
15
|
} from "@oh-my-pi/pi-ai";
|
|
16
|
+
import {
|
|
17
|
+
clearStreamingPartialJson,
|
|
18
|
+
kStreamingPartialJson,
|
|
19
|
+
type StreamingPartialJsonCarrier,
|
|
20
|
+
setStreamingPartialJson,
|
|
21
|
+
} from "@oh-my-pi/pi-ai/utils/block-symbols";
|
|
16
22
|
import { calculateCost } from "@oh-my-pi/pi-catalog/models";
|
|
17
23
|
import { parseStreamingJson, readSseJson } from "@oh-my-pi/pi-utils";
|
|
18
24
|
|
|
@@ -183,8 +189,8 @@ export function streamProxy(model: Model, context: Context, options: ProxyStream
|
|
|
183
189
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
184
190
|
const reason = options.signal?.aborted ? "aborted" : "error";
|
|
185
191
|
partial.stopReason = reason;
|
|
186
|
-
scrubPartialJson(partial);
|
|
187
192
|
partial.errorMessage = errorMessage;
|
|
193
|
+
scrubPartialJson(partial);
|
|
188
194
|
stream.push({
|
|
189
195
|
type: "error",
|
|
190
196
|
reason,
|
|
@@ -202,14 +208,13 @@ export function streamProxy(model: Model, context: Context, options: ProxyStream
|
|
|
202
208
|
}
|
|
203
209
|
|
|
204
210
|
/**
|
|
205
|
-
*
|
|
206
|
-
* that still carry it (e.g. when the stream ended without a `toolcall_end`)
|
|
211
|
+
* Clear the `partialJson` streaming symbol from any tool-call content blocks
|
|
212
|
+
* that still carry it (e.g. when the stream ended without a `toolcall_end`), so
|
|
213
|
+
* the finalized `AssistantMessage` no longer reads as still-streaming.
|
|
207
214
|
*/
|
|
208
215
|
function scrubPartialJson(partial: AssistantMessage): void {
|
|
209
216
|
for (const block of partial.content) {
|
|
210
|
-
if (block?.type === "toolCall")
|
|
211
|
-
delete (block as ToolCall & { partialJson?: string }).partialJson;
|
|
212
|
-
}
|
|
217
|
+
if (block?.type === "toolCall") clearStreamingPartialJson(block);
|
|
213
218
|
}
|
|
214
219
|
}
|
|
215
220
|
|
|
@@ -218,10 +223,10 @@ function scrubPartialJson(partial: AssistantMessage): void {
|
|
|
218
223
|
*
|
|
219
224
|
* Streaming `partialJson` for in-progress tool calls is accumulated in a
|
|
220
225
|
* side-channel map keyed by `contentIndex` and also written onto the content
|
|
221
|
-
* object
|
|
222
|
-
* during streaming. The field is
|
|
223
|
-
*
|
|
224
|
-
*
|
|
226
|
+
* object as a symbol-keyed field so downstream renderers can read it
|
|
227
|
+
* during streaming. The field is cleared at `toolcall_end` and scrubbed from any
|
|
228
|
+
* remaining blocks at `done`/`error` so the finalized `AssistantMessage` never
|
|
229
|
+
* reads as still-streaming.
|
|
225
230
|
*/
|
|
226
231
|
function processProxyEvent(
|
|
227
232
|
model: Model,
|
|
@@ -240,9 +245,10 @@ function processProxyEvent(
|
|
|
240
245
|
totalTokens: 0,
|
|
241
246
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
242
247
|
};
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
248
|
+
partial.errorMessage = undefined;
|
|
249
|
+
partial.errorId = undefined;
|
|
250
|
+
partial.duration = undefined;
|
|
251
|
+
(partial as { stopReason?: string }).stopReason = undefined;
|
|
246
252
|
return { type: "start", partial };
|
|
247
253
|
|
|
248
254
|
case "text_start":
|
|
@@ -315,8 +321,8 @@ function processProxyEvent(
|
|
|
315
321
|
id: proxyEvent.id,
|
|
316
322
|
name: proxyEvent.toolName,
|
|
317
323
|
arguments: {},
|
|
318
|
-
|
|
319
|
-
} as ToolCall &
|
|
324
|
+
[kStreamingPartialJson]: "",
|
|
325
|
+
} as ToolCall & StreamingPartialJsonCarrier;
|
|
320
326
|
partialJsonByIndex.set(proxyEvent.contentIndex, "");
|
|
321
327
|
return { type: "toolcall_start", contentIndex: proxyEvent.contentIndex, partial };
|
|
322
328
|
case "toolcall_delta": {
|
|
@@ -325,7 +331,7 @@ function processProxyEvent(
|
|
|
325
331
|
const acc = (partialJsonByIndex.get(proxyEvent.contentIndex) ?? "") + proxyEvent.delta;
|
|
326
332
|
partialJsonByIndex.set(proxyEvent.contentIndex, acc);
|
|
327
333
|
content.arguments = parseStreamingJson(acc) || {};
|
|
328
|
-
(content
|
|
334
|
+
setStreamingPartialJson(content, acc);
|
|
329
335
|
partial.content[proxyEvent.contentIndex] = { ...content }; // Trigger reactivity
|
|
330
336
|
return {
|
|
331
337
|
type: "toolcall_delta",
|
|
@@ -341,7 +347,7 @@ function processProxyEvent(
|
|
|
341
347
|
const content = partial.content[proxyEvent.contentIndex];
|
|
342
348
|
if (content?.type === "toolCall") {
|
|
343
349
|
partialJsonByIndex.delete(proxyEvent.contentIndex);
|
|
344
|
-
|
|
350
|
+
clearStreamingPartialJson(content);
|
|
345
351
|
return {
|
|
346
352
|
type: "toolcall_end",
|
|
347
353
|
contentIndex: proxyEvent.contentIndex,
|
package/src/types.ts
CHANGED
|
@@ -633,6 +633,27 @@ export interface AgentTool<TParameters extends TSchema = TSchema, TDetails = any
|
|
|
633
633
|
*/
|
|
634
634
|
matcherDigest?: (args: unknown) => string | undefined;
|
|
635
635
|
|
|
636
|
+
/**
|
|
637
|
+
* Surface the target file paths a (potentially partial) streamed call would
|
|
638
|
+
* touch, so path-scoped stream matchers (e.g. TTSR `tool:edit(*.ts)` globs)
|
|
639
|
+
* can match without a top-level `path`/`paths` argument. Used for tools whose
|
|
640
|
+
* wire grammar embeds paths inside the streamed payload (hashline section
|
|
641
|
+
* headers, apply_patch envelope markers). Return `undefined` (or an empty
|
|
642
|
+
* array) to fall back to the caller's top-level argument scan.
|
|
643
|
+
*/
|
|
644
|
+
matcherPaths?: (args: unknown) => readonly string[] | undefined;
|
|
645
|
+
|
|
646
|
+
/**
|
|
647
|
+
* Per-file projection of a (potentially partial) streamed call, pairing each
|
|
648
|
+
* touched file path with the digest of only the lines added to that file.
|
|
649
|
+
* Path-scoped stream matchers (TTSR) evaluate each entry in isolation, so a
|
|
650
|
+
* scoped rule like `tool:edit(*.ts)` never fires on text that actually
|
|
651
|
+
* belongs to a sibling Markdown hunk in a multi-file payload. Takes
|
|
652
|
+
* precedence over {@link matcherDigest} + {@link matcherPaths} when present;
|
|
653
|
+
* returns `undefined` (or empty) to fall back to the combined hooks.
|
|
654
|
+
*/
|
|
655
|
+
matcherEntries?: (args: unknown) => readonly { path: string; digest: string }[] | undefined;
|
|
656
|
+
|
|
636
657
|
/** Capability tier declaration used by approval gates. Omitted means "exec". */
|
|
637
658
|
approval?: ToolApproval;
|
|
638
659
|
|