@fleetagent/pi-coding-agent 0.0.1 → 0.0.4

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.
Files changed (125) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/dist/core/agent-session.d.ts +32 -7
  3. package/dist/core/agent-session.d.ts.map +1 -1
  4. package/dist/core/agent-session.js +225 -12
  5. package/dist/core/agent-session.js.map +1 -1
  6. package/dist/core/compaction/branch-summarization.d.ts +2 -2
  7. package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  8. package/dist/core/compaction/branch-summarization.js.map +1 -1
  9. package/dist/core/compaction/compaction.d.ts +2 -2
  10. package/dist/core/compaction/compaction.d.ts.map +1 -1
  11. package/dist/core/compaction/compaction.js +1 -1
  12. package/dist/core/compaction/compaction.js.map +1 -1
  13. package/dist/core/extensions/runner.d.ts +3 -3
  14. package/dist/core/extensions/runner.d.ts.map +1 -1
  15. package/dist/core/extensions/runner.js +5 -5
  16. package/dist/core/extensions/runner.js.map +1 -1
  17. package/dist/core/extensions/types.d.ts +15 -8
  18. package/dist/core/extensions/types.d.ts.map +1 -1
  19. package/dist/core/extensions/types.js.map +1 -1
  20. package/dist/core/footer-data-provider.d.ts +1 -1
  21. package/dist/core/footer-data-provider.d.ts.map +1 -1
  22. package/dist/core/footer-data-provider.js +1 -1
  23. package/dist/core/footer-data-provider.js.map +1 -1
  24. package/dist/core/messages.d.ts +1 -0
  25. package/dist/core/messages.d.ts.map +1 -1
  26. package/dist/core/messages.js +4 -0
  27. package/dist/core/messages.js.map +1 -1
  28. package/dist/core/pi-agent.d.ts +5 -3
  29. package/dist/core/pi-agent.d.ts.map +1 -1
  30. package/dist/core/pi-agent.js +64 -18
  31. package/dist/core/pi-agent.js.map +1 -1
  32. package/dist/core/session/in-memory-session-manager.d.ts.map +1 -1
  33. package/dist/core/session/in-memory-session-manager.js +5 -7
  34. package/dist/core/session/in-memory-session-manager.js.map +1 -1
  35. package/dist/core/session/in-memory-session.d.ts +3 -1
  36. package/dist/core/session/in-memory-session.d.ts.map +1 -1
  37. package/dist/core/session/in-memory-session.js +5 -2
  38. package/dist/core/session/in-memory-session.js.map +1 -1
  39. package/dist/core/session/index.d.ts +2 -2
  40. package/dist/core/session/index.d.ts.map +1 -1
  41. package/dist/core/session/index.js.map +1 -1
  42. package/dist/core/session/jsonl-helpers.d.ts.map +1 -1
  43. package/dist/core/session/jsonl-helpers.js +4 -4
  44. package/dist/core/session/jsonl-helpers.js.map +1 -1
  45. package/dist/core/session/local-session-manager.d.ts.map +1 -1
  46. package/dist/core/session/local-session-manager.js +12 -11
  47. package/dist/core/session/local-session-manager.js.map +1 -1
  48. package/dist/core/session/local-session.d.ts +3 -1
  49. package/dist/core/session/local-session.d.ts.map +1 -1
  50. package/dist/core/session/local-session.js +7 -2
  51. package/dist/core/session/local-session.js.map +1 -1
  52. package/dist/core/session/remote-session-client.d.ts +6 -1
  53. package/dist/core/session/remote-session-client.d.ts.map +1 -1
  54. package/dist/core/session/remote-session-client.js.map +1 -1
  55. package/dist/core/session/remote-session-manager.d.ts.map +1 -1
  56. package/dist/core/session/remote-session-manager.js +28 -7
  57. package/dist/core/session/remote-session-manager.js.map +1 -1
  58. package/dist/core/session/remote-session.d.ts +3 -0
  59. package/dist/core/session/remote-session.d.ts.map +1 -1
  60. package/dist/core/session/remote-session.js +4 -1
  61. package/dist/core/session/remote-session.js.map +1 -1
  62. package/dist/core/session/session.d.ts +9 -3
  63. package/dist/core/session/session.d.ts.map +1 -1
  64. package/dist/core/session/session.js +64 -10
  65. package/dist/core/session/session.js.map +1 -1
  66. package/dist/core/session/stores/in-memory-session-store.d.ts +6 -14
  67. package/dist/core/session/stores/in-memory-session-store.d.ts.map +1 -1
  68. package/dist/core/session/stores/in-memory-session-store.js +8 -34
  69. package/dist/core/session/stores/in-memory-session-store.js.map +1 -1
  70. package/dist/core/session/stores/jsonl-session-store.d.ts +14 -14
  71. package/dist/core/session/stores/jsonl-session-store.d.ts.map +1 -1
  72. package/dist/core/session/stores/jsonl-session-store.js +153 -162
  73. package/dist/core/session/stores/jsonl-session-store.js.map +1 -1
  74. package/dist/core/session/stores/remote-session-store.d.ts +4 -6
  75. package/dist/core/session/stores/remote-session-store.d.ts.map +1 -1
  76. package/dist/core/session/stores/remote-session-store.js +18 -30
  77. package/dist/core/session/stores/remote-session-store.js.map +1 -1
  78. package/dist/core/session/stores/session-store.d.ts +1 -15
  79. package/dist/core/session/stores/session-store.d.ts.map +1 -1
  80. package/dist/core/session/stores/session-store.js.map +1 -1
  81. package/dist/core/session-cwd.d.ts +2 -2
  82. package/dist/core/session-cwd.d.ts.map +1 -1
  83. package/dist/core/session-cwd.js +5 -5
  84. package/dist/core/session-cwd.js.map +1 -1
  85. package/dist/index.d.ts +1 -1
  86. package/dist/index.d.ts.map +1 -1
  87. package/dist/index.js.map +1 -1
  88. package/dist/modes/interactive/interactive-mode.d.ts +1 -1
  89. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  90. package/dist/modes/interactive/interactive-mode.js +39 -37
  91. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  92. package/docs/extensions.md +35 -32
  93. package/docs/index.md +1 -1
  94. package/docs/sdk.md +2 -0
  95. package/docs/session-format.md +21 -21
  96. package/docs/sessions.md +2 -2
  97. package/docs/tui.md +1 -1
  98. package/examples/README.md +3 -0
  99. package/examples/extensions/README.md +1 -1
  100. package/examples/extensions/auto-commit-on-exit.ts +1 -1
  101. package/examples/extensions/bookmark.ts +3 -3
  102. package/examples/extensions/confirm-destructive.ts +1 -1
  103. package/examples/extensions/custom-compaction.ts +1 -1
  104. package/examples/extensions/custom-footer.ts +2 -2
  105. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  106. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  107. package/examples/extensions/git-checkpoint.ts +1 -1
  108. package/examples/extensions/handoff.ts +2 -2
  109. package/examples/extensions/plan-mode/index.ts +1 -1
  110. package/examples/extensions/preset.ts +1 -1
  111. package/examples/extensions/qna.ts +1 -1
  112. package/examples/extensions/sandbox/package.json +1 -1
  113. package/examples/extensions/snake.ts +1 -1
  114. package/examples/extensions/space-invaders.ts +1 -1
  115. package/examples/extensions/summarize.ts +1 -1
  116. package/examples/extensions/tic-tac-toe.ts +1 -1
  117. package/examples/extensions/todo.ts +1 -1
  118. package/examples/extensions/tools.ts +1 -1
  119. package/examples/extensions/with-deps/package.json +1 -1
  120. package/examples/remote-session-server/README.md +66 -0
  121. package/examples/remote-session-server/server.ts +359 -0
  122. package/examples/sdk/11-sessions.ts +3 -3
  123. package/examples/sdk/13-session-runtime.ts +6 -6
  124. package/npm-shrinkwrap.json +12 -12
  125. package/package.json +4 -4
@@ -8,13 +8,13 @@
8
8
  * - Model and thinking level management
9
9
  * - Compaction (manual and auto)
10
10
  * - Bash execution
11
- * - Session switching and branching
11
+ * - Tree navigation and branching
12
12
  *
13
13
  * Modes use this class and add their own I/O layer on top.
14
14
  */
15
15
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
16
16
  import { basename, dirname } from "node:path";
17
- import { clampThinkingLevel, cleanupSessionResources, getSupportedThinkingLevels, isContextOverflow, modelsAreEqual, resetApiProviders, streamSimple, } from "@fleetagent/pi-ai";
17
+ import { clampThinkingLevel, cleanupSessionResources, getSupportedThinkingLevels, isContextOverflow, modelsAreEqual, resetApiProviders, streamSimple, validateToolArguments, } from "@fleetagent/pi-ai";
18
18
  import { theme } from "../modes/interactive/theme/theme.js";
19
19
  import { stripFrontmatter } from "../utils/frontmatter.js";
20
20
  import { resolvePath } from "../utils/paths.js";
@@ -27,6 +27,7 @@ import { exportSessionToHtml } from "./export-html/index.js";
27
27
  import { createToolHtmlRenderer } from "./export-html/tool-renderer.js";
28
28
  import { ExtensionRunner, wrapRegisteredTools, } from "./extensions/index.js";
29
29
  import { emitSessionShutdownEvent } from "./extensions/runner.js";
30
+ import { STRUCTURED_RESPONSE_INTERNAL_CUSTOM_TYPE } from "./messages.js";
30
31
  import { expandPromptTemplate } from "./prompt-templates.js";
31
32
  import { CURRENT_SESSION_VERSION, getLatestCompactionEntry } from "./session-manager.js";
32
33
  import { createSyntheticSourceInfo } from "./source-info.js";
@@ -54,6 +55,41 @@ export function parseSkillBlock(text) {
54
55
  // ============================================================================
55
56
  /** Standard thinking levels */
56
57
  const THINKING_LEVELS = ["off", "minimal", "low", "medium", "high"];
58
+ const DEFAULT_STRUCTURED_RESPONSE_TOOL_NAME = "structured_output";
59
+ const DEFAULT_STRUCTURED_RESPONSE_CORRECTIONS = 2;
60
+ function isRecord(value) {
61
+ return typeof value === "object" && value !== null && !Array.isArray(value);
62
+ }
63
+ function getAssistantText(message) {
64
+ return message.content
65
+ .filter((block) => block.type === "text")
66
+ .map((block) => block.text)
67
+ .join("\n");
68
+ }
69
+ function extractJsonCandidates(text) {
70
+ const candidates = [];
71
+ const trimmed = text.trim();
72
+ if (trimmed) {
73
+ try {
74
+ candidates.push(JSON.parse(trimmed));
75
+ }
76
+ catch {
77
+ // Try fenced JSON blocks below.
78
+ }
79
+ }
80
+ const fencePattern = /```(?:json)?\s*([\s\S]*?)```/gi;
81
+ let match = fencePattern.exec(text);
82
+ while (match) {
83
+ try {
84
+ candidates.push(JSON.parse(match[1].trim()));
85
+ }
86
+ catch {
87
+ // Ignore invalid fenced blocks.
88
+ }
89
+ match = fencePattern.exec(text);
90
+ }
91
+ return candidates;
92
+ }
57
93
  // ============================================================================
58
94
  // AgentSession Class
59
95
  // ============================================================================
@@ -111,16 +147,9 @@ export class AgentSession {
111
147
  // Base system prompt (without extension appends) - used to apply fresh appends each turn
112
148
  _baseSystemPrompt = "";
113
149
  _baseSystemPromptOptions;
114
- get sessionManager() {
115
- return this.session;
116
- }
117
150
  constructor(config) {
118
151
  this.agent = config.agent;
119
- const session = config.session ?? config.sessionManager;
120
- if (!session) {
121
- throw new Error("AgentSession requires a session");
122
- }
123
- this.session = session;
152
+ this.session = config.session;
124
153
  this.settingsManager = config.settingsManager;
125
154
  this._scopedModels = config.scopedModels ?? [];
126
155
  this._resourceLoader = config.resourceLoader;
@@ -566,10 +595,14 @@ export class AgentSession {
566
595
  get followUpMode() {
567
596
  return this.agent.followUpMode;
568
597
  }
569
- /** Current session file path, or undefined if sessions are disabled */
570
- get sessionFile() {
598
+ /** Current session reference, or undefined for ephemeral sessions. */
599
+ get sessionReference() {
571
600
  return this.session.getSessionReference();
572
601
  }
602
+ /** Current local session file path, or undefined for non-file-backed sessions. Prefer sessionReference for backend-neutral code. */
603
+ get sessionFile() {
604
+ return this.sessionReference;
605
+ }
573
606
  /** Current session ID */
574
607
  get sessionId() {
575
608
  return this.session.getSessionId();
@@ -677,6 +710,186 @@ export class AgentSession {
677
710
  }
678
711
  return await this._checkCompaction(msg);
679
712
  }
713
+ async getStructuredResponse(options) {
714
+ if (this.isStreaming) {
715
+ throw new Error("Agent is already processing. Wait for completion before requesting structured output.");
716
+ }
717
+ if (!this.model) {
718
+ throw new Error(formatNoModelSelectedMessage());
719
+ }
720
+ if (!this._modelRegistry.hasConfiguredAuth(this.model)) {
721
+ throw new Error(formatNoApiKeyFoundMessage(this.model.provider));
722
+ }
723
+ const schemaName = options.name ?? DEFAULT_STRUCTURED_RESPONSE_TOOL_NAME;
724
+ const tool = {
725
+ name: schemaName,
726
+ description: options.description ??
727
+ "Return the requested structured response. Call this tool exactly once with arguments matching the schema.",
728
+ parameters: options.schema,
729
+ };
730
+ const lastAssistant = this._findLastAssistantMessage();
731
+ if (!lastAssistant) {
732
+ throw new Error("No assistant response is available to structure.");
733
+ }
734
+ const direct = this._tryParseStructuredAssistantText(tool, lastAssistant);
735
+ if (direct.ok) {
736
+ this._appendStructuredInternalEntry("result", schemaName, 0, "Validated structured response from assistant JSON.", {
737
+ stage: "result",
738
+ schemaName,
739
+ attempt: 0,
740
+ source: "json",
741
+ });
742
+ return { output: direct.output, attempts: 0, source: "json", message: lastAssistant };
743
+ }
744
+ const maxCorrections = options.maxCorrections ?? DEFAULT_STRUCTURED_RESPONSE_CORRECTIONS;
745
+ const maxAttempts = maxCorrections + 1;
746
+ const { apiKey, headers } = await this._getRequiredRequestAuth(this.model);
747
+ const messages = this._buildStructuredResponseMessages(options.scope ?? "latest", lastAssistant, schemaName);
748
+ let lastError = direct.error;
749
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
750
+ const requestText = attempt === 1
751
+ ? `Extract the structured response by calling ${schemaName} exactly once.`
752
+ : `Correct the structured response by calling ${schemaName} exactly once.\n\nValidation error:\n${lastError}`;
753
+ const userMessage = {
754
+ role: "user",
755
+ content: [{ type: "text", text: requestText }],
756
+ timestamp: Date.now(),
757
+ };
758
+ messages.push(userMessage);
759
+ this._appendStructuredInternalEntry("request", schemaName, attempt, requestText, {
760
+ stage: "request",
761
+ schemaName,
762
+ attempt,
763
+ validationError: attempt === 1 ? undefined : lastError,
764
+ });
765
+ const responseStream = await this.agent.streamFn(this.model, {
766
+ systemPrompt: "You are a structured data extraction assistant. Do not answer in prose. Use the provided tool to return the structured data.",
767
+ messages,
768
+ tools: [tool],
769
+ }, {
770
+ apiKey,
771
+ headers,
772
+ sessionId: this.agent.sessionId,
773
+ transport: this.agent.transport,
774
+ thinkingBudgets: this.agent.thinkingBudgets,
775
+ maxRetryDelayMs: this.agent.maxRetryDelayMs,
776
+ });
777
+ const assistantMessage = await responseStream.result();
778
+ messages.push(assistantMessage);
779
+ this._appendStructuredInternalEntry("assistant", schemaName, attempt, this._formatStructuredAssistantLog(assistantMessage), {
780
+ stage: "assistant",
781
+ schemaName,
782
+ attempt,
783
+ });
784
+ const toolCall = assistantMessage.content.find((block) => block.type === "toolCall" && block.name === schemaName);
785
+ if (toolCall) {
786
+ const validation = this._validateStructuredArguments(tool, toolCall.arguments);
787
+ if (validation.ok) {
788
+ this._appendStructuredInternalEntry("result", schemaName, attempt, "Validated structured tool response.", {
789
+ stage: "result",
790
+ schemaName,
791
+ attempt,
792
+ source: "tool",
793
+ });
794
+ return { output: validation.output, attempts: attempt, source: "tool", message: assistantMessage };
795
+ }
796
+ lastError = validation.error;
797
+ const toolResult = {
798
+ role: "toolResult",
799
+ toolCallId: toolCall.id,
800
+ toolName: toolCall.name,
801
+ content: [{ type: "text", text: validation.error }],
802
+ isError: true,
803
+ timestamp: Date.now(),
804
+ };
805
+ messages.push(toolResult);
806
+ this._appendStructuredInternalEntry("tool_result", schemaName, attempt, validation.error, {
807
+ stage: "tool_result",
808
+ schemaName,
809
+ attempt,
810
+ validationError: validation.error,
811
+ });
812
+ continue;
813
+ }
814
+ const textValidation = this._tryParseStructuredAssistantText(tool, assistantMessage);
815
+ if (textValidation.ok) {
816
+ this._appendStructuredInternalEntry("result", schemaName, attempt, "Validated structured JSON response.", {
817
+ stage: "result",
818
+ schemaName,
819
+ attempt,
820
+ source: "json",
821
+ });
822
+ return { output: textValidation.output, attempts: attempt, source: "json", message: assistantMessage };
823
+ }
824
+ lastError = textValidation.error;
825
+ }
826
+ throw new Error(`Structured response validation failed after ${maxAttempts} attempt(s):\n${lastError}`);
827
+ }
828
+ _buildStructuredResponseMessages(scope, lastAssistant, schemaName) {
829
+ if (scope === "conversation") {
830
+ return this.agent.state.messages.filter((message) => message.role === "user" || message.role === "assistant" || message.role === "toolResult");
831
+ }
832
+ return [
833
+ {
834
+ role: "user",
835
+ content: [
836
+ {
837
+ type: "text",
838
+ text: `Extract structured data from the latest assistant response below. Call ${schemaName} exactly once.\n\n` +
839
+ `<assistant_response>\n${getAssistantText(lastAssistant)}\n</assistant_response>`,
840
+ },
841
+ ],
842
+ timestamp: Date.now(),
843
+ },
844
+ ];
845
+ }
846
+ _tryParseStructuredAssistantText(tool, message) {
847
+ const text = getAssistantText(message);
848
+ for (const candidate of extractJsonCandidates(text)) {
849
+ if (!isRecord(candidate)) {
850
+ continue;
851
+ }
852
+ const validation = this._validateStructuredArguments(tool, candidate);
853
+ if (validation.ok) {
854
+ return validation;
855
+ }
856
+ }
857
+ return { ok: false, error: "Assistant response did not contain valid JSON matching the requested schema." };
858
+ }
859
+ _validateStructuredArguments(tool, arguments_) {
860
+ try {
861
+ const output = validateToolArguments(tool, {
862
+ type: "toolCall",
863
+ id: "structured-response-validation",
864
+ name: tool.name,
865
+ arguments: arguments_,
866
+ });
867
+ return { ok: true, output };
868
+ }
869
+ catch (error) {
870
+ return { ok: false, error: error instanceof Error ? error.message : String(error) };
871
+ }
872
+ }
873
+ _appendStructuredInternalEntry(stage, schemaName, attempt, content, details) {
874
+ this.session.appendCustomMessageEntry(STRUCTURED_RESPONSE_INTERNAL_CUSTOM_TYPE, content, false, {
875
+ ...details,
876
+ stage,
877
+ schemaName,
878
+ attempt,
879
+ });
880
+ }
881
+ _formatStructuredAssistantLog(message) {
882
+ const parts = message.content.map((block) => {
883
+ if (block.type === "text") {
884
+ return block.text;
885
+ }
886
+ if (block.type === "thinking") {
887
+ return "[thinking omitted]";
888
+ }
889
+ return `tool:${block.name} ${JSON.stringify(block.arguments)}`;
890
+ });
891
+ return parts.join("\n");
892
+ }
680
893
  /**
681
894
  * Send a prompt to the agent.
682
895
  * - Handles extension commands (registered via pi.registerCommand) immediately, even during streaming