@caupulican/pi-adaptative 0.81.9 → 0.81.13
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 +26 -0
- package/dist/bundled-resources/skills/tool-call-repair/SKILL.md +152 -0
- package/dist/bundled-resources/skills/tool-call-repair/references/failure-grammar.md +128 -0
- package/dist/bundled-resources/skills/tool-call-repair/references/repair-catalogue.md +201 -0
- package/dist/bundled-resources/skills/tool-call-repair/references/text-protocol-grammar.md +141 -0
- package/dist/cli/list-models.d.ts.map +1 -1
- package/dist/cli/list-models.js +5 -0
- package/dist/cli/list-models.js.map +1 -1
- package/dist/core/agent-session.d.ts +46 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +394 -1
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/delegation/worker-actions.d.ts +2 -1
- package/dist/core/delegation/worker-actions.d.ts.map +1 -1
- package/dist/core/delegation/worker-actions.js +34 -2
- package/dist/core/delegation/worker-actions.js.map +1 -1
- package/dist/core/extensions/types.d.ts +3 -1
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/failure-corpus.d.ts +27 -1
- package/dist/core/failure-corpus.d.ts.map +1 -1
- package/dist/core/failure-corpus.js +33 -11
- package/dist/core/failure-corpus.js.map +1 -1
- package/dist/core/model-registry.d.ts +3 -2
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +12 -4
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/models/adaptation-store.d.ts +79 -0
- package/dist/core/models/adaptation-store.d.ts.map +1 -0
- package/dist/core/models/adaptation-store.js +196 -0
- package/dist/core/models/adaptation-store.js.map +1 -0
- package/dist/core/session-analytics.d.ts +24 -1
- package/dist/core/session-analytics.d.ts.map +1 -1
- package/dist/core/session-analytics.js +75 -0
- package/dist/core/session-analytics.js.map +1 -1
- package/dist/core/settings-manager.d.ts +6 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +10 -0
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/system-prompt-builder.d.ts +4 -0
- package/dist/core/system-prompt-builder.d.ts.map +1 -1
- package/dist/core/system-prompt-builder.js +8 -0
- package/dist/core/system-prompt-builder.js.map +1 -1
- package/dist/core/tool-repair-health.d.ts +3 -0
- package/dist/core/tool-repair-health.d.ts.map +1 -0
- package/dist/core/tool-repair-health.js +63 -0
- package/dist/core/tool-repair-health.js.map +1 -0
- package/dist/core/tool-repair-settings.d.ts +11 -0
- package/dist/core/tool-repair-settings.d.ts.map +1 -0
- package/dist/core/tool-repair-settings.js +18 -0
- package/dist/core/tool-repair-settings.js.map +1 -0
- package/dist/core/tools/edit.d.ts.map +1 -1
- package/dist/core/tools/edit.js +0 -9
- package/dist/core/tools/edit.js.map +1 -1
- package/dist/core/tools/find.d.ts.map +1 -1
- package/dist/core/tools/find.js +10 -7
- package/dist/core/tools/find.js.map +1 -1
- package/dist/core/tools/grep.d.ts.map +1 -1
- package/dist/core/tools/grep.js +15 -9
- package/dist/core/tools/grep.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts +7 -3
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +21 -4
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/external-editor.d.ts.map +1 -1
- package/dist/modes/interactive/external-editor.js +5 -3
- package/dist/modes/interactive/external-editor.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +1 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +61 -7
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +16 -0
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +47 -1
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/docs/index.md +1 -0
- package/docs/models.md +5 -1
- package/docs/rpc.md +75 -0
- package/docs/settings.md +22 -0
- package/docs/tool-repair.md +83 -0
- package/docs/usage.md +3 -0
- package/examples/extensions/custom-provider-anthropic/package-lock.json +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/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/npm-shrinkwrap.json +12 -12
- package/package.json +6 -4
- package/docs/bug-ledger.md +0 -134
|
@@ -2,7 +2,8 @@ import { readFileSync } from "node:fs";
|
|
|
2
2
|
import { basename, dirname, join } from "node:path";
|
|
3
3
|
import { classifyFailure, compactToolResultDetailsForRetention, computeRetryDelayMs, createCustomMessage, DEFAULT_RETRY_POLICY, RetryController, sleepAbortable, withStreamIdleWatchdog, } from "@caupulican/pi-agent-core";
|
|
4
4
|
import { calculateContextTokens, compact, createDeterministicCompaction, estimateContextTokens, getLatestCompactionEntry, prepareCompaction, runCompactionLoop, shouldCompact, } from "@caupulican/pi-agent-core/node";
|
|
5
|
-
import { cleanupSessionResources, isContextOverflow, streamSimple } from "@caupulican/pi-ai";
|
|
5
|
+
import { cleanupSessionResources, formatToolRepairStandingRule, generateTextToolProtocolPrimer, isContextOverflow, parseTextToolCalls, streamSimple, } from "@caupulican/pi-ai";
|
|
6
|
+
import { Type } from "typebox";
|
|
6
7
|
import { getAgentDir } from "../config.js";
|
|
7
8
|
import { stripFrontmatter } from "../utils/frontmatter.js";
|
|
8
9
|
import { formatNoApiKeyFoundMessage, formatNoModelSelectedMessage } from "./auth-guidance.js";
|
|
@@ -28,6 +29,7 @@ import { MemoryController } from "./memory-controller.js";
|
|
|
28
29
|
import { deriveModelCapabilityProfile, filterToolNamesForCapability, } from "./model-capability.js";
|
|
29
30
|
import { formatModelRouterModel, ModelRouterController } from "./model-router-controller.js";
|
|
30
31
|
import { ModelSelectionController } from "./model-selection-controller.js";
|
|
32
|
+
import { ModelAdaptationStore } from "./models/adaptation-store.js";
|
|
31
33
|
import { ProfileFilterController } from "./profile-filter-controller.js";
|
|
32
34
|
import { expandPromptTemplate } from "./prompt-templates.js";
|
|
33
35
|
import { ReflectionController } from "./reflection-controller.js";
|
|
@@ -39,6 +41,8 @@ import { SessionAnalytics } from "./session-analytics.js";
|
|
|
39
41
|
import { SessionTreeNavigator } from "./session-tree-navigator.js";
|
|
40
42
|
import { SystemPromptBuilder } from "./system-prompt-builder.js";
|
|
41
43
|
import { ToolGateController } from "./tool-gate-controller.js";
|
|
44
|
+
import { formatToolRepairHealthReport } from "./tool-repair-health.js";
|
|
45
|
+
import { resolveCurrentToolRepairSettings } from "./tool-repair-settings.js";
|
|
42
46
|
// ============================================================================
|
|
43
47
|
// Stream-idle watchdog wiring
|
|
44
48
|
// ============================================================================
|
|
@@ -52,6 +56,16 @@ import { ToolGateController } from "./tool-gate-controller.js";
|
|
|
52
56
|
* key stable regardless of how many times this module is evaluated.
|
|
53
57
|
*/
|
|
54
58
|
const RAW_STREAM_MARKER = Symbol.for("pi.rawStreamSimple");
|
|
59
|
+
const MODEL_ADAPTATION_REPAIR_THRESHOLD = 3;
|
|
60
|
+
const TEXT_TOOL_PROTOCOL_VERSION = 1;
|
|
61
|
+
const TEXT_TOOL_PROTOCOL_TRIALS_PER_VARIANT = 2;
|
|
62
|
+
const TEXT_TOOL_PROTOCOL_PARSE_FAILURE_THRESHOLD = 3;
|
|
63
|
+
const TEXT_TOOL_PROTOCOL_VARIANTS = ["tool-tag", "tool-call", "fenced-json"];
|
|
64
|
+
const TEXT_TOOL_PROTOCOL_ECHO_TOOL = {
|
|
65
|
+
name: "echo",
|
|
66
|
+
description: "Echo calibration data",
|
|
67
|
+
parameters: Type.Object({ data: Type.String() }),
|
|
68
|
+
};
|
|
55
69
|
/** Test-only override of the stream-idle bounds. Read per-request by the wiring's resolver. */
|
|
56
70
|
let streamIdleOptionsOverride;
|
|
57
71
|
/**
|
|
@@ -133,6 +147,10 @@ export class AgentSession {
|
|
|
133
147
|
_agentDir;
|
|
134
148
|
_collectWorkspaceSources;
|
|
135
149
|
_localRuntimeController;
|
|
150
|
+
_modelAdaptationStore;
|
|
151
|
+
_repairModeSessionCounts = new Map();
|
|
152
|
+
_textProtocolParseFailures = new Map();
|
|
153
|
+
_textProtocolParseObservedThisTurn = false;
|
|
136
154
|
/** Assembles the session's base system prompt from live session state (see
|
|
137
155
|
* system-prompt-builder.ts); owns the paired _baseSystemPromptOptions. */
|
|
138
156
|
_systemPromptBuilder;
|
|
@@ -242,6 +260,9 @@ export class AgentSession {
|
|
|
242
260
|
this._customTools = config.customTools ?? [];
|
|
243
261
|
this._cwd = config.cwd;
|
|
244
262
|
this._agentDir = config.agentDir ?? getAgentDir();
|
|
263
|
+
this._modelAdaptationStore = ModelAdaptationStore.forAgentDir(this._agentDir);
|
|
264
|
+
this.agent.onTextToolProtocolParse = (event) => this._handleTextToolProtocolParse(event);
|
|
265
|
+
this._applyToolRepairLayerSettings();
|
|
245
266
|
this._collectWorkspaceSources = config.collectWorkspaceSources ?? collectWorkspaceSources;
|
|
246
267
|
this._localRuntimeController = new LocalRuntimeController({
|
|
247
268
|
agentDir: this._agentDir,
|
|
@@ -260,6 +281,7 @@ export class AgentSession {
|
|
|
260
281
|
hasTool: (name) => this._runtimeBuilder.hasTool(name),
|
|
261
282
|
getToolPromptSnippet: (name) => this._runtimeBuilder.getToolPromptSnippet(name),
|
|
262
283
|
getToolPromptGuidelines: (name) => this._runtimeBuilder.getToolPromptGuidelines(name),
|
|
284
|
+
getModelAdaptationRules: () => this._getModelAdaptationRulesForPrompt(),
|
|
263
285
|
getActiveExtensions: () => this._extensionRunner.activeExtensions,
|
|
264
286
|
});
|
|
265
287
|
this._autonomyTelemetry = new AutonomyTelemetry({
|
|
@@ -479,6 +501,14 @@ export class AgentSession {
|
|
|
479
501
|
getSettingsManager: () => this.settingsManager,
|
|
480
502
|
getToolDefinition: (name) => this.getToolDefinition(name),
|
|
481
503
|
});
|
|
504
|
+
const previousToolArgumentValidation = this.agent.onToolArgumentValidation;
|
|
505
|
+
this.agent.onToolArgumentValidation = (event) => {
|
|
506
|
+
const taggedEvent = this._tagModelAdaptationRuleTeaching(event);
|
|
507
|
+
previousToolArgumentValidation?.(taggedEvent);
|
|
508
|
+
this._analytics.recordToolArgumentValidation(taggedEvent);
|
|
509
|
+
this._recordToolValidationBounce(taggedEvent);
|
|
510
|
+
this._handleModelAdaptationTelemetry(taggedEvent);
|
|
511
|
+
};
|
|
482
512
|
this._treeNavigator = new SessionTreeNavigator({
|
|
483
513
|
getSessionManager: () => this.sessionManager,
|
|
484
514
|
getModel: () => this.model,
|
|
@@ -757,6 +787,354 @@ export class AgentSession {
|
|
|
757
787
|
tools: this.agent.state.tools.slice(),
|
|
758
788
|
};
|
|
759
789
|
}
|
|
790
|
+
_modelAdaptationKeyFor(model) {
|
|
791
|
+
return model ? formatModelRouterModel(model) : undefined;
|
|
792
|
+
}
|
|
793
|
+
_toolRepairSettings() {
|
|
794
|
+
return resolveCurrentToolRepairSettings(this.settingsManager.settings);
|
|
795
|
+
}
|
|
796
|
+
_applyToolRepairLayerSettings() {
|
|
797
|
+
const settings = this._toolRepairSettings();
|
|
798
|
+
this.agent.toolArgumentRepairEnabled = settings.repair;
|
|
799
|
+
this.agent.toolArgumentTeachEnabled = settings.teach;
|
|
800
|
+
}
|
|
801
|
+
_getModelAdaptationRulesForPrompt() {
|
|
802
|
+
if (!this._toolRepairSettings().teach)
|
|
803
|
+
return [];
|
|
804
|
+
const modelKey = this._modelAdaptationKeyFor(this.agent.state.model);
|
|
805
|
+
return modelKey ? this._modelAdaptationStore.get(modelKey).rules : [];
|
|
806
|
+
}
|
|
807
|
+
_textProtocolFlag(model) {
|
|
808
|
+
// Phase 7 gating hierarchy: PI_TEXT_TOOL_CALL_PROTOCOL_DISABLED is resolved
|
|
809
|
+
// in _toolRepairSettings() as the env kill switch, then settings.toolRepair.textProtocol
|
|
810
|
+
// force-enables/disables globally, then Model.textToolCallProtocol opts in per model,
|
|
811
|
+
// then a persisted /toolprobe text-protocol verdict opts in that exact model. The
|
|
812
|
+
// calibration store is consulted after this flag; native provider tool calls still
|
|
813
|
+
// win when emitted, and this only enables the text-protocol fallback lane.
|
|
814
|
+
const override = this._toolRepairSettings().textProtocol;
|
|
815
|
+
if (override !== undefined)
|
|
816
|
+
return override;
|
|
817
|
+
if (model?.textToolCallProtocol === true)
|
|
818
|
+
return true;
|
|
819
|
+
const modelKey = this._modelAdaptationKeyFor(model);
|
|
820
|
+
return !!modelKey && this._modelAdaptationStore.get(modelKey).toolProbe?.status === "text-protocol";
|
|
821
|
+
}
|
|
822
|
+
async _streamForToolProbe(model, context, options) {
|
|
823
|
+
let requestOptions = options;
|
|
824
|
+
if (this._isRawStreamSimple(this.agent.streamFn)) {
|
|
825
|
+
const auth = await this._getRequiredRequestAuth(model);
|
|
826
|
+
requestOptions = {
|
|
827
|
+
...options,
|
|
828
|
+
apiKey: auth.apiKey,
|
|
829
|
+
headers: auth.headers || options.headers ? { ...auth.headers, ...options.headers } : undefined,
|
|
830
|
+
};
|
|
831
|
+
}
|
|
832
|
+
return this.agent.streamFn(model, context, requestOptions);
|
|
833
|
+
}
|
|
834
|
+
_textProtocolCalibrationContext(variant, token) {
|
|
835
|
+
const primer = generateTextToolProtocolPrimer([TEXT_TOOL_PROTOCOL_ECHO_TOOL], { variant });
|
|
836
|
+
const instruction = `Text tool protocol calibration trial. Using the protocol above, call echo with data exactly "${token}". Output only the tool-call envelope.`;
|
|
837
|
+
return {
|
|
838
|
+
systemPrompt: `${primer}\n\n${instruction}`,
|
|
839
|
+
messages: [{ role: "user", content: [{ type: "text", text: instruction }], timestamp: Date.now() }],
|
|
840
|
+
tools: [TEXT_TOOL_PROTOCOL_ECHO_TOOL],
|
|
841
|
+
};
|
|
842
|
+
}
|
|
843
|
+
_messageHasEchoProbe(message, token) {
|
|
844
|
+
return message.content.some((block) => block.type === "toolCall" && block.name === "echo" && block.arguments.data === token);
|
|
845
|
+
}
|
|
846
|
+
async _runNativeToolProbeTrial(model, token) {
|
|
847
|
+
const instruction = `Native tool-call capability probe. Use provider-native tool calling, not prose. ` +
|
|
848
|
+
`Call echo with data exactly "${token}".`;
|
|
849
|
+
const stream = await this._streamForToolProbe(model, {
|
|
850
|
+
systemPrompt: instruction,
|
|
851
|
+
messages: [{ role: "user", content: [{ type: "text", text: instruction }], timestamp: Date.now() }],
|
|
852
|
+
tools: [TEXT_TOOL_PROTOCOL_ECHO_TOOL],
|
|
853
|
+
}, { textToolCallProtocol: false, maxRetries: 0 });
|
|
854
|
+
return this._messageHasEchoProbe(await stream.result(), token);
|
|
855
|
+
}
|
|
856
|
+
async _runTextProtocolTrial(model, variant, token) {
|
|
857
|
+
const stream = await this._streamForToolProbe(model, this._textProtocolCalibrationContext(variant, token), {
|
|
858
|
+
textToolCallProtocol: false,
|
|
859
|
+
maxRetries: 0,
|
|
860
|
+
});
|
|
861
|
+
const message = await stream.result();
|
|
862
|
+
if (this._messageHasEchoProbe(message, token))
|
|
863
|
+
return true;
|
|
864
|
+
const text = message.content
|
|
865
|
+
.filter((block) => block.type === "text")
|
|
866
|
+
.map((block) => block.text)
|
|
867
|
+
.join("\n")
|
|
868
|
+
.trim();
|
|
869
|
+
if (!text)
|
|
870
|
+
return false;
|
|
871
|
+
const parsed = parseTextToolCalls(text, [TEXT_TOOL_PROTOCOL_ECHO_TOOL]);
|
|
872
|
+
return parsed.calls.some((call) => call.name === "echo" && call.arguments.data === token);
|
|
873
|
+
}
|
|
874
|
+
async _calibrateTextToolProtocolForModel(model, modelKey, options) {
|
|
875
|
+
const variantsTried = [];
|
|
876
|
+
for (const variant of TEXT_TOOL_PROTOCOL_VARIANTS) {
|
|
877
|
+
variantsTried.push(variant);
|
|
878
|
+
let passed = true;
|
|
879
|
+
for (let trial = 0; trial < TEXT_TOOL_PROTOCOL_TRIALS_PER_VARIANT; trial++) {
|
|
880
|
+
const ok = await this._runTextProtocolTrial(model, variant, `pi-calibration-${trial + 1}`);
|
|
881
|
+
if (!ok) {
|
|
882
|
+
passed = false;
|
|
883
|
+
break;
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
if (passed) {
|
|
887
|
+
const calibratedAt = new Date().toISOString();
|
|
888
|
+
if (modelKey) {
|
|
889
|
+
this._modelAdaptationStore.setProtocol(modelKey, { version: TEXT_TOOL_PROTOCOL_VERSION, status: "calibrated", variant, calibratedAt }, calibratedAt);
|
|
890
|
+
}
|
|
891
|
+
return { status: "calibrated", variant, calibratedAt };
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
const attemptedAt = new Date().toISOString();
|
|
895
|
+
if (modelKey && options.persistFailure) {
|
|
896
|
+
this._modelAdaptationStore.setProtocol(modelKey, { version: TEXT_TOOL_PROTOCOL_VERSION, status: "failed", attemptedAt, variantsTried }, attemptedAt);
|
|
897
|
+
}
|
|
898
|
+
return { status: "failed", attemptedAt, variantsTried };
|
|
899
|
+
}
|
|
900
|
+
async _ensureTextToolProtocolForActiveModel() {
|
|
901
|
+
const model = this.agent.state.model;
|
|
902
|
+
if (!this._textProtocolFlag(model)) {
|
|
903
|
+
this.agent.textToolCallProtocol = undefined;
|
|
904
|
+
return;
|
|
905
|
+
}
|
|
906
|
+
const modelKey = this._modelAdaptationKeyFor(model);
|
|
907
|
+
if (!modelKey) {
|
|
908
|
+
this.agent.textToolCallProtocol = true;
|
|
909
|
+
return;
|
|
910
|
+
}
|
|
911
|
+
const profile = this._modelAdaptationStore.get(modelKey);
|
|
912
|
+
if (profile.protocol?.version === TEXT_TOOL_PROTOCOL_VERSION) {
|
|
913
|
+
if (profile.protocol.status === "failed") {
|
|
914
|
+
this.agent.textToolCallProtocol = undefined;
|
|
915
|
+
throw new Error(`Previous text tool protocol calibration failed for ${modelKey} at ${profile.protocol.attemptedAt}. ` +
|
|
916
|
+
`Variants tried: ${profile.protocol.variantsTried.join(", ")}. ` +
|
|
917
|
+
`Run /toolhealth for details or /toolprotocol-reset ${modelKey} to retry calibration.`);
|
|
918
|
+
}
|
|
919
|
+
this.agent.textToolCallProtocol = { variant: profile.protocol.variant };
|
|
920
|
+
return;
|
|
921
|
+
}
|
|
922
|
+
const result = await this._calibrateTextToolProtocolForModel(model, modelKey, { persistFailure: true });
|
|
923
|
+
if (result.status === "calibrated") {
|
|
924
|
+
this.agent.textToolCallProtocol = { variant: result.variant };
|
|
925
|
+
return;
|
|
926
|
+
}
|
|
927
|
+
this.agent.textToolCallProtocol = undefined;
|
|
928
|
+
throw new Error(`Model ${modelKey} cannot follow the text tool protocol after calibration. ` +
|
|
929
|
+
`Run /toolhealth for details or /toolprotocol-reset ${modelKey} to retry calibration.`);
|
|
930
|
+
}
|
|
931
|
+
_modelRef(model) {
|
|
932
|
+
return `${model.provider}/${model.id}`;
|
|
933
|
+
}
|
|
934
|
+
_formatToolProbeReport(results) {
|
|
935
|
+
const lines = ["Tool probe results:", "Model | Verdict | Variant | Diagnostic", "--- | --- | --- | ---"];
|
|
936
|
+
for (const result of results) {
|
|
937
|
+
lines.push([
|
|
938
|
+
result.model,
|
|
939
|
+
result.verdict,
|
|
940
|
+
result.variant ?? "-",
|
|
941
|
+
result.diagnostic ? result.diagnostic.replace(/\s+/g, " ").slice(0, 160) : "-",
|
|
942
|
+
].join(" | "));
|
|
943
|
+
}
|
|
944
|
+
return lines.join("\n");
|
|
945
|
+
}
|
|
946
|
+
_storeToolProbe(modelKey, probe) {
|
|
947
|
+
this._modelAdaptationStore.setToolProbe(modelKey, probe, probe.probedAt);
|
|
948
|
+
}
|
|
949
|
+
async _probeToolCallingForModel(model) {
|
|
950
|
+
const modelKey = this._modelRef(model);
|
|
951
|
+
const probedAt = new Date().toISOString();
|
|
952
|
+
let diagnostic;
|
|
953
|
+
try {
|
|
954
|
+
if (await this._runNativeToolProbeTrial(model, "pi-native-probe")) {
|
|
955
|
+
this._storeToolProbe(modelKey, { version: TEXT_TOOL_PROTOCOL_VERSION, status: "native", probedAt });
|
|
956
|
+
return { model: modelKey, verdict: "native" };
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
catch (error) {
|
|
960
|
+
diagnostic = error instanceof Error ? error.message : String(error);
|
|
961
|
+
}
|
|
962
|
+
try {
|
|
963
|
+
const calibrated = await this._calibrateTextToolProtocolForModel(model, modelKey, { persistFailure: false });
|
|
964
|
+
if (calibrated.status === "calibrated") {
|
|
965
|
+
this._storeToolProbe(modelKey, {
|
|
966
|
+
version: TEXT_TOOL_PROTOCOL_VERSION,
|
|
967
|
+
status: "text-protocol",
|
|
968
|
+
probedAt: calibrated.calibratedAt,
|
|
969
|
+
variant: calibrated.variant,
|
|
970
|
+
});
|
|
971
|
+
return { model: modelKey, verdict: "text-protocol", variant: calibrated.variant };
|
|
972
|
+
}
|
|
973
|
+
diagnostic ??= `Text protocol variants failed: ${calibrated.variantsTried.join(", ")}`;
|
|
974
|
+
}
|
|
975
|
+
catch (error) {
|
|
976
|
+
diagnostic = error instanceof Error ? error.message : String(error);
|
|
977
|
+
}
|
|
978
|
+
this._storeToolProbe(modelKey, { version: TEXT_TOOL_PROTOCOL_VERSION, status: "none", probedAt, diagnostic });
|
|
979
|
+
return { model: modelKey, verdict: "none", diagnostic };
|
|
980
|
+
}
|
|
981
|
+
async _resolveToolProbeModels(target) {
|
|
982
|
+
const trimmed = target?.trim();
|
|
983
|
+
if (!trimmed)
|
|
984
|
+
return this._modelRegistry.getAvailable();
|
|
985
|
+
const [provider, ...modelParts] = trimmed.split("/");
|
|
986
|
+
const modelId = modelParts.join("/");
|
|
987
|
+
if (!provider || !modelId)
|
|
988
|
+
throw new Error("Usage: /toolprobe [provider/model]");
|
|
989
|
+
const exact = this._modelRegistry.find(provider, modelId);
|
|
990
|
+
if (exact)
|
|
991
|
+
return [exact];
|
|
992
|
+
const current = this.agent.state.model;
|
|
993
|
+
if (current?.provider === provider && current.id === modelId)
|
|
994
|
+
return [current];
|
|
995
|
+
throw new Error(`Model not found: ${trimmed}`);
|
|
996
|
+
}
|
|
997
|
+
async probeToolCalling(target) {
|
|
998
|
+
const models = await this._resolveToolProbeModels(target);
|
|
999
|
+
if (models.length === 0)
|
|
1000
|
+
throw new Error("No available models to probe.");
|
|
1001
|
+
const results = [];
|
|
1002
|
+
for (const model of models) {
|
|
1003
|
+
results.push(await this._probeToolCallingForModel(model));
|
|
1004
|
+
}
|
|
1005
|
+
return { results, table: this._formatToolProbeReport(results) };
|
|
1006
|
+
}
|
|
1007
|
+
_handleTextToolProtocolParse(event) {
|
|
1008
|
+
this._textProtocolParseObservedThisTurn = true;
|
|
1009
|
+
const modelKey = `${event.provider}/${event.model}`;
|
|
1010
|
+
if (event.status === "parsed") {
|
|
1011
|
+
this._textProtocolParseFailures.delete(modelKey);
|
|
1012
|
+
return;
|
|
1013
|
+
}
|
|
1014
|
+
const signature = `${event.variant}:${event.reason ?? "failed"}`;
|
|
1015
|
+
const previous = this._textProtocolParseFailures.get(modelKey);
|
|
1016
|
+
const repeats = previous?.signature === signature ? previous.repeats + 1 : 1;
|
|
1017
|
+
this._textProtocolParseFailures.set(modelKey, { signature, repeats });
|
|
1018
|
+
if (repeats < TEXT_TOOL_PROTOCOL_PARSE_FAILURE_THRESHOLD)
|
|
1019
|
+
return;
|
|
1020
|
+
const profile = this._modelAdaptationStore.get(modelKey);
|
|
1021
|
+
if (profile.protocol?.version === TEXT_TOOL_PROTOCOL_VERSION && profile.protocol.status !== "failed") {
|
|
1022
|
+
this._modelAdaptationStore.removeProtocol(modelKey);
|
|
1023
|
+
this.agent.textToolCallProtocol = undefined;
|
|
1024
|
+
}
|
|
1025
|
+
this._textProtocolParseFailures.delete(modelKey);
|
|
1026
|
+
}
|
|
1027
|
+
_recordTextToolProtocolParseOutcomeFromLastAssistant() {
|
|
1028
|
+
if (this._textProtocolParseObservedThisTurn)
|
|
1029
|
+
return;
|
|
1030
|
+
const protocol = this.agent.textToolCallProtocol;
|
|
1031
|
+
if (protocol === false || protocol === true || !protocol?.variant)
|
|
1032
|
+
return;
|
|
1033
|
+
const response = this._findLastAssistantMessage();
|
|
1034
|
+
if (!response)
|
|
1035
|
+
return;
|
|
1036
|
+
const responseText = response.content
|
|
1037
|
+
.filter((content) => content.type === "text")
|
|
1038
|
+
.map((content) => content.text)
|
|
1039
|
+
.join("\n");
|
|
1040
|
+
if (!responseText)
|
|
1041
|
+
return;
|
|
1042
|
+
const parsed = parseTextToolCalls(responseText, this.agent.state.tools);
|
|
1043
|
+
const attempted = parsed.attempted || this._looksLikeTextToolProtocolAttempt(responseText);
|
|
1044
|
+
if (!attempted)
|
|
1045
|
+
return;
|
|
1046
|
+
this._handleTextToolProtocolParse({
|
|
1047
|
+
provider: this.agent.state.model.provider,
|
|
1048
|
+
model: this.agent.state.model.id,
|
|
1049
|
+
variant: protocol.variant,
|
|
1050
|
+
status: parsed.calls.length > 0 ? "parsed" : "failed",
|
|
1051
|
+
reason: parsed.failure,
|
|
1052
|
+
callCount: parsed.calls.length,
|
|
1053
|
+
textLength: responseText.length,
|
|
1054
|
+
});
|
|
1055
|
+
}
|
|
1056
|
+
_looksLikeTextToolProtocolAttempt(text) {
|
|
1057
|
+
return /<pi:call\b|<tool_call\b|```(?:tool|tool_call)[\s\S]*"name"\s*:/i.test(text);
|
|
1058
|
+
}
|
|
1059
|
+
_recordToolValidationBounce(event) {
|
|
1060
|
+
if (event.outcome !== "bounced" || !event.failureShape || event.failureShape.length === 0)
|
|
1061
|
+
return;
|
|
1062
|
+
this._failureCorpus.recordToolValidation({
|
|
1063
|
+
provider: event.provider ?? this.agent.state.model?.provider,
|
|
1064
|
+
modelId: event.model ?? this.agent.state.model?.id,
|
|
1065
|
+
tool: event.tool,
|
|
1066
|
+
failureModes: event.failureModes,
|
|
1067
|
+
shape: event.failureShape,
|
|
1068
|
+
errorKeywords: event.errorKeywords,
|
|
1069
|
+
});
|
|
1070
|
+
}
|
|
1071
|
+
_tagModelAdaptationRuleTeaching(event) {
|
|
1072
|
+
if (!this._toolRepairSettings().teach || event.taught !== "none")
|
|
1073
|
+
return event;
|
|
1074
|
+
const modelKey = event.provider && event.model
|
|
1075
|
+
? `${event.provider}/${event.model}`
|
|
1076
|
+
: this._modelAdaptationKeyFor(this.agent.state.model);
|
|
1077
|
+
if (!modelKey)
|
|
1078
|
+
return event;
|
|
1079
|
+
try {
|
|
1080
|
+
const rules = this._modelAdaptationStore.get(modelKey).rules;
|
|
1081
|
+
const modes = new Set([...event.failureModes, ...event.repairsApplied]);
|
|
1082
|
+
if (rules.some((rule) => modes.has(rule.mode))) {
|
|
1083
|
+
return { ...event, taught: "rule" };
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
catch {
|
|
1087
|
+
// Adaptation telemetry tagging is best-effort; leave the original event unchanged.
|
|
1088
|
+
}
|
|
1089
|
+
return event;
|
|
1090
|
+
}
|
|
1091
|
+
_repairSessionCount(modelKey, mode) {
|
|
1092
|
+
const key = `${modelKey}\0${mode}`;
|
|
1093
|
+
const count = (this._repairModeSessionCounts.get(key) ?? 0) + 1;
|
|
1094
|
+
this._repairModeSessionCounts.set(key, count);
|
|
1095
|
+
return count;
|
|
1096
|
+
}
|
|
1097
|
+
_handleModelAdaptationTelemetry(event) {
|
|
1098
|
+
if (!this._toolRepairSettings().teach || event.outcome !== "repaired" || event.repairsApplied.length === 0)
|
|
1099
|
+
return;
|
|
1100
|
+
const modelKey = event.provider && event.model
|
|
1101
|
+
? `${event.provider}/${event.model}`
|
|
1102
|
+
: this._modelAdaptationKeyFor(this.agent.state.model);
|
|
1103
|
+
if (!modelKey)
|
|
1104
|
+
return;
|
|
1105
|
+
try {
|
|
1106
|
+
for (const mode of [...new Set(event.repairsApplied)]) {
|
|
1107
|
+
const profile = this._modelAdaptationStore.get(modelKey);
|
|
1108
|
+
const stats = profile.teachStats[mode] ?? { taught: 0, recurrenceBefore: 0, recurrenceAfter: 0 };
|
|
1109
|
+
if (profile.rules.some((rule) => rule.mode === mode)) {
|
|
1110
|
+
this._modelAdaptationStore.markRuleFired(modelKey, mode);
|
|
1111
|
+
this._modelAdaptationStore.setTeachStats(modelKey, mode, {
|
|
1112
|
+
...stats,
|
|
1113
|
+
recurrenceAfter: stats.recurrenceAfter + 1,
|
|
1114
|
+
});
|
|
1115
|
+
continue;
|
|
1116
|
+
}
|
|
1117
|
+
const recurrenceBefore = stats.recurrenceBefore + 1;
|
|
1118
|
+
this._modelAdaptationStore.setTeachStats(modelKey, mode, { ...stats, recurrenceBefore });
|
|
1119
|
+
const sessionCount = this._repairSessionCount(modelKey, mode);
|
|
1120
|
+
if (sessionCount >= MODEL_ADAPTATION_REPAIR_THRESHOLD ||
|
|
1121
|
+
recurrenceBefore >= MODEL_ADAPTATION_REPAIR_THRESHOLD) {
|
|
1122
|
+
this._modelAdaptationStore.addRule(modelKey, {
|
|
1123
|
+
mode,
|
|
1124
|
+
text: formatToolRepairStandingRule(mode),
|
|
1125
|
+
});
|
|
1126
|
+
this._modelAdaptationStore.setTeachStats(modelKey, mode, {
|
|
1127
|
+
...stats,
|
|
1128
|
+
taught: stats.taught + 1,
|
|
1129
|
+
recurrenceBefore,
|
|
1130
|
+
});
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
catch {
|
|
1135
|
+
// Model adaptation is best-effort; failed telemetry persistence must not affect the turn.
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
760
1138
|
/** Tool-build call-site delegation to {@link ContextPipeline.getToolArtifactStore}. */
|
|
761
1139
|
_getToolArtifactStore() {
|
|
762
1140
|
return this._pipeline.getToolArtifactStore();
|
|
@@ -896,6 +1274,17 @@ export class AgentSession {
|
|
|
896
1274
|
formatContextCompositionDashboard() {
|
|
897
1275
|
return formatContextCompositionDashboard(this.getContextCompositionReport());
|
|
898
1276
|
}
|
|
1277
|
+
formatToolRepairHealthReport() {
|
|
1278
|
+
return formatToolRepairHealthReport(this._modelAdaptationStore);
|
|
1279
|
+
}
|
|
1280
|
+
removeToolRepairRule(model, mode) {
|
|
1281
|
+
return this._modelAdaptationStore.removeRule(model, mode);
|
|
1282
|
+
}
|
|
1283
|
+
resetToolProtocolCalibration(model) {
|
|
1284
|
+
const removed = this._modelAdaptationStore.removeProtocol(model);
|
|
1285
|
+
this._textProtocolParseFailures.delete(model);
|
|
1286
|
+
return removed;
|
|
1287
|
+
}
|
|
899
1288
|
/** Curation status for diagnostics/dashboard: settings, live telemetry, last refusal reason. */
|
|
900
1289
|
/** Curation status for diagnostics/dashboard (delegates to {@link ContextPipeline.getContextCurationStatus}). */
|
|
901
1290
|
getContextCurationStatus() {
|
|
@@ -1492,6 +1881,7 @@ export class AgentSession {
|
|
|
1492
1881
|
try {
|
|
1493
1882
|
const maxGoalLoopRounds = this.settingsManager.getAutonomySettings().maxStallTurns;
|
|
1494
1883
|
this.agent.maxStallTurns = maxGoalLoopRounds;
|
|
1884
|
+
await this._ensureTextToolProtocolForActiveModel();
|
|
1495
1885
|
let goalLoopRounds = 1;
|
|
1496
1886
|
await this.agent.prompt(messages);
|
|
1497
1887
|
while ((maxGoalLoopRounds === 0 || goalLoopRounds < maxGoalLoopRounds) && (await this._handlePostAgentRun())) {
|
|
@@ -1587,6 +1977,7 @@ export class AgentSession {
|
|
|
1587
1977
|
return this._promptUnserialized(text, options);
|
|
1588
1978
|
}
|
|
1589
1979
|
async _promptUnserialized(text, options) {
|
|
1980
|
+
this._applyToolRepairLayerSettings();
|
|
1590
1981
|
const expandPromptTemplates = options?.expandPromptTemplates ?? true;
|
|
1591
1982
|
const processSlashCommands = options?.processSlashCommands ?? expandPromptTemplates;
|
|
1592
1983
|
const preflightResult = options?.preflightResult;
|
|
@@ -1789,7 +2180,9 @@ export class AgentSession {
|
|
|
1789
2180
|
return;
|
|
1790
2181
|
}
|
|
1791
2182
|
preflightResult?.(true);
|
|
2183
|
+
this._textProtocolParseObservedThisTurn = false;
|
|
1792
2184
|
await this._modelRouter.runRoutedTurn(messages, routedTurnModel, routedTurnRouteDecision);
|
|
2185
|
+
this._recordTextToolProtocolParseOutcomeFromLastAssistant();
|
|
1793
2186
|
// R4: score whether the agent actually used the recalled context, so the recall gate can adapt.
|
|
1794
2187
|
if (injectedRecall) {
|
|
1795
2188
|
const response = this._findLastAssistantMessage();
|