@oh-my-pi/pi-agent-core 18.14.0 → 18.14.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 +20 -0
- package/dist/types/compaction/compaction-v2-streaming.d.ts +1 -0
- package/dist/types/compaction/compaction.d.ts +2 -0
- package/dist/types/compaction/utils.d.ts +2 -0
- package/dist/types/sent-tool-definitions.d.ts +6 -5
- package/dist/types/types.d.ts +2 -1
- package/package.json +8 -8
- package/src/agent-loop.ts +8 -17
- package/src/agent.ts +4 -5
- package/src/compaction/branch-summarization.ts +4 -0
- package/src/compaction/compaction-v2-streaming.ts +4 -0
- package/src/compaction/compaction.ts +17 -1
- package/src/compaction/utils.ts +5 -0
- package/src/sent-tool-definitions.ts +12 -5
- package/src/types.ts +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [18.14.2] - 2026-10-31
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- The soft steering signal a tool sees as `toolCall.steeringSignal` now aborts with the source of the queued message (`"user"`, `"irc"`, …) as its `reason`, so a tool can yield only to the sources it should ([#17537](https://github.com/can1357/oh-my-pi/issues/17537))
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- Reject incomplete local compaction, turn-prefix, handoff, and branch summaries ending with `stopReason: "length"` instead of using truncated text as complete context. Omit incomplete display-only short summaries without discarding a completed full summary ([#17499](https://github.com/can1357/oh-my-pi/pull/17499) by [@Xytronix](https://github.com/Xytronix)).
|
|
14
|
+
|
|
15
|
+
## [18.14.1] - 2026-10-31
|
|
16
|
+
|
|
17
|
+
### Breaking Changes
|
|
18
|
+
|
|
19
|
+
- `SentToolDefinitions.inactiveFor(messages, tools)` is replaced by `withInactiveTools(context)`, which also clears a stale `inactiveTools`; `transformProviderContext` now receives the request's `inactiveTools` ([#17492](https://github.com/can1357/oh-my-pi/pull/17492))
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- Long tool-call intents no longer block tool execution when intent tracing is enabled ([#13140](https://github.com/can1357/oh-my-pi/issues/13140)).
|
|
24
|
+
|
|
5
25
|
## [18.14.0] - 2026-10-29
|
|
6
26
|
|
|
7
27
|
### Breaking Changes
|
|
@@ -85,6 +85,7 @@ export declare function requestCompactionV2Streaming(model: Model, apiKey: strin
|
|
|
85
85
|
providerSessionState?: Map<string, ProviderSessionState>;
|
|
86
86
|
codexCompaction?: CodexCompactionContext;
|
|
87
87
|
preferWebsockets?: boolean;
|
|
88
|
+
codexAccessPrograms?: boolean;
|
|
88
89
|
}): Promise<CompactionV2Response>;
|
|
89
90
|
/** Build Codex-style V2 replacement history from retention candidates plus compaction output. */
|
|
90
91
|
export declare function buildCompactionV2ReplacementHistory(input: unknown[], compactionItem: Record<string, unknown>, retainedMessageBudget?: number): {
|
|
@@ -256,6 +256,8 @@ export interface SummaryOptions {
|
|
|
256
256
|
providerSessionState?: Map<string, ProviderSessionState>;
|
|
257
257
|
/** Whether Codex remote compaction should prefer the provider WebSocket transport. */
|
|
258
258
|
preferWebsockets?: boolean;
|
|
259
|
+
/** Daybreak Blue request on Codex remote compaction; see `StreamOptions.codexAccessPrograms`. */
|
|
260
|
+
codexAccessPrograms?: boolean;
|
|
259
261
|
/** Classification shared by every provider request in this logical compaction. */
|
|
260
262
|
codexCompaction?: CodexCompactionContext;
|
|
261
263
|
/** Provider-visible tools for remote compaction transports that replay native tool history. */
|
|
@@ -10,6 +10,8 @@ export interface FileOperations {
|
|
|
10
10
|
edited: Set<string>;
|
|
11
11
|
}
|
|
12
12
|
export declare function createFileOps(): FileOperations;
|
|
13
|
+
/** Error text for a summarization call that hit its output limit (`stopReason: "length"`): partial text is never used. */
|
|
14
|
+
export declare function incompleteOutputError(prefix: string): string;
|
|
13
15
|
/**
|
|
14
16
|
* Split a read-tool path into its base path and trailing selector, mirroring the
|
|
15
17
|
* read tool's own splitter. Single source of the grammar in this package: the
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Context, Tool } from "@oh-my-pi/pi-ai";
|
|
2
2
|
/**
|
|
3
3
|
* Last wire definition this Agent sent for each tool name, so a provider that keeps
|
|
4
4
|
* withdrawn tools declared (Anthropic `tool_removal`) can re-declare them byte-identically.
|
|
@@ -9,9 +9,10 @@ export declare class SentToolDefinitions {
|
|
|
9
9
|
/** Remember the definitions a request is about to send. */
|
|
10
10
|
record(tools: readonly Tool[]): void;
|
|
11
11
|
/**
|
|
12
|
-
*
|
|
13
|
-
* that are not in `
|
|
14
|
-
*
|
|
12
|
+
* `context` with {@link Context.inactiveTools} set to the definitions for names the latest
|
|
13
|
+
* `requestControls.tools.declared` in its messages holds that are not in its `tools`; cleared when
|
|
14
|
+
* there are none or the request declares no tools. Names never sent by this Agent are skipped: the
|
|
15
|
+
* provider drops them from the declaration.
|
|
15
16
|
*/
|
|
16
|
-
|
|
17
|
+
withInactiveTools(context: Context): Context;
|
|
17
18
|
}
|
package/dist/types/types.d.ts
CHANGED
|
@@ -588,7 +588,8 @@ export interface ToolCallContext {
|
|
|
588
588
|
providerMetadata?: ToolCallProviderMetadata;
|
|
589
589
|
/**
|
|
590
590
|
* Cooperative steering signal: aborted when a queued user/steering message
|
|
591
|
-
* (or an interrupting peer IRC) is detected while this tool batch runs.
|
|
591
|
+
* (or an interrupting peer IRC) is detected while this tool batch runs. Its
|
|
592
|
+
* `reason` names the source: a {@link SteeringInterruptSource} or `"irc"`.
|
|
592
593
|
* Unlike the hard abort signal it NEVER kills the tool — long-running
|
|
593
594
|
* tools MAY observe it (via `ctx.toolCall.steeringSignal`) to finish early
|
|
594
595
|
* or background themselves so the message injects promptly; ignoring it is
|
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": "18.14.
|
|
4
|
+
"version": "18.14.2",
|
|
5
5
|
"description": "General-purpose agent with transport abstraction, state management, and attachment support",
|
|
6
6
|
"homepage": "https://omp.sh",
|
|
7
7
|
"author": {
|
|
@@ -38,16 +38,16 @@
|
|
|
38
38
|
"fmt": "oxfmt --no-error-on-unmatched-pattern 'src/**/*.{ts,tsx}' '{test,bench,examples,scripts}/**/*.ts' '*.ts'"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@oh-my-pi/pi-ai": "18.14.
|
|
42
|
-
"@oh-my-pi/pi-catalog": "18.14.
|
|
43
|
-
"@oh-my-pi/pi-natives": "18.14.
|
|
44
|
-
"@oh-my-pi/pi-utils": "18.14.
|
|
45
|
-
"@oh-my-pi/pi-wire": "18.14.
|
|
46
|
-
"@oh-my-pi/snapcompact": "18.14.
|
|
41
|
+
"@oh-my-pi/pi-ai": "18.14.2",
|
|
42
|
+
"@oh-my-pi/pi-catalog": "18.14.2",
|
|
43
|
+
"@oh-my-pi/pi-natives": "18.14.2",
|
|
44
|
+
"@oh-my-pi/pi-utils": "18.14.2",
|
|
45
|
+
"@oh-my-pi/pi-wire": "18.14.2",
|
|
46
|
+
"@oh-my-pi/snapcompact": "18.14.2",
|
|
47
47
|
"@opentelemetry/api": "^1.9.1"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
|
-
"@oh-my-pi/omptype": "18.14.
|
|
50
|
+
"@oh-my-pi/omptype": "18.14.2",
|
|
51
51
|
"@opentelemetry/context-async-hooks": "^2.9.0",
|
|
52
52
|
"@opentelemetry/sdk-trace-base": "^2.9.0",
|
|
53
53
|
"@types/bun": "^1.3.14"
|
package/src/agent-loop.ts
CHANGED
|
@@ -1071,12 +1071,6 @@ function resolveIntentMode(intent: AgentTool["intent"]): "require" | "optional"
|
|
|
1071
1071
|
return "require";
|
|
1072
1072
|
}
|
|
1073
1073
|
|
|
1074
|
-
/**
|
|
1075
|
-
* Longest `i` value accepted as an intent; anything past this is a tool
|
|
1076
|
-
* payload the model put in the wrong field, not an intent.
|
|
1077
|
-
*/
|
|
1078
|
-
const MAX_INTENT_LENGTH = 200;
|
|
1079
|
-
|
|
1080
1074
|
function extractIntent(args: Record<string, unknown>): { intent?: string; strippedArgs: Record<string, unknown> } {
|
|
1081
1075
|
const { [INTENT_FIELD]: intent, ...strippedArgs } = args;
|
|
1082
1076
|
if (typeof intent !== "string") {
|
|
@@ -1976,6 +1970,9 @@ async function prepareProviderCall(
|
|
|
1976
1970
|
}),
|
|
1977
1971
|
};
|
|
1978
1972
|
}
|
|
1973
|
+
// Before the transform too, so a request-size clamp in it measures the withdrawn definitions the provider
|
|
1974
|
+
// re-declares; recomputed below against what the transform returns.
|
|
1975
|
+
if (config.sentToolDefinitions) llmContext = config.sentToolDefinitions.withInactiveTools(llmContext);
|
|
1979
1976
|
if (config.transformProviderContext) {
|
|
1980
1977
|
llmContext = await config.transformProviderContext(llmContext, model);
|
|
1981
1978
|
}
|
|
@@ -1991,10 +1988,9 @@ async function prepareProviderCall(
|
|
|
1991
1988
|
};
|
|
1992
1989
|
}
|
|
1993
1990
|
// After `transformProviderContext`, so the recorded definitions are exactly what the provider receives.
|
|
1994
|
-
if (config.sentToolDefinitions
|
|
1995
|
-
config.sentToolDefinitions.record(llmContext.tools);
|
|
1996
|
-
|
|
1997
|
-
if (inactiveTools) llmContext = { ...llmContext, inactiveTools };
|
|
1991
|
+
if (config.sentToolDefinitions) {
|
|
1992
|
+
if (llmContext.tools) config.sentToolDefinitions.record(llmContext.tools);
|
|
1993
|
+
llmContext = config.sentToolDefinitions.withInactiveTools(llmContext);
|
|
1998
1994
|
}
|
|
1999
1995
|
return { model, context: llmContext, promptToolWireTools, ownedDialect };
|
|
2000
1996
|
}
|
|
@@ -3105,11 +3101,6 @@ async function prepareToolCallDispatch(
|
|
|
3105
3101
|
? { intent: undefined, strippedArgs: argsForExecution }
|
|
3106
3102
|
: extractIntent(toolCall.arguments);
|
|
3107
3103
|
argsForExecution = strippedArgs;
|
|
3108
|
-
if (intent !== undefined && intent.length > MAX_INTENT_LENGTH && tool) {
|
|
3109
|
-
entry.args = strippedArgs;
|
|
3110
|
-
entry.validationErrorMessage = `\`${INTENT_FIELD}\` is a short intent label (at most ${MAX_INTENT_LENGTH} chars); the value you sent is ${intent.length} chars. The tool was not run. Put that content in the tool's own parameters and retry with a brief \`${INTENT_FIELD}\`.`;
|
|
3111
|
-
continue;
|
|
3112
|
-
}
|
|
3113
3104
|
if (intent) {
|
|
3114
3105
|
toolCall.intent = intent;
|
|
3115
3106
|
} else if (typeof tool?.intent === "function") {
|
|
@@ -3411,7 +3402,7 @@ async function executeToolCalls(
|
|
|
3411
3402
|
// also cascades: the freshly detached job's own completion re-triggers
|
|
3412
3403
|
// this check for the next command, so millisecond-long commands chain
|
|
3413
3404
|
// into separate background deliveries (#12869).
|
|
3414
|
-
if (source === "irc" && softInterrupts) steeringSoftController.abort();
|
|
3405
|
+
if (source === "irc" && softInterrupts) steeringSoftController.abort("irc");
|
|
3415
3406
|
};
|
|
3416
3407
|
|
|
3417
3408
|
const checkSteering = async (): Promise<void> => {
|
|
@@ -3450,7 +3441,7 @@ async function executeToolCalls(
|
|
|
3450
3441
|
interruptState.triggered = true;
|
|
3451
3442
|
interruptState.source = steeringSource ?? "unknown";
|
|
3452
3443
|
steeringAbortController.abort(TOOL_INTERRUPT_ABORT_REASON);
|
|
3453
|
-
if (softInterrupts) steeringSoftController.abort();
|
|
3444
|
+
if (softInterrupts) steeringSoftController.abort(interruptState.source);
|
|
3454
3445
|
}
|
|
3455
3446
|
return;
|
|
3456
3447
|
}
|
package/src/agent.ts
CHANGED
|
@@ -959,12 +959,11 @@ export class Agent {
|
|
|
959
959
|
pruneDescriptions: this.#pruneToolDescriptions,
|
|
960
960
|
}) ?? []);
|
|
961
961
|
let context: Context = { systemPrompt, messages, tools };
|
|
962
|
+
// Side requests reuse the main loop's sent definitions without recording their own; attached before the
|
|
963
|
+
// transform too, so a request-size clamp in it measures them.
|
|
964
|
+
context = this.#sentToolDefinitions.withInactiveTools(context);
|
|
962
965
|
if (this.#transformProviderContext) context = await this.#transformProviderContext(context, model);
|
|
963
|
-
|
|
964
|
-
if (context.tools?.length) {
|
|
965
|
-
const inactiveTools = this.#sentToolDefinitions.inactiveFor(context.messages, context.tools);
|
|
966
|
-
if (inactiveTools) context = { ...context, inactiveTools };
|
|
967
|
-
}
|
|
966
|
+
context = this.#sentToolDefinitions.withInactiveTools(context);
|
|
968
967
|
return context;
|
|
969
968
|
}
|
|
970
969
|
|
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
createFileOps,
|
|
29
29
|
extractFileOpsFromMessage,
|
|
30
30
|
type FileOperations,
|
|
31
|
+
incompleteOutputError,
|
|
31
32
|
SUMMARIZATION_SYSTEM_PROMPT,
|
|
32
33
|
serializeConversationForSummary,
|
|
33
34
|
stripReadSelector,
|
|
@@ -386,6 +387,9 @@ export async function generateBranchSummary(
|
|
|
386
387
|
if (response.stopReason === "error") {
|
|
387
388
|
return { error: response.errorMessage || "Summarization failed" };
|
|
388
389
|
}
|
|
390
|
+
if (response.stopReason === "length") {
|
|
391
|
+
return { error: incompleteOutputError("Summarization failed") };
|
|
392
|
+
}
|
|
389
393
|
|
|
390
394
|
let summary = response.content
|
|
391
395
|
.filter((c): c is { type: "text"; text: string } => c.type === "text")
|
|
@@ -274,6 +274,7 @@ export async function requestCompactionV2Streaming(
|
|
|
274
274
|
providerSessionState?: Map<string, ProviderSessionState>;
|
|
275
275
|
codexCompaction?: CodexCompactionContext;
|
|
276
276
|
preferWebsockets?: boolean;
|
|
277
|
+
codexAccessPrograms?: boolean;
|
|
277
278
|
},
|
|
278
279
|
): Promise<CompactionV2Response> {
|
|
279
280
|
let fetchImpl: FetchImpl = options?.fetch ?? globalThis.fetch;
|
|
@@ -316,6 +317,7 @@ export async function requestCompactionV2Streaming(
|
|
|
316
317
|
providerSessionState: options?.providerSessionState,
|
|
317
318
|
codexCompaction: options?.codexCompaction,
|
|
318
319
|
preferWebsockets: options?.preferWebsockets,
|
|
320
|
+
codexAccessPrograms: options?.codexAccessPrograms,
|
|
319
321
|
});
|
|
320
322
|
} catch (err) {
|
|
321
323
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
@@ -352,6 +354,7 @@ async function attemptCompactionV2Streaming(
|
|
|
352
354
|
providerSessionState?: Map<string, ProviderSessionState>;
|
|
353
355
|
codexCompaction?: CodexCompactionContext;
|
|
354
356
|
preferWebsockets?: boolean;
|
|
357
|
+
codexAccessPrograms?: boolean;
|
|
355
358
|
},
|
|
356
359
|
): Promise<CompactionV2Response> {
|
|
357
360
|
// Faithful to Codex: append the compaction trigger as the final input item
|
|
@@ -377,6 +380,7 @@ async function attemptCompactionV2Streaming(
|
|
|
377
380
|
sessionId: request.sessionId,
|
|
378
381
|
providerSessionState: options.providerSessionState,
|
|
379
382
|
preferWebsockets: options.preferWebsockets,
|
|
383
|
+
codexAccessPrograms: options.codexAccessPrograms,
|
|
380
384
|
responsesLite: model.useResponsesLite,
|
|
381
385
|
codexCompaction: createOpenAICodexCompactionRequestContext({
|
|
382
386
|
context: options.codexCompaction,
|
|
@@ -91,6 +91,7 @@ import {
|
|
|
91
91
|
escapeSummaryBoundaryTags,
|
|
92
92
|
extractFileOpsFromMessage,
|
|
93
93
|
type FileOperations,
|
|
94
|
+
incompleteOutputError,
|
|
94
95
|
SUMMARIZATION_SYSTEM_PROMPT,
|
|
95
96
|
serializeConversationForSummary,
|
|
96
97
|
stripReadSelector,
|
|
@@ -750,6 +751,8 @@ export interface SummaryOptions {
|
|
|
750
751
|
providerSessionState?: Map<string, ProviderSessionState>;
|
|
751
752
|
/** Whether Codex remote compaction should prefer the provider WebSocket transport. */
|
|
752
753
|
preferWebsockets?: boolean;
|
|
754
|
+
/** Daybreak Blue request on Codex remote compaction; see `StreamOptions.codexAccessPrograms`. */
|
|
755
|
+
codexAccessPrograms?: boolean;
|
|
753
756
|
/** Classification shared by every provider request in this logical compaction. */
|
|
754
757
|
codexCompaction?: CodexCompactionContext;
|
|
755
758
|
/** Provider-visible tools for remote compaction transports that replay native tool history. */
|
|
@@ -1073,6 +1076,9 @@ async function summarizeConversationWindow(
|
|
|
1073
1076
|
if (response.stopReason === "error") {
|
|
1074
1077
|
throw createSummarizationError("Summarization failed", response);
|
|
1075
1078
|
}
|
|
1079
|
+
if (response.stopReason === "length") {
|
|
1080
|
+
throw new Error(incompleteOutputError("Summarization failed"));
|
|
1081
|
+
}
|
|
1076
1082
|
|
|
1077
1083
|
const textContent = response.content
|
|
1078
1084
|
.filter((c): c is { type: "text"; text: string } => c.type === "text")
|
|
@@ -1180,6 +1186,9 @@ export async function generateHandoffFromContext(
|
|
|
1180
1186
|
if (response.stopReason === "error") {
|
|
1181
1187
|
throw createSummarizationError("Handoff generation failed", response);
|
|
1182
1188
|
}
|
|
1189
|
+
if (response.stopReason === "length") {
|
|
1190
|
+
throw new Error(incompleteOutputError("Handoff generation failed"));
|
|
1191
|
+
}
|
|
1183
1192
|
|
|
1184
1193
|
return response.content
|
|
1185
1194
|
.filter((c): c is { type: "text"; text: string } => c.type === "text")
|
|
@@ -1303,8 +1312,10 @@ async function generateShortSummary(
|
|
|
1303
1312
|
case "error":
|
|
1304
1313
|
outcome = { kind: "failed", error: response.errorMessage || "Unknown error" };
|
|
1305
1314
|
break;
|
|
1306
|
-
case "stop":
|
|
1307
1315
|
case "length":
|
|
1316
|
+
outcome = { kind: "failed", error: incompleteOutputError("Short summary failed") };
|
|
1317
|
+
break;
|
|
1318
|
+
case "stop":
|
|
1308
1319
|
case "toolUse":
|
|
1309
1320
|
outcome = {
|
|
1310
1321
|
kind: "text",
|
|
@@ -1765,6 +1776,7 @@ export async function compact(
|
|
|
1765
1776
|
: options?.promptCacheKey,
|
|
1766
1777
|
providerSessionState: options?.providerSessionState,
|
|
1767
1778
|
preferWebsockets: options?.preferWebsockets,
|
|
1779
|
+
codexAccessPrograms: options?.codexAccessPrograms,
|
|
1768
1780
|
codexCompaction: options?.codexCompaction,
|
|
1769
1781
|
tools: options?.tools,
|
|
1770
1782
|
fetch: options?.fetch,
|
|
@@ -2000,6 +2012,7 @@ export async function compact(
|
|
|
2000
2012
|
fetch: summaryOptions.fetch,
|
|
2001
2013
|
providerSessionState: summaryOptions.providerSessionState,
|
|
2002
2014
|
preferWebsockets: summaryOptions.preferWebsockets,
|
|
2015
|
+
codexAccessPrograms: summaryOptions.codexAccessPrograms,
|
|
2003
2016
|
codexCompaction: summaryOptions.codexCompaction,
|
|
2004
2017
|
}),
|
|
2005
2018
|
{ signal },
|
|
@@ -2431,6 +2444,9 @@ async function generateTurnPrefixSummary(
|
|
|
2431
2444
|
if (response.stopReason === "error") {
|
|
2432
2445
|
throw createSummarizationError("Turn prefix summarization failed", response);
|
|
2433
2446
|
}
|
|
2447
|
+
if (response.stopReason === "length") {
|
|
2448
|
+
throw new Error(incompleteOutputError("Turn prefix summarization failed"));
|
|
2449
|
+
}
|
|
2434
2450
|
|
|
2435
2451
|
return response.content
|
|
2436
2452
|
.filter((c): c is { type: "text"; text: string } => c.type === "text")
|
package/src/compaction/utils.ts
CHANGED
|
@@ -28,6 +28,11 @@ export function createFileOps(): FileOperations {
|
|
|
28
28
|
};
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
/** Error text for a summarization call that hit its output limit (`stopReason: "length"`): partial text is never used. */
|
|
32
|
+
export function incompleteOutputError(prefix: string): string {
|
|
33
|
+
return `${prefix}: model output ended incomplete (stopReason: length)`;
|
|
34
|
+
}
|
|
35
|
+
|
|
31
36
|
// Read-tool selector grammar, mirrored from the conservative filesystem splitter in
|
|
32
37
|
// packages/coding-agent/src/tools/path-utils.ts (splitPathAndSel). Keep in sync.
|
|
33
38
|
// A trailing `:chunk` is a selector only when it is a line-range list
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Message, Tool } from "@oh-my-pi/pi-ai";
|
|
1
|
+
import type { Context, Message, Tool } from "@oh-my-pi/pi-ai";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Last wire definition this Agent sent for each tool name, so a provider that keeps
|
|
@@ -14,11 +14,18 @@ export class SentToolDefinitions {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
|
-
*
|
|
18
|
-
* that are not in `
|
|
19
|
-
*
|
|
17
|
+
* `context` with {@link Context.inactiveTools} set to the definitions for names the latest
|
|
18
|
+
* `requestControls.tools.declared` in its messages holds that are not in its `tools`; cleared when
|
|
19
|
+
* there are none or the request declares no tools. Names never sent by this Agent are skipped: the
|
|
20
|
+
* provider drops them from the declaration.
|
|
20
21
|
*/
|
|
21
|
-
|
|
22
|
+
withInactiveTools(context: Context): Context {
|
|
23
|
+
const inactiveTools = context.tools?.length ? this.#inactiveFor(context.messages, context.tools) : undefined;
|
|
24
|
+
if (!inactiveTools && !context.inactiveTools) return context;
|
|
25
|
+
return { ...context, inactiveTools };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
#inactiveFor(messages: readonly Message[], active: readonly Tool[]): Tool[] | undefined {
|
|
22
29
|
let declared: readonly string[] | undefined;
|
|
23
30
|
for (let index = messages.length - 1; index >= 0; index--) {
|
|
24
31
|
const message = messages[index];
|
package/src/types.ts
CHANGED
|
@@ -672,7 +672,8 @@ export interface ToolCallContext {
|
|
|
672
672
|
providerMetadata?: ToolCallProviderMetadata;
|
|
673
673
|
/**
|
|
674
674
|
* Cooperative steering signal: aborted when a queued user/steering message
|
|
675
|
-
* (or an interrupting peer IRC) is detected while this tool batch runs.
|
|
675
|
+
* (or an interrupting peer IRC) is detected while this tool batch runs. Its
|
|
676
|
+
* `reason` names the source: a {@link SteeringInterruptSource} or `"irc"`.
|
|
676
677
|
* Unlike the hard abort signal it NEVER kills the tool — long-running
|
|
677
678
|
* tools MAY observe it (via `ctx.toolCall.steeringSignal`) to finish early
|
|
678
679
|
* or background themselves so the message injects promptly; ignoring it is
|