@juicesharp/rpiv-advisor 2.1.0 → 2.2.0
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/advisor/execute.ts +76 -42
- package/package.json +2 -2
package/advisor/execute.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* buildAdvisorResult so the envelope is built in exactly one place.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import type { Message, StopReason, ThinkingLevel, Usage } from "@earendil-works/pi-ai";
|
|
9
|
+
import type { AssistantMessage, Message, StopReason, TextContent, ThinkingLevel, Usage } from "@earendil-works/pi-ai";
|
|
10
10
|
import {
|
|
11
11
|
type AgentToolResult,
|
|
12
12
|
type AgentToolUpdateCallback,
|
|
@@ -43,6 +43,18 @@ interface AdvisorDetails {
|
|
|
43
43
|
errorMessage?: string;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
// Extract the advisor's text content from a completeSimple response: concatenate
|
|
47
|
+
// every text part, trim. Thinking/toolCall parts are ignored. Returns "" when the
|
|
48
|
+
// model returned no text content — the empty-response class R6.4 retries once
|
|
49
|
+
// before surfacing. Pure so both attempts share one extraction path.
|
|
50
|
+
function advisorTextFromResponse(response: AssistantMessage): string {
|
|
51
|
+
return response.content
|
|
52
|
+
.filter((c): c is TextContent => c.type === "text")
|
|
53
|
+
.map((c) => c.text)
|
|
54
|
+
.join("\n")
|
|
55
|
+
.trim();
|
|
56
|
+
}
|
|
57
|
+
|
|
46
58
|
// Single result-envelope builder — every executeAdvisor branch and the pre-call
|
|
47
59
|
// error paths funnel through here. `effort` is snapshotted once at executeAdvisor
|
|
48
60
|
// entry and threaded through every call so the returned details.effort always
|
|
@@ -127,51 +139,73 @@ export async function executeAdvisor(
|
|
|
127
139
|
const requestOptions = runtimeCompleteSimple
|
|
128
140
|
? { signal, reasoning: effort }
|
|
129
141
|
: { apiKey: auth.apiKey, headers: auth.headers, signal, reasoning: effort };
|
|
130
|
-
const response = await completeSimple(
|
|
131
|
-
advisor,
|
|
132
|
-
// `tools: []` reaffirms the "never calls tools" contract even when
|
|
133
|
-
// `messages` contains prior toolCall/toolResult blocks (btw.ts:235).
|
|
134
|
-
{ systemPrompt: ADVISOR_SYSTEM_PROMPT, messages, tools: [] },
|
|
135
|
-
requestOptions,
|
|
136
|
-
);
|
|
137
|
-
|
|
138
|
-
if (response.stopReason === "aborted") {
|
|
139
|
-
return buildAdvisorResult({
|
|
140
|
-
text: ERR_CALL_ABORTED,
|
|
141
|
-
effort,
|
|
142
|
-
advisorLabel,
|
|
143
|
-
usage: response.usage,
|
|
144
|
-
stopReason: response.stopReason,
|
|
145
|
-
errorMessage: response.errorMessage ?? ERR_ABORTED_DETAIL,
|
|
146
|
-
});
|
|
147
|
-
}
|
|
148
142
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
143
|
+
// Single dispatch point — both attempts reuse the SAME `messages` and
|
|
144
|
+
// `requestOptions`, so the retry cannot diverge from attempt 1. `tools: []`
|
|
145
|
+
// reaffirms the "never calls tools" contract even when `messages` contains
|
|
146
|
+
// prior toolCall/toolResult blocks (btw.ts:235).
|
|
147
|
+
const callAdvisor = (): Promise<AssistantMessage> =>
|
|
148
|
+
completeSimple(advisor, { systemPrompt: ADVISOR_SYSTEM_PROMPT, messages, tools: [] }, requestOptions);
|
|
149
|
+
|
|
150
|
+
// Build the terminal envelope for an aborted/error stopReason, or return
|
|
151
|
+
// undefined when the attempt produced a normal stop whose text (or lack of
|
|
152
|
+
// text) the caller must still resolve. Aborted/error short-circuit and are
|
|
153
|
+
// NEVER retried — they are not the empty-response class R6.4 targets.
|
|
154
|
+
const stopReasonEnvelope = (r: AssistantMessage): AgentToolResult<AdvisorDetails> | undefined => {
|
|
155
|
+
if (r.stopReason === "aborted") {
|
|
156
|
+
return buildAdvisorResult({
|
|
157
|
+
text: ERR_CALL_ABORTED,
|
|
158
|
+
effort,
|
|
159
|
+
advisorLabel,
|
|
160
|
+
usage: r.usage,
|
|
161
|
+
stopReason: r.stopReason,
|
|
162
|
+
errorMessage: r.errorMessage ?? ERR_ABORTED_DETAIL,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
if (r.stopReason === "error") {
|
|
166
|
+
return buildAdvisorResult({
|
|
167
|
+
text: errCallFailed(r.errorMessage),
|
|
168
|
+
effort,
|
|
169
|
+
advisorLabel,
|
|
170
|
+
usage: r.usage,
|
|
171
|
+
stopReason: r.stopReason,
|
|
172
|
+
errorMessage: r.errorMessage,
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
return undefined;
|
|
176
|
+
};
|
|
159
177
|
|
|
160
|
-
|
|
161
|
-
.filter((c): c is { type: "text"; text: string } => c.type === "text")
|
|
162
|
-
.map((c) => c.text)
|
|
163
|
-
.join("\n")
|
|
164
|
-
.trim();
|
|
178
|
+
let response = await callAdvisor();
|
|
165
179
|
|
|
180
|
+
// Aborted/error short-circuit on the first attempt — no retry.
|
|
181
|
+
const firstTerminal = stopReasonEnvelope(response);
|
|
182
|
+
if (firstTerminal) return firstTerminal;
|
|
183
|
+
|
|
184
|
+
let advisorText = advisorTextFromResponse(response);
|
|
185
|
+
|
|
186
|
+
// R6.4: a transient empty advisor response (normal stop, no text) gets
|
|
187
|
+
// exactly ONE retry with identical inputs before surfacing as a terminal
|
|
188
|
+
// error. Bounded to a single second call — never a `while`/loop — so a
|
|
189
|
+
// persistent-empty provider cannot hot-loop. The retry reuses the SAME
|
|
190
|
+
// pre-computed `messages`/`requestOptions` (no re-derivation that could
|
|
191
|
+
// diverge from attempt 1), then applies the same three-way route.
|
|
166
192
|
if (!advisorText) {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
193
|
+
response = await callAdvisor();
|
|
194
|
+
|
|
195
|
+
const retryTerminal = stopReasonEnvelope(response);
|
|
196
|
+
if (retryTerminal) return retryTerminal;
|
|
197
|
+
|
|
198
|
+
advisorText = advisorTextFromResponse(response);
|
|
199
|
+
if (!advisorText) {
|
|
200
|
+
return buildAdvisorResult({
|
|
201
|
+
text: ERR_EMPTY_RESPONSE,
|
|
202
|
+
effort,
|
|
203
|
+
advisorLabel,
|
|
204
|
+
usage: response.usage,
|
|
205
|
+
stopReason: response.stopReason,
|
|
206
|
+
errorMessage: ERR_EMPTY_RESPONSE_DETAIL,
|
|
207
|
+
});
|
|
208
|
+
}
|
|
175
209
|
}
|
|
176
210
|
|
|
177
211
|
return buildAdvisorResult({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@juicesharp/rpiv-advisor",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"description": "Pi extension. A second opinion the model can request from a stronger reviewer model before it acts.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
]
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@juicesharp/rpiv-config": "^2.
|
|
48
|
+
"@juicesharp/rpiv-config": "^2.2.0",
|
|
49
49
|
"typebox": "^1.1.24"
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|