@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.
- package/CHANGELOG.md +32 -0
- package/dist/core/agent-session.d.ts +32 -7
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +225 -12
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/compaction/branch-summarization.d.ts +2 -2
- package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
- package/dist/core/compaction/branch-summarization.js.map +1 -1
- package/dist/core/compaction/compaction.d.ts +2 -2
- package/dist/core/compaction/compaction.d.ts.map +1 -1
- package/dist/core/compaction/compaction.js +1 -1
- package/dist/core/compaction/compaction.js.map +1 -1
- package/dist/core/extensions/runner.d.ts +3 -3
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +5 -5
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +15 -8
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/footer-data-provider.d.ts +1 -1
- package/dist/core/footer-data-provider.d.ts.map +1 -1
- package/dist/core/footer-data-provider.js +1 -1
- package/dist/core/footer-data-provider.js.map +1 -1
- package/dist/core/messages.d.ts +1 -0
- package/dist/core/messages.d.ts.map +1 -1
- package/dist/core/messages.js +4 -0
- package/dist/core/messages.js.map +1 -1
- package/dist/core/pi-agent.d.ts +5 -3
- package/dist/core/pi-agent.d.ts.map +1 -1
- package/dist/core/pi-agent.js +64 -18
- package/dist/core/pi-agent.js.map +1 -1
- package/dist/core/session/in-memory-session-manager.d.ts.map +1 -1
- package/dist/core/session/in-memory-session-manager.js +5 -7
- package/dist/core/session/in-memory-session-manager.js.map +1 -1
- package/dist/core/session/in-memory-session.d.ts +3 -1
- package/dist/core/session/in-memory-session.d.ts.map +1 -1
- package/dist/core/session/in-memory-session.js +5 -2
- package/dist/core/session/in-memory-session.js.map +1 -1
- package/dist/core/session/index.d.ts +2 -2
- package/dist/core/session/index.d.ts.map +1 -1
- package/dist/core/session/index.js.map +1 -1
- package/dist/core/session/jsonl-helpers.d.ts.map +1 -1
- package/dist/core/session/jsonl-helpers.js +4 -4
- package/dist/core/session/jsonl-helpers.js.map +1 -1
- package/dist/core/session/local-session-manager.d.ts.map +1 -1
- package/dist/core/session/local-session-manager.js +12 -11
- package/dist/core/session/local-session-manager.js.map +1 -1
- package/dist/core/session/local-session.d.ts +3 -1
- package/dist/core/session/local-session.d.ts.map +1 -1
- package/dist/core/session/local-session.js +7 -2
- package/dist/core/session/local-session.js.map +1 -1
- package/dist/core/session/remote-session-client.d.ts +6 -1
- package/dist/core/session/remote-session-client.d.ts.map +1 -1
- package/dist/core/session/remote-session-client.js.map +1 -1
- package/dist/core/session/remote-session-manager.d.ts.map +1 -1
- package/dist/core/session/remote-session-manager.js +28 -7
- package/dist/core/session/remote-session-manager.js.map +1 -1
- package/dist/core/session/remote-session.d.ts +3 -0
- package/dist/core/session/remote-session.d.ts.map +1 -1
- package/dist/core/session/remote-session.js +4 -1
- package/dist/core/session/remote-session.js.map +1 -1
- package/dist/core/session/session.d.ts +9 -3
- package/dist/core/session/session.d.ts.map +1 -1
- package/dist/core/session/session.js +64 -10
- package/dist/core/session/session.js.map +1 -1
- package/dist/core/session/stores/in-memory-session-store.d.ts +6 -14
- package/dist/core/session/stores/in-memory-session-store.d.ts.map +1 -1
- package/dist/core/session/stores/in-memory-session-store.js +8 -34
- package/dist/core/session/stores/in-memory-session-store.js.map +1 -1
- package/dist/core/session/stores/jsonl-session-store.d.ts +14 -14
- package/dist/core/session/stores/jsonl-session-store.d.ts.map +1 -1
- package/dist/core/session/stores/jsonl-session-store.js +153 -162
- package/dist/core/session/stores/jsonl-session-store.js.map +1 -1
- package/dist/core/session/stores/remote-session-store.d.ts +4 -6
- package/dist/core/session/stores/remote-session-store.d.ts.map +1 -1
- package/dist/core/session/stores/remote-session-store.js +18 -30
- package/dist/core/session/stores/remote-session-store.js.map +1 -1
- package/dist/core/session/stores/session-store.d.ts +1 -15
- package/dist/core/session/stores/session-store.d.ts.map +1 -1
- package/dist/core/session/stores/session-store.js.map +1 -1
- package/dist/core/session-cwd.d.ts +2 -2
- package/dist/core/session-cwd.d.ts.map +1 -1
- package/dist/core/session-cwd.js +5 -5
- package/dist/core/session-cwd.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +39 -37
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/docs/extensions.md +35 -32
- package/docs/index.md +1 -1
- package/docs/sdk.md +2 -0
- package/docs/session-format.md +21 -21
- package/docs/sessions.md +2 -2
- package/docs/tui.md +1 -1
- package/examples/README.md +3 -0
- package/examples/extensions/README.md +1 -1
- package/examples/extensions/auto-commit-on-exit.ts +1 -1
- package/examples/extensions/bookmark.ts +3 -3
- package/examples/extensions/confirm-destructive.ts +1 -1
- package/examples/extensions/custom-compaction.ts +1 -1
- package/examples/extensions/custom-footer.ts +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/git-checkpoint.ts +1 -1
- package/examples/extensions/handoff.ts +2 -2
- package/examples/extensions/plan-mode/index.ts +1 -1
- package/examples/extensions/preset.ts +1 -1
- package/examples/extensions/qna.ts +1 -1
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/snake.ts +1 -1
- package/examples/extensions/space-invaders.ts +1 -1
- package/examples/extensions/summarize.ts +1 -1
- package/examples/extensions/tic-tac-toe.ts +1 -1
- package/examples/extensions/todo.ts +1 -1
- package/examples/extensions/tools.ts +1 -1
- package/examples/extensions/with-deps/package.json +1 -1
- package/examples/remote-session-server/README.md +66 -0
- package/examples/remote-session-server/server.ts +359 -0
- package/examples/sdk/11-sessions.ts +3 -3
- package/examples/sdk/13-session-runtime.ts +6 -6
- package/npm-shrinkwrap.json +12 -12
- 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
|
-
* -
|
|
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
|
-
|
|
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
|
|
570
|
-
get
|
|
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
|