@caupulican/pi-adaptative 0.81.12 → 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 +6 -1
- package/dist/bundled-resources/skills/tool-call-repair/SKILL.md +22 -18
- 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 +28 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +215 -19
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/model-registry.d.ts +1 -0
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +6 -0
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/models/adaptation-store.d.ts +18 -1
- package/dist/core/models/adaptation-store.d.ts.map +1 -1
- package/dist/core/models/adaptation-store.js +31 -3
- package/dist/core/models/adaptation-store.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +5 -0
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/tool-repair-health.d.ts.map +1 -1
- package/dist/core/tool-repair-health.js +21 -1
- package/dist/core/tool-repair-health.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +24 -0
- 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 +8 -0
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +23 -1
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/docs/models.md +5 -1
- package/docs/rpc.md +39 -0
- package/docs/settings.md +4 -2
- package/docs/tool-repair.md +13 -5
- package/docs/usage.md +1 -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 +4 -4
|
@@ -59,6 +59,7 @@ const RAW_STREAM_MARKER = Symbol.for("pi.rawStreamSimple");
|
|
|
59
59
|
const MODEL_ADAPTATION_REPAIR_THRESHOLD = 3;
|
|
60
60
|
const TEXT_TOOL_PROTOCOL_VERSION = 1;
|
|
61
61
|
const TEXT_TOOL_PROTOCOL_TRIALS_PER_VARIANT = 2;
|
|
62
|
+
const TEXT_TOOL_PROTOCOL_PARSE_FAILURE_THRESHOLD = 3;
|
|
62
63
|
const TEXT_TOOL_PROTOCOL_VARIANTS = ["tool-tag", "tool-call", "fenced-json"];
|
|
63
64
|
const TEXT_TOOL_PROTOCOL_ECHO_TOOL = {
|
|
64
65
|
name: "echo",
|
|
@@ -148,6 +149,8 @@ export class AgentSession {
|
|
|
148
149
|
_localRuntimeController;
|
|
149
150
|
_modelAdaptationStore;
|
|
150
151
|
_repairModeSessionCounts = new Map();
|
|
152
|
+
_textProtocolParseFailures = new Map();
|
|
153
|
+
_textProtocolParseObservedThisTurn = false;
|
|
151
154
|
/** Assembles the session's base system prompt from live session state (see
|
|
152
155
|
* system-prompt-builder.ts); owns the paired _baseSystemPromptOptions. */
|
|
153
156
|
_systemPromptBuilder;
|
|
@@ -258,6 +261,7 @@ export class AgentSession {
|
|
|
258
261
|
this._cwd = config.cwd;
|
|
259
262
|
this._agentDir = config.agentDir ?? getAgentDir();
|
|
260
263
|
this._modelAdaptationStore = ModelAdaptationStore.forAgentDir(this._agentDir);
|
|
264
|
+
this.agent.onTextToolProtocolParse = (event) => this._handleTextToolProtocolParse(event);
|
|
261
265
|
this._applyToolRepairLayerSettings();
|
|
262
266
|
this._collectWorkspaceSources = config.collectWorkspaceSources ?? collectWorkspaceSources;
|
|
263
267
|
this._localRuntimeController = new LocalRuntimeController({
|
|
@@ -801,8 +805,31 @@ export class AgentSession {
|
|
|
801
805
|
return modelKey ? this._modelAdaptationStore.get(modelKey).rules : [];
|
|
802
806
|
}
|
|
803
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.
|
|
804
814
|
const override = this._toolRepairSettings().textProtocol;
|
|
805
|
-
|
|
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);
|
|
806
833
|
}
|
|
807
834
|
_textProtocolCalibrationContext(variant, token) {
|
|
808
835
|
const primer = generateTextToolProtocolPrimer([TEXT_TOOL_PROTOCOL_ECHO_TOOL], { variant });
|
|
@@ -813,15 +840,27 @@ export class AgentSession {
|
|
|
813
840
|
tools: [TEXT_TOOL_PROTOCOL_ECHO_TOOL],
|
|
814
841
|
};
|
|
815
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
|
+
}
|
|
816
856
|
async _runTextProtocolTrial(model, variant, token) {
|
|
817
|
-
const stream = await this.
|
|
857
|
+
const stream = await this._streamForToolProbe(model, this._textProtocolCalibrationContext(variant, token), {
|
|
818
858
|
textToolCallProtocol: false,
|
|
819
859
|
maxRetries: 0,
|
|
820
860
|
});
|
|
821
861
|
const message = await stream.result();
|
|
822
|
-
if (
|
|
862
|
+
if (this._messageHasEchoProbe(message, token))
|
|
823
863
|
return true;
|
|
824
|
-
}
|
|
825
864
|
const text = message.content
|
|
826
865
|
.filter((block) => block.type === "text")
|
|
827
866
|
.map((block) => block.text)
|
|
@@ -832,6 +871,32 @@ export class AgentSession {
|
|
|
832
871
|
const parsed = parseTextToolCalls(text, [TEXT_TOOL_PROTOCOL_ECHO_TOOL]);
|
|
833
872
|
return parsed.calls.some((call) => call.name === "echo" && call.arguments.data === token);
|
|
834
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
|
+
}
|
|
835
900
|
async _ensureTextToolProtocolForActiveModel() {
|
|
836
901
|
const model = this.agent.state.model;
|
|
837
902
|
if (!this._textProtocolFlag(model)) {
|
|
@@ -845,27 +910,151 @@ export class AgentSession {
|
|
|
845
910
|
}
|
|
846
911
|
const profile = this._modelAdaptationStore.get(modelKey);
|
|
847
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
|
+
}
|
|
848
919
|
this.agent.textToolCallProtocol = { variant: profile.protocol.variant };
|
|
849
920
|
return;
|
|
850
921
|
}
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
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" };
|
|
859
957
|
}
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
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 };
|
|
865
972
|
}
|
|
973
|
+
diagnostic ??= `Text protocol variants failed: ${calibrated.variantsTried.join(", ")}`;
|
|
866
974
|
}
|
|
867
|
-
|
|
868
|
-
|
|
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);
|
|
869
1058
|
}
|
|
870
1059
|
_recordToolValidationBounce(event) {
|
|
871
1060
|
if (event.outcome !== "bounced" || !event.failureShape || event.failureShape.length === 0)
|
|
@@ -1091,6 +1280,11 @@ export class AgentSession {
|
|
|
1091
1280
|
removeToolRepairRule(model, mode) {
|
|
1092
1281
|
return this._modelAdaptationStore.removeRule(model, mode);
|
|
1093
1282
|
}
|
|
1283
|
+
resetToolProtocolCalibration(model) {
|
|
1284
|
+
const removed = this._modelAdaptationStore.removeProtocol(model);
|
|
1285
|
+
this._textProtocolParseFailures.delete(model);
|
|
1286
|
+
return removed;
|
|
1287
|
+
}
|
|
1094
1288
|
/** Curation status for diagnostics/dashboard: settings, live telemetry, last refusal reason. */
|
|
1095
1289
|
/** Curation status for diagnostics/dashboard (delegates to {@link ContextPipeline.getContextCurationStatus}). */
|
|
1096
1290
|
getContextCurationStatus() {
|
|
@@ -1986,7 +2180,9 @@ export class AgentSession {
|
|
|
1986
2180
|
return;
|
|
1987
2181
|
}
|
|
1988
2182
|
preflightResult?.(true);
|
|
2183
|
+
this._textProtocolParseObservedThisTurn = false;
|
|
1989
2184
|
await this._modelRouter.runRoutedTurn(messages, routedTurnModel, routedTurnRouteDecision);
|
|
2185
|
+
this._recordTextToolProtocolParseOutcomeFromLastAssistant();
|
|
1990
2186
|
// R4: score whether the agent actually used the recalled context, so the recall gate can adapt.
|
|
1991
2187
|
if (injectedRecall) {
|
|
1992
2188
|
const response = this._findLastAssistantMessage();
|