@linxiraos/pi-agent-core 1.1.13 → 1.1.15

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/src/tokenizer.ts CHANGED
@@ -219,14 +219,22 @@ export class Tokenizer {
219
219
  }
220
220
 
221
221
  switch (message.role) {
222
- case "user": {
223
- const content: string | Array<{ type: string; text?: string }> = message.content;
222
+ case "user":
223
+ case "developer": {
224
+ // Both roles carry `string | (TextContent | ImageContent)[]` and both are
225
+ // sent to the provider -- convertMessageToLlm handles developer alongside
226
+ // user -- so they are counted alike. Without the developer case the switch
227
+ // fell through to `default: return 0`, and the old annotation narrowed the
228
+ // blocks to text-only, hiding the image charge the toolResult arm applies.
229
+ const content = message.content;
224
230
  if (typeof content === "string") {
225
231
  fragments.push(content);
226
232
  } else if (Array.isArray(content)) {
227
233
  for (const block of content) {
228
234
  if (block.type === "text" && block.text) {
229
235
  fragments.push(block.text);
236
+ } else if (block.type === "image") {
237
+ extra += IMAGE_TOKEN_ESTIMATE;
230
238
  }
231
239
  }
232
240
  }
package/src/types.ts CHANGED
@@ -33,6 +33,8 @@ export type StreamFn = (
33
33
 
34
34
  /** Called once an aside has been inserted into the agent's live context. */
35
35
  export const ASIDE_MESSAGE_COMMIT = Symbol("aside-message-commit");
36
+ /** Symbol-keyed handoff for one finalized, tool-owned stream speculation session. */
37
+ export const SPECULATIVE_STREAM_SESSION = Symbol("speculative-stream-session");
36
38
  /** Called when an aside was drained but the agent loop ended before inserting it. */
37
39
  export const ASIDE_MESSAGE_DISCARD = Symbol("aside-message-discard");
38
40
 
@@ -338,13 +340,33 @@ export interface AgentLoopConfig extends SimpleStreamOptions {
338
340
  * Use for deobfuscating secrets or rewriting arguments.
339
341
  */
340
342
  transformToolCallArguments?: (args: Record<string, unknown>, toolName: string) => Record<string, unknown>;
343
+ /**
344
+ * Opt-in speculative execution for finalized, discard-safe tool calls.
345
+ *
346
+ * Candidates remain invisible until ordinary dispatch commits their result.
347
+ */
348
+ speculativeToolExecution?: SpeculativeToolExecutionConfig;
341
349
  /**
342
350
  * Resolve a tool call whose name matched no advertised tool (including
343
351
  * `customWireName` aliases). Lets hosts route calls to tools they expose
344
352
  * through side transports (e.g. `xd://` device mounts) instead of failing
345
353
  * with "Tool not found". Returning `undefined` keeps the failure.
354
+ *
355
+ * `advertised` is the very snapshot exact-name dispatch just searched — the
356
+ * set offered to THIS request. A host must resolve against it rather than
357
+ * its own live tool state: an MCP `tools/list_changed` mid-stream reassigns
358
+ * the agent's tools, so live state can hold a roster the model never saw,
359
+ * and a name-recovering host would dispatch a tool this request never
360
+ * advertised while exact dispatch still answered from the snapshot.
346
361
  */
347
- resolveFallbackTool?: (name: string) => AgentTool<any> | undefined;
362
+ resolveFallbackTool?: (name: string, advertised: readonly AgentTool<any>[]) => AgentTool<any> | undefined;
363
+ /**
364
+ * Names reachable through {@link resolveFallbackTool} but absent from the
365
+ * advertised set (e.g. `xd://` device mounts). Consulted only to name a
366
+ * plausible target when a call misses, so a mis-transcribed device call is
367
+ * recoverable; never a dispatch source.
368
+ */
369
+ suggestFallbackToolNames?: () => Iterable<string>;
348
370
 
349
371
  /**
350
372
  * Enable intent tracing for tool calls.
@@ -565,6 +587,192 @@ export interface ToolCallContext {
565
587
  /** A single tool-call content block emitted by an assistant message. */
566
588
  export type AgentToolCall = Extract<AssistantMessage["content"][number], { type: "toolCall" }>;
567
589
 
590
+ export interface SpeculativeResourceAccess {
591
+ scheme: "file";
592
+ path: string;
593
+ access: "read";
594
+ }
595
+
596
+ /** Declares an operation whose early execution can be discarded without rollback. */
597
+ export type ToolSpeculationEffect =
598
+ | { kind: "pure" }
599
+ | { kind: "local_read"; resources: readonly SpeculativeResourceAccess[] };
600
+
601
+ export type ToolSpeculationAssessment =
602
+ | { eligible: false; reason: string }
603
+ | { eligible: true; effect: ToolSpeculationEffect };
604
+
605
+ /** Immutable finalized call data provided to a tool-owned policy. */
606
+ export interface ToolSpeculationAssessmentContext {
607
+ toolCall: AgentToolCall;
608
+ args: Readonly<Record<string, unknown>>;
609
+ }
610
+
611
+ export interface ToolSpeculationExecutionContext extends ToolSpeculationAssessmentContext {
612
+ effect: ToolSpeculationEffect;
613
+ }
614
+
615
+ export interface ToolSpeculationCommitContext extends ToolSpeculationExecutionContext {
616
+ physicalOutcome: SpeculativePhysicalOutcome;
617
+ }
618
+
619
+ export interface ToolSpeculationDiscardContext extends ToolSpeculationExecutionContext {
620
+ reason: string;
621
+ }
622
+
623
+ export interface SpeculativePhysicalOutcome {
624
+ kind: "result";
625
+ result: AgentToolResult<unknown>;
626
+ isError: boolean;
627
+ /** Opaque evidence binding the result to the local bytes actually consumed. */
628
+ evidence?: unknown;
629
+ }
630
+
631
+ export interface SpeculativeToolReference {
632
+ name: string;
633
+ approval?: ToolApproval;
634
+ formatApprovalDetails?: (args: unknown) => string | string[] | undefined;
635
+ }
636
+
637
+ export interface SpeculativeOperationContext extends ToolSpeculationExecutionContext {
638
+ tool: SpeculativeToolReference;
639
+ candidateId: string;
640
+ source: "direct" | "eval_shadow";
641
+ dependencies: readonly string[];
642
+ }
643
+
644
+ export interface SpeculativeCommitContext extends SpeculativeOperationContext {
645
+ physicalOutcome: SpeculativePhysicalOutcome;
646
+ }
647
+
648
+ export interface SpeculativeDiscardContext extends SpeculativeOperationContext {
649
+ reason: string;
650
+ }
651
+
652
+ export type SpeculativeAuthorization =
653
+ | { allowed: false; reason: string }
654
+ | { allowed: true; deferBeforeToolCall?: boolean };
655
+
656
+ export type SpeculativeCommitDecision =
657
+ | { kind: "committed"; result: AgentToolResult<unknown> }
658
+ | { kind: "fallback"; reason: string }
659
+ | { kind: "failed"; error: unknown };
660
+
661
+ export interface SpeculativeExecutionHost {
662
+ authorize(context: SpeculativeOperationContext): SpeculativeAuthorization | Promise<SpeculativeAuthorization>;
663
+ /**
664
+ * Capture content evidence after admission gates pass but before the
665
+ * candidate executes. The coordinator invokes this immediately before
666
+ * starting speculative execution — which for hook-deferred candidates is
667
+ * after `beforeToolCall` runs — so content inspection never precedes a
668
+ * hook that may block the call. Return false (or throw) to veto the
669
+ * candidate without executing it. Hosts without this hook keep the legacy
670
+ * behavior of capturing during `authorize`.
671
+ */
672
+ captureEvidence?(context: SpeculativeOperationContext): boolean | Promise<boolean>;
673
+ validate?(context: SpeculativeCommitContext): boolean | Promise<boolean>;
674
+ commit?(
675
+ context: SpeculativeCommitContext,
676
+ commitDefault: () => Promise<AgentToolResult<unknown>>,
677
+ ): Promise<SpeculativeCommitDecision>;
678
+ discard?(context: SpeculativeDiscardContext): void | Promise<void>;
679
+ close?(reason: string): void | Promise<void>;
680
+ }
681
+
682
+ export interface ToolSpeculationStreamContext {
683
+ readonly coordinator: SpeculativeOperationSink;
684
+ readonly parentToolCallId: string;
685
+ }
686
+
687
+ /** One dependency-aware child operation projected by a streamed outer tool. */
688
+ export interface SpeculativeChildDefinition {
689
+ candidateId: string;
690
+ parentToolCallId: string;
691
+ dependencies: readonly string[];
692
+ toolCall: AgentToolCall;
693
+ tool: AgentTool;
694
+ source: "eval_shadow";
695
+ virtualDurationMs?: number;
696
+ }
697
+
698
+ /** Opaque ownership handle returned after agent-core validates and authorizes a child. */
699
+ export interface SpeculativeChildHandle {
700
+ readonly candidateId: string;
701
+ readonly fingerprint: string;
702
+ readonly effect: ToolSpeculationEffect;
703
+ readonly outcome: Promise<SpeculativePhysicalOutcome>;
704
+ commit(actualArgs: Readonly<Record<string, unknown>>): Promise<AgentToolResult<unknown> | undefined>;
705
+ discard(reason: string): Promise<void>;
706
+ }
707
+
708
+ export interface ToolSpeculationStreamSession {
709
+ /** True when the tool owns claim routing and does not need an AgentToolContext attachment. */
710
+ readonly contextIndependent?: boolean;
711
+ update(toolCall: AgentToolCall, partialJson?: string): void | Promise<void>;
712
+ finalize(context: ToolSpeculationAssessmentContext): void | Promise<void>;
713
+ commit(): void | Promise<void>;
714
+ discard(reason: string): void | Promise<void>;
715
+ /**
716
+ * Whether the session's streamed plan still authorizes these final
717
+ * arguments. The coordinator discards the session when the finalized call
718
+ * kept its ID but a hook or argument transform replaced its arguments:
719
+ * deferred work planned from the original code must never release.
720
+ * Sessions without this predicate are always retained.
721
+ */
722
+ matchesFinalArgs?(args: Readonly<Record<string, unknown>>): boolean;
723
+ }
724
+
725
+ export interface SpeculativeOperationSink {
726
+ readonly maxInFlight: number;
727
+ admit(definition: SpeculativeChildDefinition): Promise<SpeculativeChildHandle | undefined>;
728
+ discardChildren?(parentToolCallId: string, reason: string): void | Promise<void>;
729
+ close(reason: string): void | Promise<void>;
730
+ }
731
+
732
+ export interface ToolSpeculationPolicy {
733
+ finalized?: {
734
+ assess(context: ToolSpeculationAssessmentContext): ToolSpeculationAssessment | Promise<ToolSpeculationAssessment>;
735
+ execute(context: ToolSpeculationExecutionContext, signal: AbortSignal): Promise<SpeculativePhysicalOutcome>;
736
+ commit?(
737
+ context: ToolSpeculationCommitContext,
738
+ outcome: SpeculativePhysicalOutcome,
739
+ ): Promise<AgentToolResult<unknown>>;
740
+ discard?(context: ToolSpeculationDiscardContext): void | Promise<void>;
741
+ };
742
+ stream?: {
743
+ open(
744
+ context: ToolSpeculationStreamContext,
745
+ ): ToolSpeculationStreamSession | Promise<ToolSpeculationStreamSession | undefined>;
746
+ };
747
+ }
748
+
749
+ /** Diagnostic information for one speculative operation. */
750
+ export interface SpeculativeToolTelemetry {
751
+ source: "direct" | "eval_shadow";
752
+ candidateId: string;
753
+ parentToolCallId?: string;
754
+ toolName: string;
755
+ effectKind?: ToolSpeculationEffect["kind"];
756
+ candidateStartedAt?: number;
757
+ candidateFinishedAt?: number;
758
+ dispatchReachedAt?: number;
759
+ dependencyCount: number;
760
+ queueMs?: number;
761
+ executionDurationMs?: number;
762
+ overlapMs?: number;
763
+ outcome: "committed" | "discarded" | "ineligible" | "fingerprint_mismatch" | "aborted" | "commit_conflict";
764
+ reason?: string;
765
+ resourceCount: number;
766
+ }
767
+
768
+ /** Opt-in configuration for discard-safe speculative tool execution. */
769
+ export interface SpeculativeToolExecutionConfig {
770
+ enabled: boolean;
771
+ maxInFlight?: number;
772
+ host?: SpeculativeExecutionHost;
773
+ onTelemetry?: (event: SpeculativeToolTelemetry) => void;
774
+ }
775
+
568
776
  /**
569
777
  * Result returned from `beforeToolCall`.
570
778
  *
@@ -609,7 +817,7 @@ export interface BeforeToolCallContext {
609
817
  /** The raw tool call block from `assistantMessage.content`. */
610
818
  toolCall: AgentToolCall;
611
819
  /** The resolved tool the call dispatches to. */
612
- tool: AgentTool<any>;
820
+ tool: AgentTool;
613
821
  /**
614
822
  * Validated tool arguments. The same reference is forwarded to `tool.execute`
615
823
  * (after any `transformToolCallArguments` pass), so in-place mutations stick;
@@ -748,7 +956,8 @@ export type ToolApproval = ToolApprovalDecision | ((args: unknown) => ToolApprov
748
956
  * Apps can extend via declaration merging.
749
957
  */
750
958
  export interface AgentToolContext {
751
- // Empty by default - apps extend via declaration merging
959
+ /** Present only while the matching outer tool owns its finalized stream session. */
960
+ [SPECULATIVE_STREAM_SESSION]?: ToolSpeculationStreamSession;
752
961
  }
753
962
 
754
963
  export type AgentToolExecFn<TParameters extends TSchema = TSchema, TDetails = any, TTheme = unknown> = (
@@ -806,6 +1015,12 @@ export interface AgentTool<
806
1015
  * - function: resolved per call from the (raw, pre-validation) arguments
807
1016
  */
808
1017
  concurrency?: "shared" | "exclusive" | ((args: Partial<Static<TParameters>>) => "shared" | "exclusive");
1018
+
1019
+ /**
1020
+ * Declares the bounded, validated effect of a finalized call that may execute
1021
+ * before ordinary dispatch commits its result.
1022
+ */
1023
+ speculation?: ToolSpeculationPolicy;
809
1024
  /** If true, argument validation errors are non-fatal: raw args are passed to execute() instead of returning an error to the LLM. */
810
1025
  lenientArgValidation?: boolean;
811
1026
  /**