@oh-my-pi/pi-agent-core 16.0.4 → 16.0.6
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 +24 -0
- package/README.md +2 -2
- package/dist/types/agent.d.ts +18 -5
- package/dist/types/index.d.ts +1 -0
- package/dist/types/tokenizer.d.ts +1 -0
- package/dist/types/types.d.ts +20 -5
- package/package.json +6 -6
- package/src/agent-loop.ts +310 -417
- package/src/agent.ts +26 -5
- package/src/compaction/compaction.ts +1 -1
- package/src/compaction/openai.ts +1 -1
- package/src/compaction/shake.ts +1 -1
- package/src/index.ts +2 -0
- package/src/proxy.ts +12 -0
- package/src/tokenizer.ts +17 -0
- package/src/types.ts +22 -5
package/src/agent.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { isPromise } from "node:util/types";
|
|
5
5
|
import {
|
|
6
|
-
type
|
|
6
|
+
type ApiKey,
|
|
7
7
|
type AssistantMessage,
|
|
8
8
|
type AssistantMessageEvent,
|
|
9
9
|
type Context,
|
|
@@ -131,6 +131,8 @@ export interface AgentOptions {
|
|
|
131
131
|
* Custom stream function (for proxy backends, etc.). Default uses streamSimple.
|
|
132
132
|
*/
|
|
133
133
|
streamFn?: StreamFn;
|
|
134
|
+
/** Absolute wall-clock deadline in Unix epoch milliseconds. */
|
|
135
|
+
deadline?: number;
|
|
134
136
|
|
|
135
137
|
/**
|
|
136
138
|
* Optional session identifier forwarded to LLM providers.
|
|
@@ -148,10 +150,10 @@ export interface AgentOptions {
|
|
|
148
150
|
providerSessionState?: Map<string, ProviderSessionState>;
|
|
149
151
|
|
|
150
152
|
/**
|
|
151
|
-
* Resolves an API key dynamically for each LLM call.
|
|
152
|
-
* Useful for expiring tokens
|
|
153
|
+
* Resolves an API key or resolver dynamically for each LLM call.
|
|
154
|
+
* Useful for expiring tokens and model-scoped credential routing.
|
|
153
155
|
*/
|
|
154
|
-
getApiKey?: (
|
|
156
|
+
getApiKey?: (model: Model) => Promise<ApiKey | undefined> | ApiKey | undefined;
|
|
155
157
|
|
|
156
158
|
/**
|
|
157
159
|
* Inspect or replace provider payloads before they are sent.
|
|
@@ -255,6 +257,13 @@ export interface AgentOptions {
|
|
|
255
257
|
*/
|
|
256
258
|
afterToolCall?: AgentLoopConfig["afterToolCall"];
|
|
257
259
|
|
|
260
|
+
/**
|
|
261
|
+
* Called once an assistant message is finalized, before it reaches the
|
|
262
|
+
* context, the UI, or tool dispatch. May mutate the message in place (text +
|
|
263
|
+
* tool-call arguments). See {@link AgentLoopConfig.transformAssistantMessage}.
|
|
264
|
+
*/
|
|
265
|
+
transformAssistantMessage?: AgentLoopConfig["transformAssistantMessage"];
|
|
266
|
+
|
|
258
267
|
/**
|
|
259
268
|
* Opt-in OpenTelemetry instrumentation. Passing `{}` enables the loop's
|
|
260
269
|
* GenAI-semantic-convention spans using the global tracer provider. See
|
|
@@ -303,6 +312,7 @@ export class Agent {
|
|
|
303
312
|
#followUpMode: "all" | "one-at-a-time";
|
|
304
313
|
#interruptMode: "immediate" | "wait";
|
|
305
314
|
#sessionId?: string;
|
|
315
|
+
#deadline?: number;
|
|
306
316
|
#promptCacheKey?: string;
|
|
307
317
|
#metadata?: Record<string, unknown>;
|
|
308
318
|
#metadataResolver?: (provider: string) => Record<string, unknown> | undefined;
|
|
@@ -344,7 +354,7 @@ export class Agent {
|
|
|
344
354
|
#cursorToolResultBuffer: CursorToolResultEntry[] = [];
|
|
345
355
|
|
|
346
356
|
streamFn: StreamFn;
|
|
347
|
-
getApiKey?: (
|
|
357
|
+
getApiKey?: (model: Model) => Promise<ApiKey | undefined> | ApiKey | undefined;
|
|
348
358
|
/**
|
|
349
359
|
* Hook invoked after tool arguments are validated and before execution.
|
|
350
360
|
* Reassign at any time to swap the implementation (e.g. on extension reload).
|
|
@@ -355,6 +365,11 @@ export class Agent {
|
|
|
355
365
|
* message emission. Reassign at any time to swap the implementation.
|
|
356
366
|
*/
|
|
357
367
|
afterToolCall?: AgentLoopConfig["afterToolCall"];
|
|
368
|
+
/**
|
|
369
|
+
* Hook invoked once an assistant message is finalized, before context append,
|
|
370
|
+
* UI emission, and tool dispatch. Reassign at any time to swap the implementation.
|
|
371
|
+
*/
|
|
372
|
+
transformAssistantMessage?: AgentLoopConfig["transformAssistantMessage"];
|
|
358
373
|
|
|
359
374
|
constructor(opts: AgentOptions = {}) {
|
|
360
375
|
this.#state = { ...this.#state, ...opts.initialState };
|
|
@@ -368,6 +383,7 @@ export class Agent {
|
|
|
368
383
|
this.#interruptMode = opts.interruptMode || "immediate";
|
|
369
384
|
this.streamFn = opts.streamFn || streamSimple;
|
|
370
385
|
this.#sessionId = opts.sessionId;
|
|
386
|
+
this.#deadline = opts.deadline;
|
|
371
387
|
this.#promptCacheKey = opts.promptCacheKey;
|
|
372
388
|
this.#providerSessionState = opts.providerSessionState;
|
|
373
389
|
this.#thinkingBudgets = opts.thinkingBudgets;
|
|
@@ -398,6 +414,7 @@ export class Agent {
|
|
|
398
414
|
this.#onHarmonyLeak = opts.onHarmonyLeak;
|
|
399
415
|
this.beforeToolCall = opts.beforeToolCall;
|
|
400
416
|
this.afterToolCall = opts.afterToolCall;
|
|
417
|
+
this.transformAssistantMessage = opts.transformAssistantMessage;
|
|
401
418
|
this.#telemetry = opts.telemetry;
|
|
402
419
|
this.#appendOnlyContext = opts.appendOnlyContext;
|
|
403
420
|
this.#transformProviderContext = opts.transformProviderContext;
|
|
@@ -1014,6 +1031,7 @@ export class Agent {
|
|
|
1014
1031
|
hideThinkingSummary: this.#hideThinkingSummary,
|
|
1015
1032
|
interruptMode: this.#interruptMode,
|
|
1016
1033
|
sessionId: this.#sessionId,
|
|
1034
|
+
deadline: this.#deadline,
|
|
1017
1035
|
promptCacheKey: this.#promptCacheKey,
|
|
1018
1036
|
metadata: this.#metadataResolver ? undefined : this.#metadata,
|
|
1019
1037
|
metadataResolver: this.#metadataResolver,
|
|
@@ -1046,6 +1064,9 @@ export class Agent {
|
|
|
1046
1064
|
appendOnlyContext: this.#appendOnlyContext,
|
|
1047
1065
|
beforeToolCall: this.beforeToolCall ? (ctx, signal) => this.beforeToolCall?.(ctx, signal) : undefined,
|
|
1048
1066
|
afterToolCall: this.afterToolCall ? (ctx, signal) => this.afterToolCall?.(ctx, signal) : undefined,
|
|
1067
|
+
transformAssistantMessage: this.transformAssistantMessage
|
|
1068
|
+
? (message, signal) => this.transformAssistantMessage?.(message, signal)
|
|
1069
|
+
: undefined,
|
|
1049
1070
|
onAssistantMessageEvent: this.#onAssistantMessageEvent,
|
|
1050
1071
|
onHarmonyLeak: this.#onHarmonyLeak,
|
|
1051
1072
|
onTurnEnd: (messages, signal) => this.#onTurnEnd?.(messages, signal),
|
|
@@ -20,11 +20,11 @@ import {
|
|
|
20
20
|
} from "@oh-my-pi/pi-ai";
|
|
21
21
|
import { preferredDialect } from "@oh-my-pi/pi-catalog/identity";
|
|
22
22
|
import { clampThinkingLevelForModel } from "@oh-my-pi/pi-catalog/model-thinking";
|
|
23
|
-
import { countTokens } from "@oh-my-pi/pi-natives";
|
|
24
23
|
import { logger, prompt } from "@oh-my-pi/pi-utils";
|
|
25
24
|
import * as snapcompact from "@oh-my-pi/snapcompact";
|
|
26
25
|
import { type AgentTelemetry, instrumentedCompleteSimple } from "../telemetry";
|
|
27
26
|
import { ThinkingLevel } from "../thinking";
|
|
27
|
+
import { countTokens } from "../tokenizer";
|
|
28
28
|
import type { AgentMessage } from "../types";
|
|
29
29
|
import type { CompactionEntry, SessionEntry } from "./entries";
|
|
30
30
|
import { type ConvertToLlm, createBranchSummaryMessage, createCustomMessage, defaultConvertToLlm } from "./messages";
|
package/src/compaction/openai.ts
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
import { ProviderHttpError } from "@oh-my-pi/pi-ai/errors";
|
|
16
|
-
import { parseTextSignature } from "@oh-my-pi/pi-ai/providers/openai-
|
|
16
|
+
import { 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 { AssistantMessage, FetchImpl, Message, Model } from "@oh-my-pi/pi-ai/types";
|
|
19
19
|
import {
|
package/src/compaction/shake.ts
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import type { TextContent, ToolResultMessage } from "@oh-my-pi/pi-ai";
|
|
14
|
-
import { countTokens } from "
|
|
14
|
+
import { countTokens } from "../tokenizer";
|
|
15
15
|
import type { AgentMessage } from "../types";
|
|
16
16
|
import { estimateTokens } from "./compaction";
|
|
17
17
|
import type { CustomMessageEntry, SessionEntry, SessionMessageEntry } from "./entries";
|
package/src/index.ts
CHANGED
|
@@ -14,6 +14,8 @@ export * from "./run-collector";
|
|
|
14
14
|
export * from "./telemetry";
|
|
15
15
|
// Thinking selectors
|
|
16
16
|
export * from "./thinking";
|
|
17
|
+
// Tokenizer choice
|
|
18
|
+
export * from "./tokenizer";
|
|
17
19
|
// Types
|
|
18
20
|
export * from "./types";
|
|
19
21
|
// Yield utilities for Bun event-loop busy-wait prevention
|
package/src/proxy.ts
CHANGED
|
@@ -210,6 +210,18 @@ function processProxyEvent(
|
|
|
210
210
|
): AssistantMessageEvent | undefined {
|
|
211
211
|
switch (proxyEvent.type) {
|
|
212
212
|
case "start":
|
|
213
|
+
partial.content.length = 0;
|
|
214
|
+
partial.usage = {
|
|
215
|
+
input: 0,
|
|
216
|
+
output: 0,
|
|
217
|
+
cacheRead: 0,
|
|
218
|
+
cacheWrite: 0,
|
|
219
|
+
totalTokens: 0,
|
|
220
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
221
|
+
};
|
|
222
|
+
delete (partial as { stopReason?: string }).stopReason;
|
|
223
|
+
delete (partial as { errorMessage?: string }).errorMessage;
|
|
224
|
+
delete (partial as { duration?: number }).duration;
|
|
213
225
|
return { type: "start", partial };
|
|
214
226
|
|
|
215
227
|
case "text_start":
|
package/src/tokenizer.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { countTokens as countTokensNat } from "@oh-my-pi/pi-natives";
|
|
2
|
+
|
|
3
|
+
const accurate = process.env.PI_TOKENIZER_ACCURATE === "1" && Bun.env.NODE_ENV !== "test";
|
|
4
|
+
|
|
5
|
+
function estimateTokens(text: string) {
|
|
6
|
+
return (Buffer.byteLength(text, "utf-8") + 3) >> 2;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function countTokens(text: string | string[]): number {
|
|
10
|
+
if (accurate) {
|
|
11
|
+
return countTokensNat(text);
|
|
12
|
+
} else if (Array.isArray(text)) {
|
|
13
|
+
return text.reduce((sum, t) => sum + estimateTokens(t), 0);
|
|
14
|
+
} else {
|
|
15
|
+
return estimateTokens(text);
|
|
16
|
+
}
|
|
17
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type {
|
|
2
|
-
|
|
2
|
+
ApiKey,
|
|
3
3
|
AssistantMessage,
|
|
4
4
|
AssistantMessageEvent,
|
|
5
5
|
AssistantMessageEventStream,
|
|
@@ -55,6 +55,9 @@ export interface AgentLoopConfig extends SimpleStreamOptions {
|
|
|
55
55
|
*/
|
|
56
56
|
sessionId?: string;
|
|
57
57
|
|
|
58
|
+
/** Absolute wall-clock deadline in Unix epoch milliseconds. */
|
|
59
|
+
deadline?: number;
|
|
60
|
+
|
|
58
61
|
/**
|
|
59
62
|
* Optional resolver called per LLM request to produce request metadata.
|
|
60
63
|
* When set, the agent loop evaluates it **after** `getApiKey` resolves the
|
|
@@ -117,12 +120,12 @@ export interface AgentLoopConfig extends SimpleStreamOptions {
|
|
|
117
120
|
transformProviderContext?: (context: Context, model: Model) => Context;
|
|
118
121
|
|
|
119
122
|
/**
|
|
120
|
-
* Resolves
|
|
123
|
+
* Resolves the API key or resolver for the current model before each LLM call.
|
|
121
124
|
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
125
|
+
* Returning an ApiKeyResolver lets the stream retry policy refresh or rotate
|
|
126
|
+
* the model-scoped credential after auth/usage-limit errors.
|
|
124
127
|
*/
|
|
125
|
-
getApiKey?: (
|
|
128
|
+
getApiKey?: (model: Model) => Promise<ApiKey | undefined> | ApiKey | undefined;
|
|
126
129
|
|
|
127
130
|
/**
|
|
128
131
|
* Returns steering messages to inject into the conversation mid-run.
|
|
@@ -290,6 +293,20 @@ export interface AgentLoopConfig extends SimpleStreamOptions {
|
|
|
290
293
|
*/
|
|
291
294
|
onTurnEnd?: (messages: AgentMessage[], signal?: AbortSignal) => Promise<void> | void;
|
|
292
295
|
|
|
296
|
+
/**
|
|
297
|
+
* Called once an assistant message is finalized from the model stream, before
|
|
298
|
+
* it is appended to the context, emitted as `message_end`, or its tool calls
|
|
299
|
+
* are validated and dispatched. The hook may mutate the message in place —
|
|
300
|
+
* both its text content and its tool-call arguments — and those edits are seen
|
|
301
|
+
* by the transcript, the UI, and tool execution alike (single source of truth).
|
|
302
|
+
*
|
|
303
|
+
* Used for inline macro expansion: rewriting `@[[runtime.name(args)]]` tokens
|
|
304
|
+
* to host-computed values before anything downstream consumes the message.
|
|
305
|
+
* Runs at most once per assistant message; must not throw (a throw would abort
|
|
306
|
+
* the turn).
|
|
307
|
+
*/
|
|
308
|
+
transformAssistantMessage?: (message: AssistantMessage, signal?: AbortSignal) => Promise<void> | void;
|
|
309
|
+
|
|
293
310
|
/**
|
|
294
311
|
* Called after a tool finishes executing, before `tool_execution_end` and the
|
|
295
312
|
* tool-result message are emitted.
|