pi-advisor-flow 0.8.0 → 0.8.1-dev.1
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 +8 -0
- package/dist/index.js +606 -41
- package/package.json +1 -1
- package/src/child-session.ts +15 -0
- package/src/commands/activation.ts +10 -3
- package/src/commands/lifecycle.ts +6 -1
- package/src/scout.ts +16 -7
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,14 @@ All notable changes to this project are documented here.
|
|
|
4
4
|
|
|
5
5
|
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0/).
|
|
6
6
|
|
|
7
|
+
## 0.8.1-dev.1 - 2026-09-23
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- Preserve a native subagent's host-selected model and thinking level when `alwaysOn` or explicit `/advisor` activation runs in a process marked with `PI_SUBAGENT_CHILD=1`.
|
|
12
|
+
- Keep marked child model selections out of the persisted parent Executor configuration.
|
|
13
|
+
- Run Advisor Scout with the marked child's active model and thinking level instead of replacing them with the configured parent Executor.
|
|
14
|
+
|
|
7
15
|
## 0.8.0 - 2026-09-22
|
|
8
16
|
|
|
9
17
|
### Added
|
package/dist/index.js
CHANGED
|
@@ -272,6 +272,16 @@ var splitRef = (ref) => {
|
|
|
272
272
|
return i === -1 ? ["openai-codex", ref] : [ref.slice(0, i), ref.slice(i + 1)];
|
|
273
273
|
};
|
|
274
274
|
|
|
275
|
+
// src/child-session.ts
|
|
276
|
+
var isMarkedSubagent = () => process.env.PI_SUBAGENT_CHILD === "1";
|
|
277
|
+
var effectiveExecutorRef = (ctx) => {
|
|
278
|
+
if (isMarkedSubagent() && ctx.model) {
|
|
279
|
+
return `${ctx.model.provider}/${ctx.model.id}`;
|
|
280
|
+
}
|
|
281
|
+
return executorRef;
|
|
282
|
+
};
|
|
283
|
+
var effectiveExecutorEffort = (ctx) => isMarkedSubagent() ? ctx.thinkingLevel : executorEffortRef;
|
|
284
|
+
|
|
275
285
|
// src/content-utils.ts
|
|
276
286
|
var isRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
277
287
|
var isRecordOf = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
@@ -2067,8 +2077,8 @@ var toolResultEntry = (message, toolResultMaxLines, toolResultMaxBytes, policies
|
|
|
2067
2077
|
return `[Tool Result for ${toolName}] (excluded by Advisor tool policy)`;
|
|
2068
2078
|
}
|
|
2069
2079
|
if (policy === "summary") {
|
|
2070
|
-
const
|
|
2071
|
-
return `[Tool Result for ${toolName}] (output omitted by Advisor tool policy: summary; status: ${status}; ${
|
|
2080
|
+
const capped2 = capToolResult(source, toolResultMaxLines, toolResultMaxBytes);
|
|
2081
|
+
return `[Tool Result for ${toolName}] (output omitted by Advisor tool policy: summary; status: ${status}; ${capped2.totalLines} lines, ${capped2.totalBytes} bytes; source output was${capped2.truncated ? "" : " not"} truncated)`;
|
|
2072
2082
|
}
|
|
2073
2083
|
const disclosed = redact ? redactSecrets(source) : source;
|
|
2074
2084
|
const capped = capToolResult(disclosed, toolResultMaxLines, toolResultMaxBytes);
|
|
@@ -2108,17 +2118,17 @@ var selectRecentEntries = (entries, maxChars) => {
|
|
|
2108
2118
|
}
|
|
2109
2119
|
const newestTruncated = "[Newest entry truncated]";
|
|
2110
2120
|
if (entries.length === 1) {
|
|
2111
|
-
const
|
|
2112
|
-
return `${
|
|
2121
|
+
const prefix2 = `${newestTruncated}${separator}`;
|
|
2122
|
+
return `${prefix2}${entries[0].slice(0, Math.max(0, maxChars - prefix2.length))}`.slice(0, maxChars);
|
|
2113
2123
|
}
|
|
2114
2124
|
const selected = [];
|
|
2115
2125
|
let selectedLength = 0;
|
|
2116
2126
|
for (let index = entries.length - 1;index >= 0; index -= 1) {
|
|
2117
2127
|
const entry = entries[index];
|
|
2118
2128
|
const candidateCount = selected.length + 1;
|
|
2119
|
-
const
|
|
2129
|
+
const omitted2 = entries.length - candidateCount;
|
|
2120
2130
|
const candidateLength = selectedLength + entry.length + (selected.length > 0 ? separator.length : 0);
|
|
2121
|
-
if (omissionMarker(
|
|
2131
|
+
if (omissionMarker(omitted2).length + separator.length + candidateLength > maxChars) {
|
|
2122
2132
|
break;
|
|
2123
2133
|
}
|
|
2124
2134
|
selected.unshift(entry);
|
|
@@ -2814,17 +2824,17 @@ var setupAbortWatch = (parentSignal, timeoutMs) => {
|
|
|
2814
2824
|
wasTimedOut: () => timedOut
|
|
2815
2825
|
};
|
|
2816
2826
|
};
|
|
2817
|
-
var streamScoutResponse = async (dependencies, resolved, manifest, parentSignal, timeoutMs, publish) => {
|
|
2827
|
+
var streamScoutResponse = async (dependencies, resolved, executorModel, executorEffort, manifest, parentSignal, timeoutMs, publish) => {
|
|
2818
2828
|
const { abortPromise, controller, teardown, wasTimedOut } = setupAbortWatch(parentSignal, timeoutMs);
|
|
2819
2829
|
try {
|
|
2820
2830
|
const collection = dependencies.collect(resolved, {
|
|
2821
2831
|
messages: [manifestMessage(manifest)],
|
|
2822
2832
|
onChunk: (thinking, text) => {
|
|
2823
2833
|
if (!controller.signal.aborted) {
|
|
2824
|
-
publish({ model:
|
|
2834
|
+
publish({ model: executorModel, text, thinking, type: "chunk" });
|
|
2825
2835
|
}
|
|
2826
2836
|
},
|
|
2827
|
-
reasoning:
|
|
2837
|
+
reasoning: executorEffort,
|
|
2828
2838
|
signal: controller.signal,
|
|
2829
2839
|
systemPrompt: SCOUT_SYSTEM
|
|
2830
2840
|
});
|
|
@@ -2853,22 +2863,24 @@ var runAdvisorScout = async (ctx, manifest, parentSignal, onEvent, timeoutMs = S
|
|
|
2853
2863
|
return { cancelled: true, ok: false };
|
|
2854
2864
|
};
|
|
2855
2865
|
const fallback = (category, message, usage) => {
|
|
2856
|
-
const
|
|
2866
|
+
const outcome2 = {
|
|
2857
2867
|
category,
|
|
2858
2868
|
message,
|
|
2859
2869
|
metrics: usage === undefined ? baseMetrics(manifest, startedAt) : { ...baseMetrics(manifest, startedAt), usage },
|
|
2860
|
-
model:
|
|
2870
|
+
model: effectiveExecutorRef(ctx),
|
|
2861
2871
|
ok: false
|
|
2862
2872
|
};
|
|
2863
|
-
publish({ outcome, type: "fallback" });
|
|
2864
|
-
return
|
|
2873
|
+
publish({ outcome: outcome2, type: "fallback" });
|
|
2874
|
+
return outcome2;
|
|
2865
2875
|
};
|
|
2866
2876
|
if (parentSignal?.aborted) {
|
|
2867
2877
|
return cancelled();
|
|
2868
2878
|
}
|
|
2879
|
+
const executorModel = effectiveExecutorRef(ctx);
|
|
2880
|
+
const executorEffort = effectiveExecutorEffort(ctx);
|
|
2869
2881
|
let resolved;
|
|
2870
2882
|
try {
|
|
2871
|
-
resolved = await dependencies.resolve(ctx,
|
|
2883
|
+
resolved = await dependencies.resolve(ctx, executorModel, "Scout");
|
|
2872
2884
|
} catch (error) {
|
|
2873
2885
|
if (parentSignal?.aborted) {
|
|
2874
2886
|
return cancelled();
|
|
@@ -2879,8 +2891,8 @@ var runAdvisorScout = async (ctx, manifest, parentSignal, onEvent, timeoutMs = S
|
|
|
2879
2891
|
if (parentSignal?.aborted) {
|
|
2880
2892
|
return cancelled();
|
|
2881
2893
|
}
|
|
2882
|
-
publish({ model:
|
|
2883
|
-
const streamed = await streamScoutResponse(dependencies, resolved, manifest, parentSignal, timeoutMs, publish);
|
|
2894
|
+
publish({ model: executorModel, type: "call" });
|
|
2895
|
+
const streamed = await streamScoutResponse(dependencies, resolved, executorModel, executorEffort, manifest, parentSignal, timeoutMs, publish);
|
|
2884
2896
|
if (!streamed.ok) {
|
|
2885
2897
|
if (parentSignal?.aborted) {
|
|
2886
2898
|
return cancelled();
|
|
@@ -2907,7 +2919,7 @@ var runAdvisorScout = async (ctx, manifest, parentSignal, onEvent, timeoutMs = S
|
|
|
2907
2919
|
]).size,
|
|
2908
2920
|
usage: snapshotAdvisorUsage(streamed.streamed.usage)
|
|
2909
2921
|
},
|
|
2910
|
-
model:
|
|
2922
|
+
model: executorModel,
|
|
2911
2923
|
ok: true,
|
|
2912
2924
|
selectedLabels: manifest.groups.filter((group) => group.required || selection.selectedIds.includes(group.id)).map((group) => group.label),
|
|
2913
2925
|
selection
|
|
@@ -2958,12 +2970,12 @@ var curateAdvisorConversation = async (ctx, legacyConversation, signal, onScout,
|
|
|
2958
2970
|
};
|
|
2959
2971
|
|
|
2960
2972
|
// src/tools/prompts.ts
|
|
2961
|
-
var advisorMessageText = (conversation, question, changes, draft, preferences,
|
|
2973
|
+
var advisorMessageText = (conversation, question, changes, draft, preferences, untracked2, tracked2) => {
|
|
2962
2974
|
const safeConversation = escapeRepositoryText(conversation);
|
|
2963
2975
|
const safeDraft = draft ? escapeRepositoryText(draft) : undefined;
|
|
2964
2976
|
const safePreferences = preferences ? escapeRepositoryText(preferences) : undefined;
|
|
2965
|
-
const safeUntracked = (
|
|
2966
|
-
const safeTracked = (
|
|
2977
|
+
const safeUntracked = (untracked2 ?? []).map(escapeRepositoryText);
|
|
2978
|
+
const safeTracked = (tracked2 ?? []).map(escapeRepositoryText);
|
|
2967
2979
|
const text = `${safeConversation ? `<conversation>
|
|
2968
2980
|
${safeConversation}
|
|
2969
2981
|
</conversation>` : ""}${changes ? `
|
|
@@ -3076,16 +3088,16 @@ var assembleConsultationContext = async (options) => {
|
|
|
3076
3088
|
const curated = await curateAdvisorConversation(ctx, legacyConversation, options.signal, options.onScout, advisorScoutEnabledRef, runAdvisorScout, options.currentInvocationId, conversationBudget);
|
|
3077
3089
|
const preferences = await readProjectPreferences(ctx, ATTACHMENT_TEXT_MAX_BYTES, advisorRedactSecretsRef);
|
|
3078
3090
|
const draftText = options.draft ? redactAndCapText(options.draft, ATTACHMENT_TEXT_MAX_BYTES, advisorRedactSecretsRef) : undefined;
|
|
3079
|
-
const
|
|
3080
|
-
const
|
|
3091
|
+
const untracked2 = await readUntrackedFiles(ctx.cwd, options.includeUntracked ?? [], advisorUntrackedContentRef, advisorRedactSecretsRef);
|
|
3092
|
+
const tracked2 = await readTrackedFiles(ctx.cwd, options.includeTracked ?? [], advisorTrackedFileContentRef, advisorRedactSecretsRef, Math.max(0, ATTACHMENTS_TOTAL_MAX_BYTES - untracked2.reduce((sum, item) => sum + item.bytes, 0)));
|
|
3081
3093
|
return {
|
|
3082
3094
|
changeText,
|
|
3083
3095
|
conversation: curated.conversation,
|
|
3084
3096
|
draftText,
|
|
3085
3097
|
preferences,
|
|
3086
3098
|
scout: curated.scout,
|
|
3087
|
-
tracked,
|
|
3088
|
-
untracked
|
|
3099
|
+
tracked: tracked2,
|
|
3100
|
+
untracked: untracked2
|
|
3089
3101
|
};
|
|
3090
3102
|
};
|
|
3091
3103
|
|
|
@@ -3149,11 +3161,11 @@ var parseAutomaticDecision = (text) => {
|
|
|
3149
3161
|
if (!subsequent) {
|
|
3150
3162
|
continue;
|
|
3151
3163
|
}
|
|
3152
|
-
const
|
|
3164
|
+
const repeated2 = namedGroups(subsequent).decision.trim().toLowerCase();
|
|
3153
3165
|
if (openingFence) {
|
|
3154
|
-
pendingFencedDecisions.push(
|
|
3166
|
+
pendingFencedDecisions.push(repeated2);
|
|
3155
3167
|
} else {
|
|
3156
|
-
decisions.push(
|
|
3168
|
+
decisions.push(repeated2);
|
|
3157
3169
|
}
|
|
3158
3170
|
}
|
|
3159
3171
|
if (openingFence) {
|
|
@@ -4173,7 +4185,7 @@ var resolveActivationModels = async (runtime, ctx) => {
|
|
|
4173
4185
|
if (!(advisorAuth.ok && advisorAuth.apiKey)) {
|
|
4174
4186
|
return { error: `No API key for Advisor ${advisorRef}` };
|
|
4175
4187
|
}
|
|
4176
|
-
if (!await runtime.setExecutorModel(executor)) {
|
|
4188
|
+
if (!isMarkedSubagent() && !await runtime.setExecutorModel(executor)) {
|
|
4177
4189
|
return { error: `No API key for Executor ${executorRef}` };
|
|
4178
4190
|
}
|
|
4179
4191
|
return {};
|
|
@@ -4219,7 +4231,7 @@ var activateAdvisor = async (runtime, args, ctx, announce = true) => {
|
|
|
4219
4231
|
saveConfig(ctx, { persistAdvisor: true, persistExecutor: true });
|
|
4220
4232
|
}
|
|
4221
4233
|
runtime.pendingExecutorModelRef = undefined;
|
|
4222
|
-
if (executorEffortRef) {
|
|
4234
|
+
if (executorEffortRef && !isMarkedSubagent()) {
|
|
4223
4235
|
runtime.pi.setThinkingLevel(executorEffortRef);
|
|
4224
4236
|
}
|
|
4225
4237
|
if (!runtime.flowEnabled()) {
|
|
@@ -4230,20 +4242,22 @@ var activateAdvisor = async (runtime, args, ctx, announce = true) => {
|
|
|
4230
4242
|
]);
|
|
4231
4243
|
}
|
|
4232
4244
|
if (announce) {
|
|
4245
|
+
const activeExecutorRef = effectiveExecutorRef(ctx);
|
|
4246
|
+
const activeExecutorEffort = effectiveExecutorEffort(ctx);
|
|
4233
4247
|
notify(ctx, `${ADVISOR_ACTIVATION_EXPLANATION}
|
|
4234
4248
|
|
|
4235
|
-
Advisor flow ready — Executor: ${
|
|
4249
|
+
Advisor flow ready — Executor: ${activeExecutorRef} (thinking: ${activeExecutorEffort || "default"}) · Advisor: ${advisorRef} (thinking: ${advisorEffortRef || "default"})`, "info");
|
|
4236
4250
|
}
|
|
4237
4251
|
};
|
|
4238
4252
|
|
|
4239
4253
|
// src/commands/lifecycle.ts
|
|
4240
|
-
var registerCommandLifecycle = (runtime,
|
|
4254
|
+
var registerCommandLifecycle = (runtime, activateAdvisor2) => {
|
|
4241
4255
|
runtime.pi.on("session_start", async (_event, ctx) => {
|
|
4242
4256
|
runtime.pendingExecutorModelRef = undefined;
|
|
4243
4257
|
try {
|
|
4244
4258
|
loadConfig(ctx);
|
|
4245
4259
|
if (alwaysOnRef) {
|
|
4246
|
-
await
|
|
4260
|
+
await activateAdvisor2("", ctx, false);
|
|
4247
4261
|
}
|
|
4248
4262
|
} catch (error) {
|
|
4249
4263
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -4251,7 +4265,7 @@ var registerCommandLifecycle = (runtime, activateAdvisor) => {
|
|
|
4251
4265
|
}
|
|
4252
4266
|
});
|
|
4253
4267
|
runtime.pi.on("model_select", (event, ctx) => {
|
|
4254
|
-
if (event.source !== "set" || runtime.suppressModelSelectionSync) {
|
|
4268
|
+
if (event.source !== "set" || runtime.suppressModelSelectionSync || isMarkedSubagent()) {
|
|
4255
4269
|
return;
|
|
4256
4270
|
}
|
|
4257
4271
|
const selected = `${event.model.provider}/${event.model.id}`;
|
|
@@ -4638,12 +4652,12 @@ var connectionFailure = (error) => {
|
|
|
4638
4652
|
failure: new JevFailureError("network", `Jev connection failed: ${message}`)
|
|
4639
4653
|
};
|
|
4640
4654
|
};
|
|
4641
|
-
var sleepWithAbort = (signal, ms) => new Promise((
|
|
4642
|
-
const timer = setTimeout(
|
|
4655
|
+
var sleepWithAbort = (signal, ms) => new Promise((resolve2) => {
|
|
4656
|
+
const timer = setTimeout(resolve2, ms);
|
|
4643
4657
|
timer.unref?.();
|
|
4644
4658
|
signal.addEventListener("abort", () => {
|
|
4645
4659
|
clearTimeout(timer);
|
|
4646
|
-
|
|
4660
|
+
resolve2();
|
|
4647
4661
|
}, { once: true });
|
|
4648
4662
|
});
|
|
4649
4663
|
|
|
@@ -5630,6 +5644,56 @@ import { getKeybindings } from "@earendil-works/pi-tui";
|
|
|
5630
5644
|
import { Key as Key2, matchesKey as matchesKey2, truncateToWidth as truncateToWidth5 } from "@earendil-works/pi-tui";
|
|
5631
5645
|
|
|
5632
5646
|
// node_modules/@typesafe-ai/sdk/dist/index.mjs
|
|
5647
|
+
var requestIdFrom = (headers) => headers.get("x-typesafe-request-id") ?? undefined;
|
|
5648
|
+
var APIPromise = class APIPromise2 extends Promise {
|
|
5649
|
+
#responsePromise;
|
|
5650
|
+
#parseResponse;
|
|
5651
|
+
#parsed;
|
|
5652
|
+
constructor(responsePromise, parseResponse) {
|
|
5653
|
+
super((resolve2) => resolve2(undefined));
|
|
5654
|
+
this.#responsePromise = responsePromise;
|
|
5655
|
+
this.#parseResponse = parseResponse;
|
|
5656
|
+
}
|
|
5657
|
+
asResponse() {
|
|
5658
|
+
return this.#responsePromise;
|
|
5659
|
+
}
|
|
5660
|
+
async withResponse() {
|
|
5661
|
+
const [data, response] = await Promise.all([this.#parse(), this.#responsePromise]);
|
|
5662
|
+
return {
|
|
5663
|
+
data,
|
|
5664
|
+
response,
|
|
5665
|
+
requestId: requestIdFrom(response.headers)
|
|
5666
|
+
};
|
|
5667
|
+
}
|
|
5668
|
+
map(fn) {
|
|
5669
|
+
return new APIPromise2(this.#responsePromise, () => this.#parse().then(fn));
|
|
5670
|
+
}
|
|
5671
|
+
#parse() {
|
|
5672
|
+
this.#parsed ??= this.#responsePromise.then(this.#parseResponse);
|
|
5673
|
+
return this.#parsed;
|
|
5674
|
+
}
|
|
5675
|
+
then(onfulfilled, onrejected) {
|
|
5676
|
+
return this.#parse().then(onfulfilled, onrejected);
|
|
5677
|
+
}
|
|
5678
|
+
catch(onrejected) {
|
|
5679
|
+
return this.#parse().catch(onrejected);
|
|
5680
|
+
}
|
|
5681
|
+
finally(onfinally) {
|
|
5682
|
+
return this.#parse().finally(onfinally);
|
|
5683
|
+
}
|
|
5684
|
+
};
|
|
5685
|
+
var ENV = {
|
|
5686
|
+
apiKey: "TYPESAFE_API_KEY",
|
|
5687
|
+
baseURL: "TYPESAFE_BASE_URL",
|
|
5688
|
+
defaultModel: "TYPESAFE_DEFAULT_MODEL",
|
|
5689
|
+
logLevel: "TYPESAFE_LOG_LEVEL"
|
|
5690
|
+
};
|
|
5691
|
+
var readEnv = (name) => {
|
|
5692
|
+
if (typeof process === "undefined" || !process.env)
|
|
5693
|
+
return;
|
|
5694
|
+
return process.env[name]?.trim() || undefined;
|
|
5695
|
+
};
|
|
5696
|
+
var fromCodeOrEnv = (fromCode, envVar) => fromCode ?? readEnv(envVar);
|
|
5633
5697
|
var range2 = (from, to) => Array.from({ length: to - from }, (_, i) => from + i);
|
|
5634
5698
|
var DEFAULT_RETRY_POLICY = {
|
|
5635
5699
|
maxRetries: 2,
|
|
@@ -5647,11 +5711,239 @@ var DEFAULT_RETRY_POLICY = {
|
|
|
5647
5711
|
apiTimeoutError: true
|
|
5648
5712
|
};
|
|
5649
5713
|
DEFAULT_RETRY_POLICY.maxRetries;
|
|
5714
|
+
var isRetryableStatus = (status, policy = DEFAULT_RETRY_POLICY) => policy.httpStatuses.has(status);
|
|
5715
|
+
var parseRetryAfter = (headers, now = Date.now()) => {
|
|
5716
|
+
const ms = Number(headers.get("retry-after-ms"));
|
|
5717
|
+
if (headers.has("retry-after-ms") && Number.isFinite(ms) && ms >= 0)
|
|
5718
|
+
return ms;
|
|
5719
|
+
const raw = headers.get("retry-after");
|
|
5720
|
+
if (raw === null)
|
|
5721
|
+
return;
|
|
5722
|
+
const seconds = Number(raw);
|
|
5723
|
+
if (Number.isFinite(seconds))
|
|
5724
|
+
return seconds >= 0 ? seconds * 1000 : undefined;
|
|
5725
|
+
const date = Date.parse(raw);
|
|
5726
|
+
if (!Number.isNaN(date))
|
|
5727
|
+
return Math.max(0, date - now);
|
|
5728
|
+
};
|
|
5729
|
+
var retryDelayMs = (attempt, headers, policy = DEFAULT_RETRY_POLICY, random = Math.random) => {
|
|
5730
|
+
if (policy.respectRetryAfter && headers !== undefined) {
|
|
5731
|
+
const retryAfter = parseRetryAfter(headers);
|
|
5732
|
+
if (retryAfter !== undefined && retryAfter <= policy.maxRetryAfterMs)
|
|
5733
|
+
return retryAfter;
|
|
5734
|
+
}
|
|
5735
|
+
const exponential = Math.min(policy.backoffInitialMs * 2 ** attempt, policy.backoffMaxMs);
|
|
5736
|
+
return Math.round(exponential * (1 - random() * policy.backoffJitter));
|
|
5737
|
+
};
|
|
5738
|
+
var sleep = (ms, signal) => new Promise((resolve2, reject) => {
|
|
5739
|
+
if (signal?.aborted)
|
|
5740
|
+
return reject(signal.reason);
|
|
5741
|
+
const onAbort = () => {
|
|
5742
|
+
clearTimeout(timer);
|
|
5743
|
+
reject(signal?.reason);
|
|
5744
|
+
};
|
|
5745
|
+
const timer = setTimeout(() => {
|
|
5746
|
+
signal?.removeEventListener("abort", onAbort);
|
|
5747
|
+
resolve2();
|
|
5748
|
+
}, ms);
|
|
5749
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
5750
|
+
});
|
|
5751
|
+
var TypeSafeError = class extends Error {
|
|
5752
|
+
constructor(message, options) {
|
|
5753
|
+
super(message, options);
|
|
5754
|
+
this.name = new.target.name;
|
|
5755
|
+
}
|
|
5756
|
+
};
|
|
5757
|
+
var isRecord2 = (value) => typeof value === "object" && value !== null;
|
|
5758
|
+
var extractMessage = (body) => {
|
|
5759
|
+
if (typeof body === "string")
|
|
5760
|
+
return body || undefined;
|
|
5761
|
+
if (!isRecord2(body))
|
|
5762
|
+
return;
|
|
5763
|
+
const { error, message, detail } = body;
|
|
5764
|
+
if (typeof error === "string")
|
|
5765
|
+
return error;
|
|
5766
|
+
if (isRecord2(error) && typeof error.message === "string")
|
|
5767
|
+
return error.message;
|
|
5768
|
+
if (typeof message === "string")
|
|
5769
|
+
return message;
|
|
5770
|
+
if (typeof detail === "string")
|
|
5771
|
+
return detail;
|
|
5772
|
+
if (isRecord2(detail) && typeof detail.message === "string")
|
|
5773
|
+
return detail.message;
|
|
5774
|
+
if (Array.isArray(detail))
|
|
5775
|
+
return describeValidationErrors(detail);
|
|
5776
|
+
};
|
|
5777
|
+
var describeValidationErrors = (errors) => {
|
|
5778
|
+
const parts = errors.flatMap((e) => {
|
|
5779
|
+
if (!isRecord2(e) || typeof e.msg !== "string")
|
|
5780
|
+
return [];
|
|
5781
|
+
const loc = Array.isArray(e.loc) ? e.loc.filter((x) => x !== "body").join(".") : "";
|
|
5782
|
+
return [loc ? `${loc}: ${e.msg}` : e.msg];
|
|
5783
|
+
});
|
|
5784
|
+
return parts.length > 0 ? parts.join("; ") : undefined;
|
|
5785
|
+
};
|
|
5786
|
+
var MAX_RAW_BODY_IN_MESSAGE = 200;
|
|
5787
|
+
var APIError = class APIError2 extends TypeSafeError {
|
|
5788
|
+
status;
|
|
5789
|
+
headers;
|
|
5790
|
+
body;
|
|
5791
|
+
requestId;
|
|
5792
|
+
constructor(status, body, headers, message) {
|
|
5793
|
+
super(message ?? APIError2.describe(status, body));
|
|
5794
|
+
this.status = status;
|
|
5795
|
+
this.body = body;
|
|
5796
|
+
this.headers = headers;
|
|
5797
|
+
this.requestId = requestIdFrom(headers);
|
|
5798
|
+
}
|
|
5799
|
+
static describe(status, body) {
|
|
5800
|
+
const detail = extractMessage(body);
|
|
5801
|
+
if (detail)
|
|
5802
|
+
return `${status} ${detail}`;
|
|
5803
|
+
if (body === undefined)
|
|
5804
|
+
return `${status} status code (no body)`;
|
|
5805
|
+
const raw = typeof body === "string" ? body : JSON.stringify(body);
|
|
5806
|
+
return `${status} ${raw.length > MAX_RAW_BODY_IN_MESSAGE ? `${raw.slice(0, MAX_RAW_BODY_IN_MESSAGE)}…` : raw}`;
|
|
5807
|
+
}
|
|
5808
|
+
static fromResponse(status, body, headers) {
|
|
5809
|
+
if (status === 400)
|
|
5810
|
+
return new BadRequestError(status, body, headers);
|
|
5811
|
+
if (status === 401)
|
|
5812
|
+
return new AuthenticationError(status, body, headers);
|
|
5813
|
+
if (status === 403)
|
|
5814
|
+
return new PermissionDeniedError(status, body, headers);
|
|
5815
|
+
if (status === 404)
|
|
5816
|
+
return new NotFoundError(status, body, headers);
|
|
5817
|
+
if (status === 422)
|
|
5818
|
+
return new UnprocessableEntityError(status, body, headers);
|
|
5819
|
+
if (status === 429)
|
|
5820
|
+
return new RateLimitError(status, body, headers);
|
|
5821
|
+
if (status >= 500)
|
|
5822
|
+
return new InternalServerError(status, body, headers);
|
|
5823
|
+
return new APIError2(status, body, headers);
|
|
5824
|
+
}
|
|
5825
|
+
};
|
|
5826
|
+
var BadRequestError = class extends APIError {
|
|
5827
|
+
};
|
|
5828
|
+
var AuthenticationError = class extends APIError {
|
|
5829
|
+
};
|
|
5830
|
+
var PermissionDeniedError = class extends APIError {
|
|
5831
|
+
};
|
|
5832
|
+
var NotFoundError = class extends APIError {
|
|
5833
|
+
};
|
|
5834
|
+
var UnprocessableEntityError = class extends APIError {
|
|
5835
|
+
};
|
|
5836
|
+
var RateLimitError = class extends APIError {
|
|
5837
|
+
retryAfterMs = parseRetryAfter(this.headers);
|
|
5838
|
+
};
|
|
5839
|
+
var InternalServerError = class extends APIError {
|
|
5840
|
+
};
|
|
5841
|
+
var APIConnectionError = class extends TypeSafeError {
|
|
5842
|
+
constructor(message = "Connection error.", options) {
|
|
5843
|
+
super(message, options);
|
|
5844
|
+
}
|
|
5845
|
+
};
|
|
5846
|
+
var APITimeoutError = class extends APIConnectionError {
|
|
5847
|
+
timeoutMs;
|
|
5848
|
+
constructor(timeoutMs, options) {
|
|
5849
|
+
super(`Request timed out after ${timeoutMs}ms.`, options);
|
|
5850
|
+
this.timeoutMs = timeoutMs;
|
|
5851
|
+
}
|
|
5852
|
+
};
|
|
5853
|
+
var APIUserAbortError = class extends TypeSafeError {
|
|
5854
|
+
constructor(message = "Request was aborted.", options) {
|
|
5855
|
+
super(message, options);
|
|
5856
|
+
}
|
|
5857
|
+
};
|
|
5858
|
+
var LOG_LEVELS = [
|
|
5859
|
+
"debug",
|
|
5860
|
+
"info",
|
|
5861
|
+
"warn",
|
|
5862
|
+
"error",
|
|
5863
|
+
"off"
|
|
5864
|
+
];
|
|
5865
|
+
var DEFAULT_LOG_LEVEL = "warn";
|
|
5866
|
+
var isLogLevel = (value) => LOG_LEVELS.includes(value);
|
|
5867
|
+
var parseLogLevel = (value, source) => {
|
|
5868
|
+
if (isLogLevel(value))
|
|
5869
|
+
return value;
|
|
5870
|
+
throw new TypeSafeError(`Invalid log level "${value}" from ${source}. Expected one of: ${LOG_LEVELS.join(", ")}.`);
|
|
5871
|
+
};
|
|
5872
|
+
var PREFIX = "[typesafe-sdk]";
|
|
5873
|
+
var consoleLogger = {
|
|
5874
|
+
debug: (message, ...args) => console.debug(`${PREFIX} ${message}`, ...args),
|
|
5875
|
+
info: (message, ...args) => console.info(`${PREFIX} ${message}`, ...args),
|
|
5876
|
+
warn: (message, ...args) => console.warn(`${PREFIX} ${message}`, ...args),
|
|
5877
|
+
error: (message, ...args) => console.error(`${PREFIX} ${message}`, ...args)
|
|
5878
|
+
};
|
|
5879
|
+
var RANK = {
|
|
5880
|
+
debug: 0,
|
|
5881
|
+
info: 1,
|
|
5882
|
+
warn: 2,
|
|
5883
|
+
error: 3,
|
|
5884
|
+
off: 4
|
|
5885
|
+
};
|
|
5886
|
+
var drop = () => {};
|
|
5887
|
+
var withLevel = (sink, level) => {
|
|
5888
|
+
const enabled = (at) => RANK[at] >= RANK[level];
|
|
5889
|
+
return {
|
|
5890
|
+
debug: enabled("debug") ? (message, ...args) => sink.debug(message, ...args) : drop,
|
|
5891
|
+
info: enabled("info") ? (message, ...args) => sink.info(message, ...args) : drop,
|
|
5892
|
+
warn: enabled("warn") ? (message, ...args) => sink.warn(message, ...args) : drop,
|
|
5893
|
+
error: enabled("error") ? (message, ...args) => sink.error(message, ...args) : drop
|
|
5894
|
+
};
|
|
5895
|
+
};
|
|
5896
|
+
var KEY_HEADERS = /* @__PURE__ */ new Set([
|
|
5897
|
+
"authorization",
|
|
5898
|
+
"proxy-authorization",
|
|
5899
|
+
"x-api-key"
|
|
5900
|
+
]);
|
|
5901
|
+
var OPAQUE_HEADERS = /* @__PURE__ */ new Set(["cookie", "set-cookie"]);
|
|
5902
|
+
var redactKey = (value) => {
|
|
5903
|
+
const [scheme, secret] = value.includes(" ") ? value.split(/\s+/, 2) : [undefined, value];
|
|
5904
|
+
const tail = secret && secret.length > 8 ? secret.slice(-4) : "";
|
|
5905
|
+
return `${scheme ? `${scheme} ` : ""}***${tail}`;
|
|
5906
|
+
};
|
|
5907
|
+
var redact = (name, value) => {
|
|
5908
|
+
const lower = name.toLowerCase();
|
|
5909
|
+
if (KEY_HEADERS.has(lower))
|
|
5910
|
+
return redactKey(value);
|
|
5911
|
+
if (OPAQUE_HEADERS.has(lower))
|
|
5912
|
+
return "***";
|
|
5913
|
+
return value;
|
|
5914
|
+
};
|
|
5915
|
+
var redactHeaders = (headers) => Object.fromEntries(Object.entries(headers).map(([name, value]) => [name, redact(name, value)]));
|
|
5650
5916
|
var noul = (instructions = null, criteria) => ({
|
|
5651
5917
|
type: "noul",
|
|
5652
5918
|
instructions,
|
|
5653
5919
|
criteria
|
|
5654
5920
|
});
|
|
5921
|
+
var validateQuestions = (questions) => {
|
|
5922
|
+
if (Object.keys(questions).length === 0)
|
|
5923
|
+
throw new TypeSafeError("At least one question is required.");
|
|
5924
|
+
for (const [name, question] of Object.entries(questions)) {
|
|
5925
|
+
if (question.type !== "score")
|
|
5926
|
+
continue;
|
|
5927
|
+
if (!Array.isArray(question.criteria))
|
|
5928
|
+
throw new TypeSafeError(`Score question "${name}" has criteria that are not a list; score criteria must be a list of descriptions indexed by score from zero.`);
|
|
5929
|
+
if (question.criteria.length < 2)
|
|
5930
|
+
throw new TypeSafeError(`Score question "${name}" has ${question.criteria.length} criteria; at least two scores are required.`);
|
|
5931
|
+
}
|
|
5932
|
+
};
|
|
5933
|
+
var Models = class {
|
|
5934
|
+
#transport;
|
|
5935
|
+
constructor(transport) {
|
|
5936
|
+
this.#transport = transport;
|
|
5937
|
+
}
|
|
5938
|
+
list(options = {}) {
|
|
5939
|
+
return this.#transport.request("GET", "/v1/models", options).map(unwrapModels);
|
|
5940
|
+
}
|
|
5941
|
+
};
|
|
5942
|
+
var unwrapModels = (wire) => {
|
|
5943
|
+
if (Array.isArray(wire?.models))
|
|
5944
|
+
return wire.models;
|
|
5945
|
+
throw new TypeSafeError("Unexpected response shape from GET /v1/models; expected { models: [...] }.");
|
|
5946
|
+
};
|
|
5655
5947
|
var g = globalThis;
|
|
5656
5948
|
var isBrowser = () => typeof g.window !== "undefined" && typeof g.window.document !== "undefined" && typeof g.navigator !== "undefined";
|
|
5657
5949
|
var describeRuntime = () => {
|
|
@@ -5670,7 +5962,280 @@ var describeRuntime = () => {
|
|
|
5670
5962
|
return "browser";
|
|
5671
5963
|
return "unknown";
|
|
5672
5964
|
};
|
|
5965
|
+
var VERSION = "0.6.0";
|
|
5966
|
+
var missingApiKey = () => {
|
|
5967
|
+
throw new TypeSafeError(`No API key was provided. Pass \`apiKey\` to the TypeSafeClient constructor or set the ${ENV.apiKey} environment variable.`);
|
|
5968
|
+
};
|
|
5969
|
+
var missingFetch = () => {
|
|
5970
|
+
throw new TypeSafeError("No global `fetch` is available in this runtime. Pass a `fetch` implementation to the TypeSafeClient constructor.");
|
|
5971
|
+
};
|
|
5972
|
+
var refuseBrowser = () => {
|
|
5973
|
+
throw new TypeSafeError("TypeSafeClient is running in a browser, which would expose your API key to anyone using the page. Call the API from a server instead, or pass `dangerouslyAllowBrowser: true` if you understand the risk.");
|
|
5974
|
+
};
|
|
5975
|
+
var defaultFetch = (input, init) => globalThis.fetch(input, init);
|
|
5976
|
+
var assertNonNegativeInteger = (name, value) => {
|
|
5977
|
+
if (!Number.isInteger(value) || value < 0)
|
|
5978
|
+
throw new TypeSafeError(`\`${name}\` must be a non-negative integer, got ${String(value)}.`);
|
|
5979
|
+
return value;
|
|
5980
|
+
};
|
|
5981
|
+
var assertPositiveMs = (name, value) => {
|
|
5982
|
+
if (!Number.isFinite(value) || value <= 0)
|
|
5983
|
+
throw new TypeSafeError(`\`${name}\` must be a positive number of milliseconds, got ${String(value)}.`);
|
|
5984
|
+
return value;
|
|
5985
|
+
};
|
|
5986
|
+
var assertNonNegativeMs = (name, value) => {
|
|
5987
|
+
if (!Number.isFinite(value) || value < 0)
|
|
5988
|
+
throw new TypeSafeError(`\`${name}\` must be a non-negative number of milliseconds, got ${String(value)}.`);
|
|
5989
|
+
return value;
|
|
5990
|
+
};
|
|
5991
|
+
var assertFraction = (name, value) => {
|
|
5992
|
+
if (!Number.isFinite(value) || value < 0 || value > 1)
|
|
5993
|
+
throw new TypeSafeError(`\`${name}\` must be between 0 and 1, got ${String(value)}.`);
|
|
5994
|
+
return value;
|
|
5995
|
+
};
|
|
5996
|
+
var assertStatusSet = (name, statuses) => {
|
|
5997
|
+
for (const status of statuses)
|
|
5998
|
+
if (!Number.isInteger(status) || status < 100 || status > 999)
|
|
5999
|
+
throw new TypeSafeError(`\`${name}\` must contain HTTP status codes, got ${String(status)}.`);
|
|
6000
|
+
return statuses;
|
|
6001
|
+
};
|
|
6002
|
+
var resolveRetryPolicy = (base, overrides) => {
|
|
6003
|
+
const o = overrides ?? {};
|
|
6004
|
+
return {
|
|
6005
|
+
maxRetries: o.maxRetries === undefined ? base.maxRetries : assertNonNegativeInteger("retry.maxRetries", o.maxRetries),
|
|
6006
|
+
backoffInitialMs: o.backoffInitialMs === undefined ? base.backoffInitialMs : assertNonNegativeMs("retry.backoffInitialMs", o.backoffInitialMs),
|
|
6007
|
+
backoffMaxMs: o.backoffMaxMs === undefined ? base.backoffMaxMs : assertNonNegativeMs("retry.backoffMaxMs", o.backoffMaxMs),
|
|
6008
|
+
backoffJitter: o.backoffJitter === undefined ? base.backoffJitter : assertFraction("retry.backoffJitter", o.backoffJitter),
|
|
6009
|
+
httpStatuses: new Set(o.httpStatuses === undefined ? base.httpStatuses : assertStatusSet("retry.httpStatuses", o.httpStatuses)),
|
|
6010
|
+
respectRetryAfter: o.respectRetryAfter ?? base.respectRetryAfter,
|
|
6011
|
+
maxRetryAfterMs: o.maxRetryAfterMs === undefined ? base.maxRetryAfterMs : assertNonNegativeMs("retry.maxRetryAfterMs", o.maxRetryAfterMs),
|
|
6012
|
+
apiConnectionError: o.apiConnectionError ?? base.apiConnectionError,
|
|
6013
|
+
apiTimeoutError: o.apiTimeoutError ?? base.apiTimeoutError
|
|
6014
|
+
};
|
|
6015
|
+
};
|
|
6016
|
+
var isRetryableError = (err, policy) => {
|
|
6017
|
+
if (err instanceof APITimeoutError)
|
|
6018
|
+
return policy.apiTimeoutError;
|
|
6019
|
+
if (err instanceof APIConnectionError)
|
|
6020
|
+
return policy.apiConnectionError;
|
|
6021
|
+
return false;
|
|
6022
|
+
};
|
|
6023
|
+
var resolveLogLevel = (fromCode) => {
|
|
6024
|
+
if (fromCode !== undefined)
|
|
6025
|
+
return parseLogLevel(fromCode, "the `logLevel` option");
|
|
6026
|
+
const fromEnv = readEnv(ENV.logLevel);
|
|
6027
|
+
if (fromEnv !== undefined)
|
|
6028
|
+
return parseLogLevel(fromEnv, ENV.logLevel);
|
|
6029
|
+
return DEFAULT_LOG_LEVEL;
|
|
6030
|
+
};
|
|
6031
|
+
var stripTrailingSlashes = (url) => url.replace(/\/+$/, "");
|
|
6032
|
+
var mergeHeaders = (...sources) => {
|
|
6033
|
+
const entries = /* @__PURE__ */ new Map;
|
|
6034
|
+
for (const source of sources)
|
|
6035
|
+
for (const [name, value] of Object.entries(source))
|
|
6036
|
+
if (value === undefined)
|
|
6037
|
+
entries.delete(name.toLowerCase());
|
|
6038
|
+
else
|
|
6039
|
+
entries.set(name.toLowerCase(), [name, value]);
|
|
6040
|
+
return Object.fromEntries(entries.values());
|
|
6041
|
+
};
|
|
6042
|
+
var bufferResponse = async (response, signal) => {
|
|
6043
|
+
const reader = response.clone().body?.getReader();
|
|
6044
|
+
if (!reader)
|
|
6045
|
+
return;
|
|
6046
|
+
const cancel = () => {
|
|
6047
|
+
reader.cancel(signal.reason).catch(() => {});
|
|
6048
|
+
response.body?.cancel(signal.reason).catch(() => {});
|
|
6049
|
+
};
|
|
6050
|
+
signal.addEventListener("abort", cancel, { once: true });
|
|
6051
|
+
try {
|
|
6052
|
+
if (signal.aborted)
|
|
6053
|
+
cancel();
|
|
6054
|
+
signal.throwIfAborted();
|
|
6055
|
+
while (!(await reader.read()).done)
|
|
6056
|
+
signal.throwIfAborted();
|
|
6057
|
+
signal.throwIfAborted();
|
|
6058
|
+
} finally {
|
|
6059
|
+
signal.removeEventListener("abort", cancel);
|
|
6060
|
+
reader.releaseLock();
|
|
6061
|
+
}
|
|
6062
|
+
};
|
|
5673
6063
|
var RUNTIME = describeRuntime();
|
|
6064
|
+
var TypeSafeClient = class {
|
|
6065
|
+
#apiKey;
|
|
6066
|
+
baseURL;
|
|
6067
|
+
defaultModel;
|
|
6068
|
+
logLevel;
|
|
6069
|
+
logger;
|
|
6070
|
+
retry;
|
|
6071
|
+
timeout;
|
|
6072
|
+
defaultHeaders;
|
|
6073
|
+
fetch;
|
|
6074
|
+
models;
|
|
6075
|
+
#requestCount = 0;
|
|
6076
|
+
constructor(config = {}) {
|
|
6077
|
+
if (isBrowser() && !config.dangerouslyAllowBrowser)
|
|
6078
|
+
refuseBrowser();
|
|
6079
|
+
this.#apiKey = fromCodeOrEnv(config.apiKey, ENV.apiKey) ?? missingApiKey();
|
|
6080
|
+
this.baseURL = stripTrailingSlashes(fromCodeOrEnv(config.baseURL, ENV.baseURL) ?? "https://api.typesafe.ai");
|
|
6081
|
+
this.defaultModel = fromCodeOrEnv(config.defaultModel, ENV.defaultModel) ?? "jev-latest";
|
|
6082
|
+
this.logLevel = resolveLogLevel(config.logLevel);
|
|
6083
|
+
this.logger = withLevel(config.logger ?? consoleLogger, this.logLevel);
|
|
6084
|
+
this.retry = resolveRetryPolicy(DEFAULT_RETRY_POLICY, config.retry);
|
|
6085
|
+
this.timeout = assertPositiveMs("timeout", config.timeout ?? 1e4);
|
|
6086
|
+
this.defaultHeaders = { ...config.defaultHeaders };
|
|
6087
|
+
if (config.fetch === undefined && typeof globalThis.fetch !== "function")
|
|
6088
|
+
missingFetch();
|
|
6089
|
+
this.fetch = config.fetch ?? defaultFetch;
|
|
6090
|
+
const transport = {
|
|
6091
|
+
request: (method, path, options) => this.#request(method, path, options),
|
|
6092
|
+
defaultModel: this.defaultModel
|
|
6093
|
+
};
|
|
6094
|
+
this.models = new Models(transport);
|
|
6095
|
+
}
|
|
6096
|
+
systemOne(request, options = {}) {
|
|
6097
|
+
validateQuestions(request.questions);
|
|
6098
|
+
const body = {
|
|
6099
|
+
...request,
|
|
6100
|
+
model: request.model ?? this.defaultModel
|
|
6101
|
+
};
|
|
6102
|
+
return this.#request("POST", "/v1/systemone", {
|
|
6103
|
+
...options,
|
|
6104
|
+
body
|
|
6105
|
+
});
|
|
6106
|
+
}
|
|
6107
|
+
#request(method, path, options = {}) {
|
|
6108
|
+
const resolved = {
|
|
6109
|
+
method,
|
|
6110
|
+
path,
|
|
6111
|
+
body: options.body,
|
|
6112
|
+
headers: mergeHeaders(this.defaultHeaders, options.headers ?? {}),
|
|
6113
|
+
signal: options.signal,
|
|
6114
|
+
timeout: options.timeout === undefined ? this.timeout : assertPositiveMs("timeout", options.timeout),
|
|
6115
|
+
retry: resolveRetryPolicy(this.retry, options.retry)
|
|
6116
|
+
};
|
|
6117
|
+
const tag = `#${++this.#requestCount} ${method} ${path}`;
|
|
6118
|
+
return new APIPromise(this.fetchWithRetries(tag, resolved), async (res) => {
|
|
6119
|
+
const parsed = await parseBody(res);
|
|
6120
|
+
this.logger.debug(`${tag} <- body`, parsed);
|
|
6121
|
+
return parsed;
|
|
6122
|
+
});
|
|
6123
|
+
}
|
|
6124
|
+
async fetchWithRetries(tag, req) {
|
|
6125
|
+
const url = `${this.baseURL}${req.path}`;
|
|
6126
|
+
const headers = mergeHeaders(req.headers, {
|
|
6127
|
+
Authorization: `Bearer ${this.#apiKey}`,
|
|
6128
|
+
Accept: "application/json",
|
|
6129
|
+
"User-Agent": `typesafe-sdk/${VERSION}`,
|
|
6130
|
+
"X-TypeSafe-SDK": `typesafe-sdk/${VERSION}`,
|
|
6131
|
+
"X-TypeSafe-Runtime": RUNTIME,
|
|
6132
|
+
"Content-Type": req.body === undefined ? undefined : "application/json",
|
|
6133
|
+
"X-TypeSafe-Retry-Count": undefined
|
|
6134
|
+
});
|
|
6135
|
+
const body = req.body === undefined ? undefined : JSON.stringify(req.body);
|
|
6136
|
+
for (let attempt = 0;; attempt++) {
|
|
6137
|
+
const retriesLeft = req.retry.maxRetries - attempt;
|
|
6138
|
+
const attemptHeaders = attempt === 0 ? headers : {
|
|
6139
|
+
...headers,
|
|
6140
|
+
"X-TypeSafe-Retry-Count": String(attempt)
|
|
6141
|
+
};
|
|
6142
|
+
this.logger.debug(`${tag} -> ${url}`, {
|
|
6143
|
+
headers: redactHeaders(attemptHeaders),
|
|
6144
|
+
body: req.body
|
|
6145
|
+
});
|
|
6146
|
+
const started = Date.now();
|
|
6147
|
+
let res;
|
|
6148
|
+
try {
|
|
6149
|
+
res = await this.attempt(tag, url, {
|
|
6150
|
+
method: req.method,
|
|
6151
|
+
headers: attemptHeaders,
|
|
6152
|
+
body
|
|
6153
|
+
}, req);
|
|
6154
|
+
} catch (err) {
|
|
6155
|
+
if (err instanceof APIUserAbortError || retriesLeft <= 0)
|
|
6156
|
+
throw err;
|
|
6157
|
+
if (!isRetryableError(err, req.retry))
|
|
6158
|
+
throw err;
|
|
6159
|
+
await this.backOff(tag, attempt, retriesLeft, err.message, undefined, req);
|
|
6160
|
+
continue;
|
|
6161
|
+
}
|
|
6162
|
+
const requestId = requestIdFrom(res.headers);
|
|
6163
|
+
this.logger.info(`${tag} <- ${res.status} in ${Date.now() - started}ms${requestId ? ` (request ${requestId})` : ""}`);
|
|
6164
|
+
if (res.ok)
|
|
6165
|
+
return res;
|
|
6166
|
+
const errorBody = await parseBody(res);
|
|
6167
|
+
this.logger.debug(`${tag} <- error body`, errorBody);
|
|
6168
|
+
const error = APIError.fromResponse(res.status, errorBody, res.headers);
|
|
6169
|
+
if (retriesLeft <= 0 || !isRetryableStatus(res.status, req.retry))
|
|
6170
|
+
throw error;
|
|
6171
|
+
await this.backOff(tag, attempt, retriesLeft, `${res.status}`, res.headers, req);
|
|
6172
|
+
}
|
|
6173
|
+
}
|
|
6174
|
+
async attempt(tag, url, init, { signal, timeout }) {
|
|
6175
|
+
const controller = new AbortController;
|
|
6176
|
+
const abortFromCaller = () => controller.abort(signal?.reason);
|
|
6177
|
+
if (signal?.aborted)
|
|
6178
|
+
abortFromCaller();
|
|
6179
|
+
signal?.addEventListener("abort", abortFromCaller, { once: true });
|
|
6180
|
+
let timedOut = false;
|
|
6181
|
+
const timer = setTimeout(() => {
|
|
6182
|
+
timedOut = true;
|
|
6183
|
+
controller.abort();
|
|
6184
|
+
}, timeout);
|
|
6185
|
+
const started = Date.now();
|
|
6186
|
+
const elapsed = () => `${Date.now() - started}ms`;
|
|
6187
|
+
try {
|
|
6188
|
+
const response = await this.fetch(url, {
|
|
6189
|
+
...init,
|
|
6190
|
+
signal: controller.signal
|
|
6191
|
+
});
|
|
6192
|
+
await bufferResponse(response, controller.signal);
|
|
6193
|
+
return response;
|
|
6194
|
+
} catch (err) {
|
|
6195
|
+
if (signal?.aborted) {
|
|
6196
|
+
this.logger.info(`${tag} aborted by caller after ${elapsed()}`);
|
|
6197
|
+
throw new APIUserAbortError(undefined, { cause: err });
|
|
6198
|
+
}
|
|
6199
|
+
if (timedOut) {
|
|
6200
|
+
this.logger.info(`${tag} timed out after ${elapsed()}`);
|
|
6201
|
+
throw new APITimeoutError(timeout, { cause: err });
|
|
6202
|
+
}
|
|
6203
|
+
this.logger.info(`${tag} connection error after ${elapsed()}`, err);
|
|
6204
|
+
throw new APIConnectionError(err instanceof Error ? `Connection error: ${err.message}` : undefined, { cause: err });
|
|
6205
|
+
} finally {
|
|
6206
|
+
clearTimeout(timer);
|
|
6207
|
+
signal?.removeEventListener("abort", abortFromCaller);
|
|
6208
|
+
}
|
|
6209
|
+
}
|
|
6210
|
+
async backOff(tag, attempt, retriesLeft, reason, headers, { retry, signal }) {
|
|
6211
|
+
const delay = retryDelayMs(attempt, headers, retry);
|
|
6212
|
+
const nth = attempt + 1;
|
|
6213
|
+
const total = attempt + retriesLeft;
|
|
6214
|
+
this.logger.info(`${tag} retrying in ${delay}ms (retry ${nth}/${total}) after ${reason}`);
|
|
6215
|
+
try {
|
|
6216
|
+
await sleep(delay, signal);
|
|
6217
|
+
} catch (err) {
|
|
6218
|
+
this.logger.info(`${tag} aborted by caller while waiting to retry`);
|
|
6219
|
+
throw new APIUserAbortError(undefined, { cause: err });
|
|
6220
|
+
}
|
|
6221
|
+
}
|
|
6222
|
+
};
|
|
6223
|
+
var parseBody = async (res) => {
|
|
6224
|
+
const text = await res.text();
|
|
6225
|
+
if (text.length === 0)
|
|
6226
|
+
return;
|
|
6227
|
+
if ((res.headers.get("content-type") ?? "").includes("application/json"))
|
|
6228
|
+
try {
|
|
6229
|
+
return JSON.parse(text);
|
|
6230
|
+
} catch {
|
|
6231
|
+
return text;
|
|
6232
|
+
}
|
|
6233
|
+
try {
|
|
6234
|
+
return JSON.parse(text);
|
|
6235
|
+
} catch {
|
|
6236
|
+
return text;
|
|
6237
|
+
}
|
|
6238
|
+
};
|
|
5674
6239
|
|
|
5675
6240
|
// src/ui/masked-input.ts
|
|
5676
6241
|
import { Input as Input2, truncateToWidth as truncateToWidth4 } from "@earendil-works/pi-tui";
|
|
@@ -6773,7 +7338,7 @@ import {
|
|
|
6773
7338
|
writeFile
|
|
6774
7339
|
} from "node:fs/promises";
|
|
6775
7340
|
import { join as join5 } from "node:path";
|
|
6776
|
-
import { setTimeout as
|
|
7341
|
+
import { setTimeout as sleep2 } from "node:timers/promises";
|
|
6777
7342
|
import { getAgentDir as getAgentDir4 } from "@earendil-works/pi-coding-agent";
|
|
6778
7343
|
var ADOPTIONS = [
|
|
6779
7344
|
"followed",
|
|
@@ -6861,7 +7426,7 @@ var withOutcomeLock = async (run) => {
|
|
|
6861
7426
|
}
|
|
6862
7427
|
continue;
|
|
6863
7428
|
}
|
|
6864
|
-
await
|
|
7429
|
+
await sleep2(5);
|
|
6865
7430
|
}
|
|
6866
7431
|
}
|
|
6867
7432
|
throw new Error("Timed out waiting to append an Advisor outcome.");
|
|
@@ -7763,8 +8328,8 @@ function registerPiAdvisor(pi) {
|
|
|
7763
8328
|
});
|
|
7764
8329
|
}
|
|
7765
8330
|
export {
|
|
7766
|
-
|
|
7767
|
-
registerPiAdvisor as default,
|
|
8331
|
+
runAdvisorGate2 as runAdvisorGate,
|
|
7768
8332
|
parseAutomaticDecision2 as parseAutomaticDecision,
|
|
7769
|
-
|
|
8333
|
+
registerPiAdvisor as default,
|
|
8334
|
+
consultAdvisor2 as consultAdvisor
|
|
7770
8335
|
};
|
package/package.json
CHANGED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
|
|
3
|
+
import { executorEffortRef, executorRef } from "./config/state.ts";
|
|
4
|
+
|
|
5
|
+
export const isMarkedSubagent = () => process.env.PI_SUBAGENT_CHILD === "1";
|
|
6
|
+
|
|
7
|
+
export const effectiveExecutorRef = (ctx: ExtensionContext) => {
|
|
8
|
+
if (isMarkedSubagent() && ctx.model) {
|
|
9
|
+
return `${ctx.model.provider}/${ctx.model.id}`;
|
|
10
|
+
}
|
|
11
|
+
return executorRef;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const effectiveExecutorEffort = (ctx: ExtensionContext) =>
|
|
15
|
+
isMarkedSubagent() ? ctx.thinkingLevel : executorEffortRef;
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
|
|
3
|
+
import {
|
|
4
|
+
effectiveExecutorEffort,
|
|
5
|
+
effectiveExecutorRef,
|
|
6
|
+
isMarkedSubagent,
|
|
7
|
+
} from "../child-session.ts";
|
|
3
8
|
import { parseArgs } from "../config/args.ts";
|
|
4
9
|
import {
|
|
5
10
|
advisorEffortRef,
|
|
@@ -51,7 +56,7 @@ const resolveActivationModels = async (
|
|
|
51
56
|
if (!(advisorAuth.ok && advisorAuth.apiKey)) {
|
|
52
57
|
return { error: `No API key for Advisor ${advisorRef}` };
|
|
53
58
|
}
|
|
54
|
-
if (!(await runtime.setExecutorModel(executor))) {
|
|
59
|
+
if (!isMarkedSubagent() && !(await runtime.setExecutorModel(executor))) {
|
|
55
60
|
return { error: `No API key for Executor ${executorRef}` };
|
|
56
61
|
}
|
|
57
62
|
return {};
|
|
@@ -116,7 +121,7 @@ export const activateAdvisor = async (
|
|
|
116
121
|
// an older inactive selection override an explicit activation argument on a
|
|
117
122
|
// later attempt.
|
|
118
123
|
runtime.pendingExecutorModelRef = undefined;
|
|
119
|
-
if (executorEffortRef) {
|
|
124
|
+
if (executorEffortRef && !isMarkedSubagent()) {
|
|
120
125
|
// SAFETY: executorEffortRef is operator-configured and trusted to name a ThinkingLevel.
|
|
121
126
|
runtime.pi.setThinkingLevel(executorEffortRef as ThinkingLevel);
|
|
122
127
|
}
|
|
@@ -128,9 +133,11 @@ export const activateAdvisor = async (
|
|
|
128
133
|
]);
|
|
129
134
|
}
|
|
130
135
|
if (announce) {
|
|
136
|
+
const activeExecutorRef = effectiveExecutorRef(ctx);
|
|
137
|
+
const activeExecutorEffort = effectiveExecutorEffort(ctx);
|
|
131
138
|
notify(
|
|
132
139
|
ctx,
|
|
133
|
-
`${ADVISOR_ACTIVATION_EXPLANATION}\n\nAdvisor flow ready — Executor: ${
|
|
140
|
+
`${ADVISOR_ACTIVATION_EXPLANATION}\n\nAdvisor flow ready — Executor: ${activeExecutorRef} (thinking: ${activeExecutorEffort || "default"}) · Advisor: ${advisorRef} (thinking: ${advisorEffortRef || "default"})`,
|
|
134
141
|
"info"
|
|
135
142
|
);
|
|
136
143
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
|
|
3
|
+
import { isMarkedSubagent } from "../child-session.ts";
|
|
3
4
|
import {
|
|
4
5
|
alwaysOnRef,
|
|
5
6
|
executorRef,
|
|
@@ -40,7 +41,11 @@ export const registerCommandLifecycle = (
|
|
|
40
41
|
// "restore" replays a stored session model and "cycle" changes the active
|
|
41
42
|
// model without an explicit `/model` choice. Neither should redefine the
|
|
42
43
|
// configured Executor.
|
|
43
|
-
if (
|
|
44
|
+
if (
|
|
45
|
+
event.source !== "set" ||
|
|
46
|
+
runtime.suppressModelSelectionSync ||
|
|
47
|
+
isMarkedSubagent()
|
|
48
|
+
) {
|
|
44
49
|
return;
|
|
45
50
|
}
|
|
46
51
|
const selected = `${event.model.provider}/${event.model.id}`;
|
package/src/scout.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import type { Message } from "@earendil-works/pi-ai/compat";
|
|
2
2
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
effectiveExecutorEffort,
|
|
6
|
+
effectiveExecutorRef,
|
|
7
|
+
} from "./child-session.ts";
|
|
5
8
|
import { isRecord, isString } from "./content-utils.ts";
|
|
6
9
|
import { collectTextStream, resolveConfiguredModel } from "./model-stream.ts";
|
|
7
10
|
import type {
|
|
@@ -250,6 +253,8 @@ type ScoutStreamResult =
|
|
|
250
253
|
const streamScoutResponse = async (
|
|
251
254
|
dependencies: ScoutDependencies,
|
|
252
255
|
resolved: ResolvedConfiguredModel,
|
|
256
|
+
executorModel: string,
|
|
257
|
+
executorEffort: string | undefined,
|
|
253
258
|
manifest: ScoutManifest,
|
|
254
259
|
parentSignal: AbortSignal | undefined,
|
|
255
260
|
timeoutMs: number,
|
|
@@ -264,10 +269,10 @@ const streamScoutResponse = async (
|
|
|
264
269
|
messages: [manifestMessage(manifest)],
|
|
265
270
|
onChunk: (thinking, text) => {
|
|
266
271
|
if (!controller.signal.aborted) {
|
|
267
|
-
publish({ model:
|
|
272
|
+
publish({ model: executorModel, text, thinking, type: "chunk" });
|
|
268
273
|
}
|
|
269
274
|
},
|
|
270
|
-
reasoning:
|
|
275
|
+
reasoning: executorEffort,
|
|
271
276
|
signal: controller.signal,
|
|
272
277
|
systemPrompt: SCOUT_SYSTEM,
|
|
273
278
|
});
|
|
@@ -317,7 +322,7 @@ export const runAdvisorScout = async (
|
|
|
317
322
|
usage === undefined
|
|
318
323
|
? baseMetrics(manifest, startedAt)
|
|
319
324
|
: { ...baseMetrics(manifest, startedAt), usage },
|
|
320
|
-
model:
|
|
325
|
+
model: effectiveExecutorRef(ctx),
|
|
321
326
|
ok: false as const,
|
|
322
327
|
};
|
|
323
328
|
publish({ outcome, type: "fallback" });
|
|
@@ -328,9 +333,11 @@ export const runAdvisorScout = async (
|
|
|
328
333
|
return cancelled();
|
|
329
334
|
}
|
|
330
335
|
|
|
336
|
+
const executorModel = effectiveExecutorRef(ctx);
|
|
337
|
+
const executorEffort = effectiveExecutorEffort(ctx);
|
|
331
338
|
let resolved: ResolvedConfiguredModel;
|
|
332
339
|
try {
|
|
333
|
-
resolved = await dependencies.resolve(ctx,
|
|
340
|
+
resolved = await dependencies.resolve(ctx, executorModel, "Scout");
|
|
334
341
|
} catch (error) {
|
|
335
342
|
if (parentSignal?.aborted) {
|
|
336
343
|
return cancelled();
|
|
@@ -342,11 +349,13 @@ export const runAdvisorScout = async (
|
|
|
342
349
|
if (parentSignal?.aborted) {
|
|
343
350
|
return cancelled();
|
|
344
351
|
}
|
|
345
|
-
publish({ model:
|
|
352
|
+
publish({ model: executorModel, type: "call" });
|
|
346
353
|
|
|
347
354
|
const streamed = await streamScoutResponse(
|
|
348
355
|
dependencies,
|
|
349
356
|
resolved,
|
|
357
|
+
executorModel,
|
|
358
|
+
executorEffort,
|
|
350
359
|
manifest,
|
|
351
360
|
parentSignal,
|
|
352
361
|
timeoutMs,
|
|
@@ -390,7 +399,7 @@ export const runAdvisorScout = async (
|
|
|
390
399
|
]).size,
|
|
391
400
|
usage: snapshotAdvisorUsage(streamed.streamed.usage),
|
|
392
401
|
},
|
|
393
|
-
model:
|
|
402
|
+
model: executorModel,
|
|
394
403
|
ok: true as const,
|
|
395
404
|
selectedLabels: manifest.groups
|
|
396
405
|
.filter(
|