@f5-sales-demo/pi-agent-core 19.61.1 → 19.61.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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@f5-sales-demo/pi-agent-core",
4
- "version": "19.61.1",
4
+ "version": "19.61.3",
5
5
  "description": "General-purpose agent with transport abstraction, state management, and attachment support",
6
6
  "homepage": "https://github.com/f5-sales-demo/xcsh",
7
7
  "author": "Can Boluk",
@@ -35,8 +35,8 @@
35
35
  "fmt": "biome format --write ."
36
36
  },
37
37
  "dependencies": {
38
- "@f5-sales-demo/pi-ai": "19.61.1",
39
- "@f5-sales-demo/pi-utils": "19.61.1"
38
+ "@f5-sales-demo/pi-ai": "19.61.3",
39
+ "@f5-sales-demo/pi-utils": "19.61.3"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@sinclair/typebox": "^0.34",
package/src/agent-loop.ts CHANGED
@@ -10,6 +10,7 @@ import {
10
10
  type ToolResultMessage,
11
11
  validateToolArguments,
12
12
  } from "@f5-sales-demo/pi-ai";
13
+ import { logger } from "@f5-sales-demo/pi-utils";
13
14
  import type {
14
15
  AgentContext,
15
16
  AgentEvent,
@@ -218,7 +219,7 @@ async function runLoop(
218
219
 
219
220
  // Refresh prompt/tool context from live state before each model call
220
221
  if (config.syncContextBeforeModelCall) {
221
- await config.syncContextBeforeModelCall(currentContext);
222
+ await logger.ttftAttr("ttft.sync-context", () => config.syncContextBeforeModelCall!(currentContext));
222
223
  }
223
224
 
224
225
  // Stream assistant response
@@ -306,18 +307,18 @@ async function streamAssistantResponse(
306
307
  // Apply context transform if configured (AgentMessage[] → AgentMessage[])
307
308
  let messages = context.messages;
308
309
  if (config.transformContext) {
309
- messages = await config.transformContext(messages, signal);
310
+ messages = await logger.ttftAttr("ttft.transform-context", () => config.transformContext!(messages, signal));
310
311
  }
311
312
 
312
313
  // Convert to LLM-compatible messages (AgentMessage[] → Message[])
313
- const llmMessages = await config.convertToLlm(messages);
314
+ const llmMessages = await logger.ttftAttr("ttft.convert-to-llm", () => config.convertToLlm(messages));
314
315
  const normalizedMessages = normalizeMessagesForProvider(llmMessages, config.model);
315
316
 
316
317
  // Build LLM context
317
318
  const llmContext: Context = {
318
319
  systemPrompt: context.systemPrompt,
319
320
  messages: normalizedMessages,
320
- tools: normalizeTools(context.tools, !!config.intentTracing),
321
+ tools: logger.ttftAttr("ttft.normalize-tools", () => normalizeTools(context.tools, !!config.intentTracing)),
321
322
  };
322
323
 
323
324
  const streamFunction = streamFn || streamSimple;
@@ -327,17 +328,25 @@ async function streamAssistantResponse(
327
328
  (config.getApiKey ? await config.getApiKey(config.model.provider) : undefined) || config.apiKey;
328
329
 
329
330
  const dynamicToolChoice = config.getToolChoice?.();
330
- const response = await streamFunction(config.model, llmContext, {
331
- ...config,
332
- apiKey: resolvedApiKey,
333
- toolChoice: dynamicToolChoice ?? config.toolChoice,
334
- signal,
335
- });
331
+ const response = await logger.ttftAttr("ttft.stream-fn", () =>
332
+ streamFunction(config.model, llmContext, {
333
+ ...config,
334
+ apiKey: resolvedApiKey,
335
+ toolChoice: dynamicToolChoice ?? config.toolChoice,
336
+ signal,
337
+ }),
338
+ );
339
+ const tAfterStream = performance.now();
336
340
 
337
341
  let partialMessage: AssistantMessage | null = null;
338
342
  let addedPartial = false;
343
+ let firstDeltaMarked = false;
339
344
 
340
345
  for await (const event of response) {
346
+ if (!firstDeltaMarked && event.type === "text_delta") {
347
+ firstDeltaMarked = true;
348
+ logger.ttftMark("ttft.first-token-wait", performance.now() - tAfterStream);
349
+ }
341
350
  // Check for abort signal before processing each event
342
351
  if (signal?.aborted) {
343
352
  const errorMessage = "Request was aborted";
package/src/agent.ts CHANGED
@@ -20,6 +20,7 @@ import {
20
20
  type ToolChoice,
21
21
  type ToolResultMessage,
22
22
  } from "@f5-sales-demo/pi-ai";
23
+ import { logger } from "@f5-sales-demo/pi-utils";
23
24
  import { agentLoop, agentLoopContinue } from "./agent-loop";
24
25
  import type {
25
26
  AgentContext,
@@ -711,81 +712,85 @@ export class Agent {
711
712
 
712
713
  const reasoning = this.#state.thinkingLevel;
713
714
 
714
- const context: AgentContext = {
715
- systemPrompt: this.#state.systemPrompt,
716
- messages: this.#state.messages.slice(),
717
- tools: this.#state.tools,
718
- };
715
+ const { context, config } = logger.ttftAttr("ttft.runloop-setup", () => {
716
+ const context: AgentContext = {
717
+ systemPrompt: this.#state.systemPrompt,
718
+ messages: this.#state.messages.slice(),
719
+ tools: this.#state.tools,
720
+ };
719
721
 
720
- const cursorOnToolResult =
721
- this.#cursorExecHandlers || this.#cursorOnToolResult
722
- ? async (message: ToolResultMessage) => {
723
- let finalMessage = message;
724
- if (this.#cursorOnToolResult) {
725
- try {
726
- const updated = await this.#cursorOnToolResult(message);
727
- if (updated) {
728
- finalMessage = updated;
729
- }
730
- } catch {}
722
+ const cursorOnToolResult =
723
+ this.#cursorExecHandlers || this.#cursorOnToolResult
724
+ ? async (message: ToolResultMessage) => {
725
+ let finalMessage = message;
726
+ if (this.#cursorOnToolResult) {
727
+ try {
728
+ const updated = await this.#cursorOnToolResult(message);
729
+ if (updated) {
730
+ finalMessage = updated;
731
+ }
732
+ } catch {}
733
+ }
734
+ // Buffer tool result with current text length for correct ordering later.
735
+ // Cursor executes tools server-side during streaming, so the assistant message
736
+ // already incorporates results. We buffer here and emit in correct order
737
+ // when the assistant message ends.
738
+ const textLength = this.#getAssistantTextLength(this.#state.streamMessage);
739
+ this.#cursorToolResultBuffer.push({ toolResult: finalMessage, textLengthAtCall: textLength });
740
+ return finalMessage;
731
741
  }
732
- // Buffer tool result with current text length for correct ordering later.
733
- // Cursor executes tools server-side during streaming, so the assistant message
734
- // already incorporates results. We buffer here and emit in correct order
735
- // when the assistant message ends.
736
- const textLength = this.#getAssistantTextLength(this.#state.streamMessage);
737
- this.#cursorToolResultBuffer.push({ toolResult: finalMessage, textLengthAtCall: textLength });
738
- return finalMessage;
742
+ : undefined;
743
+
744
+ const getToolChoice = () =>
745
+ this.#getToolChoice?.() ?? refreshToolChoiceForActiveTools(options?.toolChoice, this.#state.tools);
746
+
747
+ const config: AgentLoopConfig = {
748
+ model,
749
+ reasoning,
750
+ temperature: this.#temperature,
751
+ topP: this.#topP,
752
+ topK: this.#topK,
753
+ minP: this.#minP,
754
+ presencePenalty: this.#presencePenalty,
755
+ repetitionPenalty: this.#repetitionPenalty,
756
+ serviceTier: this.#serviceTier,
757
+ interruptMode: this.#interruptMode,
758
+ sessionId: this.#sessionId,
759
+ providerSessionState: this.#providerSessionState,
760
+ thinkingBudgets: this.#thinkingBudgets,
761
+ maxRetryDelayMs: this.#maxRetryDelayMs,
762
+ kimiApiFormat: this.#kimiApiFormat,
763
+ preferWebsockets: this.#preferWebsockets,
764
+ convertToLlm: this.#convertToLlm,
765
+ transformContext: this.#transformContext,
766
+ onPayload: this.#onPayload,
767
+ getApiKey: this.getApiKey,
768
+ getToolContext: this.#getToolContext,
769
+ syncContextBeforeModelCall: async context => {
770
+ if (this.#listeners.size > 0) {
771
+ await Bun.sleep(0);
739
772
  }
740
- : undefined;
741
-
742
- const getToolChoice = () =>
743
- this.#getToolChoice?.() ?? refreshToolChoiceForActiveTools(options?.toolChoice, this.#state.tools);
744
-
745
- const config: AgentLoopConfig = {
746
- model,
747
- reasoning,
748
- temperature: this.#temperature,
749
- topP: this.#topP,
750
- topK: this.#topK,
751
- minP: this.#minP,
752
- presencePenalty: this.#presencePenalty,
753
- repetitionPenalty: this.#repetitionPenalty,
754
- serviceTier: this.#serviceTier,
755
- interruptMode: this.#interruptMode,
756
- sessionId: this.#sessionId,
757
- providerSessionState: this.#providerSessionState,
758
- thinkingBudgets: this.#thinkingBudgets,
759
- maxRetryDelayMs: this.#maxRetryDelayMs,
760
- kimiApiFormat: this.#kimiApiFormat,
761
- preferWebsockets: this.#preferWebsockets,
762
- convertToLlm: this.#convertToLlm,
763
- transformContext: this.#transformContext,
764
- onPayload: this.#onPayload,
765
- getApiKey: this.getApiKey,
766
- getToolContext: this.#getToolContext,
767
- syncContextBeforeModelCall: async context => {
768
- if (this.#listeners.size > 0) {
769
- await Bun.sleep(0);
770
- }
771
- context.systemPrompt = this.#state.systemPrompt;
772
- context.tools = this.#state.tools;
773
- },
774
- cursorExecHandlers: this.#cursorExecHandlers,
775
- cursorOnToolResult,
776
- transformToolCallArguments: this.#transformToolCallArguments,
777
- intentTracing: this.#intentTracing,
778
- onAssistantMessageEvent: this.#onAssistantMessageEvent,
779
- getToolChoice,
780
- getSteeringMessages: async () => {
781
- if (skipInitialSteeringPoll) {
782
- skipInitialSteeringPoll = false;
783
- return [];
784
- }
785
- return this.#dequeueSteeringMessages();
786
- },
787
- getFollowUpMessages: async () => this.#dequeueFollowUpMessages(),
788
- };
773
+ context.systemPrompt = this.#state.systemPrompt;
774
+ context.tools = this.#state.tools;
775
+ },
776
+ cursorExecHandlers: this.#cursorExecHandlers,
777
+ cursorOnToolResult,
778
+ transformToolCallArguments: this.#transformToolCallArguments,
779
+ intentTracing: this.#intentTracing,
780
+ onAssistantMessageEvent: this.#onAssistantMessageEvent,
781
+ getToolChoice,
782
+ getSteeringMessages: async () => {
783
+ if (skipInitialSteeringPoll) {
784
+ skipInitialSteeringPoll = false;
785
+ return [];
786
+ }
787
+ return this.#dequeueSteeringMessages();
788
+ },
789
+ getFollowUpMessages: async () => this.#dequeueFollowUpMessages(),
790
+ };
791
+
792
+ return { context, config };
793
+ });
789
794
 
790
795
  let partial: AgentMessage | null = null;
791
796