opencode-immune 1.0.72 → 1.0.74
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/dist/plugin/server.js +103 -35
- package/package.json +1 -1
package/dist/plugin/server.js
CHANGED
|
@@ -3777,7 +3777,7 @@ import { fileURLToPath } from "url";
|
|
|
3777
3777
|
import { createHash } from "crypto";
|
|
3778
3778
|
import { tmpdir } from "os";
|
|
3779
3779
|
import { execFile } from "child_process";
|
|
3780
|
-
var PLUGIN_VERSION = "1.0.
|
|
3780
|
+
var PLUGIN_VERSION = "1.0.74";
|
|
3781
3781
|
var PLUGIN_PACKAGE_NAME = "opencode-immune";
|
|
3782
3782
|
var PLUGIN_DIRNAME = dirname(fileURLToPath(import.meta.url));
|
|
3783
3783
|
function getServerAuthHeaders() {
|
|
@@ -3855,6 +3855,7 @@ function createState(input) {
|
|
|
3855
3855
|
ultraworkPermissionSessions: /* @__PURE__ */ new Set(),
|
|
3856
3856
|
fallbackAgentByAgent: /* @__PURE__ */ new Map(),
|
|
3857
3857
|
baseAgentByFallbackAgent: /* @__PURE__ */ new Map(),
|
|
3858
|
+
fallbackModelCandidates: [],
|
|
3858
3859
|
ultraworkMarkerPath: join(
|
|
3859
3860
|
input.directory,
|
|
3860
3861
|
".opencode",
|
|
@@ -3911,24 +3912,15 @@ var MANAGED_SESSION_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
|
3911
3912
|
var PROVIDER_RETRY_WATCHDOG_MS = 3e4;
|
|
3912
3913
|
var RETRY_PROMPT_DELIVERY_ATTEMPTS = 3;
|
|
3913
3914
|
var CHILD_FALLBACK_REQUEST_TTL_MS = 10 * 60 * 1e3;
|
|
3914
|
-
var RATE_LIMIT_FALLBACK_MODEL = {
|
|
3915
|
-
providerID: "codexsale",
|
|
3916
|
-
modelID: "gpt-5.4-mini"
|
|
3917
|
-
};
|
|
3918
|
-
var CHILD_SESSION_FALLBACK_MODEL = {
|
|
3919
|
-
providerID: "claudehub",
|
|
3920
|
-
modelID: "claude-opus-4-7"
|
|
3921
|
-
};
|
|
3922
|
-
var HIGH_CAPABILITY_FALLBACK_MODELS = [
|
|
3923
|
-
{ providerID: "codexsale", modelID: "gpt-5.5" },
|
|
3924
|
-
{ providerID: "codexsale", modelID: "gpt-5.4" }
|
|
3925
|
-
];
|
|
3926
3915
|
var AUTO_CYCLE_LOCK_TTL_MS = 30 * 60 * 1e3;
|
|
3927
|
-
var
|
|
3928
|
-
|
|
3929
|
-
|
|
3930
|
-
|
|
3931
|
-
|
|
3916
|
+
var MODEL_NAME_CAPABILITY_SCORE = {
|
|
3917
|
+
"claude-opus-4-7": 100,
|
|
3918
|
+
"gpt-5.5": 100,
|
|
3919
|
+
"gpt-5.4": 90,
|
|
3920
|
+
"claude-sonnet-4-6": 90,
|
|
3921
|
+
"gpt-5.4-mini": 50,
|
|
3922
|
+
"claude-haiku-4-5": 50
|
|
3923
|
+
};
|
|
3932
3924
|
var FALLBACK_AGENT_SUFFIX = "-provider-fallback";
|
|
3933
3925
|
function isManagedUltraworkSession(state, sessionID) {
|
|
3934
3926
|
return !!sessionID && state.managedUltraworkSessions.has(sessionID);
|
|
@@ -4212,15 +4204,57 @@ function isSameModel(a, b) {
|
|
|
4212
4204
|
if (!a || !b) return false;
|
|
4213
4205
|
return a.providerID === b.providerID && a.modelID === b.modelID;
|
|
4214
4206
|
}
|
|
4215
|
-
function
|
|
4207
|
+
function getModelCapabilityScore(model) {
|
|
4208
|
+
if (!model) return 90;
|
|
4209
|
+
const modelID = model.modelID.toLowerCase();
|
|
4210
|
+
const explicitScore = MODEL_NAME_CAPABILITY_SCORE[modelID];
|
|
4211
|
+
if (explicitScore !== void 0) return explicitScore;
|
|
4212
|
+
if (/\b(mini|nano|small|lite|haiku|flash)\b/i.test(modelID)) return 50;
|
|
4213
|
+
if (/\b(opus|gpt-5\.5|gpt-5\.4|o3|o4|gpt-4\.1|gpt-4o)\b/i.test(modelID)) return 90;
|
|
4214
|
+
return 70;
|
|
4215
|
+
}
|
|
4216
|
+
function getMinimumFallbackCapabilityScore(currentModel) {
|
|
4217
|
+
const currentScore = getModelCapabilityScore(currentModel);
|
|
4218
|
+
if (currentScore >= 90) return 90;
|
|
4219
|
+
if (currentScore >= 70) return 70;
|
|
4220
|
+
return 0;
|
|
4221
|
+
}
|
|
4222
|
+
function sortFallbackModelsByCapability(candidates, currentModel) {
|
|
4223
|
+
const currentScore = getModelCapabilityScore(currentModel);
|
|
4224
|
+
const minimumScore = getMinimumFallbackCapabilityScore(currentModel);
|
|
4225
|
+
return [...candidates].sort((a, b) => {
|
|
4226
|
+
const aScore = getModelCapabilityScore(a);
|
|
4227
|
+
const bScore = getModelCapabilityScore(b);
|
|
4228
|
+
const aAllowed = aScore >= minimumScore ? 0 : 1;
|
|
4229
|
+
const bAllowed = bScore >= minimumScore ? 0 : 1;
|
|
4230
|
+
if (aAllowed !== bAllowed) return aAllowed - bAllowed;
|
|
4231
|
+
const aDirectionPenalty = aScore >= currentScore ? 0 : 1;
|
|
4232
|
+
const bDirectionPenalty = bScore >= currentScore ? 0 : 1;
|
|
4233
|
+
if (aDirectionPenalty !== bDirectionPenalty) {
|
|
4234
|
+
return aDirectionPenalty - bDirectionPenalty;
|
|
4235
|
+
}
|
|
4236
|
+
const aSameModelName = currentModel && a.modelID === currentModel.modelID ? 0 : 1;
|
|
4237
|
+
const bSameModelName = currentModel && b.modelID === currentModel.modelID ? 0 : 1;
|
|
4238
|
+
if (aSameModelName !== bSameModelName) return aSameModelName - bSameModelName;
|
|
4239
|
+
const aDistance = Math.abs(aScore - currentScore);
|
|
4240
|
+
const bDistance = Math.abs(bScore - currentScore);
|
|
4241
|
+
if (aDistance !== bDistance) return aDistance - bDistance;
|
|
4242
|
+
return bScore - aScore;
|
|
4243
|
+
});
|
|
4244
|
+
}
|
|
4245
|
+
function selectFallbackModel(state, currentModel) {
|
|
4216
4246
|
const currentProviderID = getModelProviderID(currentModel);
|
|
4217
|
-
const
|
|
4218
|
-
(candidate) => !isSameModel(candidate, currentModel)
|
|
4247
|
+
const fallbackCandidates = state.fallbackModelCandidates.filter(
|
|
4248
|
+
(candidate) => !isSameModel(candidate, currentModel)
|
|
4219
4249
|
);
|
|
4250
|
+
const otherProviderModel = sortFallbackModelsByCapability(
|
|
4251
|
+
fallbackCandidates.filter(
|
|
4252
|
+
(candidate) => getModelProviderID(candidate) !== currentProviderID
|
|
4253
|
+
),
|
|
4254
|
+
currentModel
|
|
4255
|
+
)[0];
|
|
4220
4256
|
if (otherProviderModel) return otherProviderModel;
|
|
4221
|
-
return
|
|
4222
|
-
(candidate) => !isSameModel(candidate, currentModel)
|
|
4223
|
-
) ?? RATE_LIMIT_FALLBACK_MODEL;
|
|
4257
|
+
return sortFallbackModelsByCapability(fallbackCandidates, currentModel)[0];
|
|
4224
4258
|
}
|
|
4225
4259
|
function getFailedModelFromError(error) {
|
|
4226
4260
|
if (!error || typeof error !== "object") return void 0;
|
|
@@ -4233,13 +4267,6 @@ function getFailedModelFromError(error) {
|
|
|
4233
4267
|
modelID: providerModelMatch[2].toLowerCase()
|
|
4234
4268
|
};
|
|
4235
4269
|
}
|
|
4236
|
-
const unsupportedModelMatch = message.match(/["']([^"']+)["']\s+model\s+is\s+not\s+supported/i);
|
|
4237
|
-
if (unsupportedModelMatch?.[1] && /\bcodex\b/i.test(message)) {
|
|
4238
|
-
return {
|
|
4239
|
-
providerID: "codexsale",
|
|
4240
|
-
modelID: unsupportedModelMatch[1].toLowerCase()
|
|
4241
|
-
};
|
|
4242
|
-
}
|
|
4243
4270
|
return void 0;
|
|
4244
4271
|
}
|
|
4245
4272
|
async function updateManagedSessionModel(state, sessionID, model) {
|
|
@@ -4256,7 +4283,7 @@ function getSessionFallbackModel(state, sessionID, preferredModel) {
|
|
|
4256
4283
|
if (preferredModel && !isSameModel(preferredModel, currentModel)) {
|
|
4257
4284
|
return preferredModel;
|
|
4258
4285
|
}
|
|
4259
|
-
return selectFallbackModel(currentModel);
|
|
4286
|
+
return selectFallbackModel(state, currentModel);
|
|
4260
4287
|
}
|
|
4261
4288
|
function getFallbackAgentForAgent(state, agent) {
|
|
4262
4289
|
return state.fallbackAgentByAgent.get(agent) ?? agent;
|
|
@@ -4393,8 +4420,10 @@ async function recoverUntrackedRootSessionForActiveTask(state, sessionID, reason
|
|
|
4393
4420
|
}
|
|
4394
4421
|
function recordChildFallbackRequest(state, managedSession, childSessionID, error) {
|
|
4395
4422
|
const fallbackModel = selectFallbackModel(
|
|
4423
|
+
state,
|
|
4396
4424
|
getFailedModelFromError(error) ?? managedSession.currentModel
|
|
4397
4425
|
);
|
|
4426
|
+
if (!fallbackModel) return;
|
|
4398
4427
|
const routerSessionID = getRouterSessionIDForChild(managedSession);
|
|
4399
4428
|
const request = {
|
|
4400
4429
|
childSessionID,
|
|
@@ -4425,6 +4454,7 @@ function scheduleProviderRetryWatchdog(state, sessionID, model) {
|
|
|
4425
4454
|
if (!isManagedRootUltraworkSession(state, sessionID)) return;
|
|
4426
4455
|
if (state.sessionRetryTimers.has(sessionID)) return;
|
|
4427
4456
|
const fallbackModel = getSessionFallbackModel(state, sessionID, model);
|
|
4457
|
+
if (!fallbackModel) return;
|
|
4428
4458
|
await setSessionFallbackModel(state, sessionID, fallbackModel);
|
|
4429
4459
|
await writeDiagnosticLog(state, "provider-retry-watchdog:fired", {
|
|
4430
4460
|
sessionID,
|
|
@@ -4656,6 +4686,36 @@ function parseModelRef(model) {
|
|
|
4656
4686
|
if (!providerID || !modelID) return void 0;
|
|
4657
4687
|
return { providerID, modelID };
|
|
4658
4688
|
}
|
|
4689
|
+
function addFallbackModelCandidate(candidates, model) {
|
|
4690
|
+
const parsed = parseModelRef(model);
|
|
4691
|
+
if (!parsed) return;
|
|
4692
|
+
candidates.set(modelRefToString(parsed).toLowerCase(), parsed);
|
|
4693
|
+
}
|
|
4694
|
+
function collectFallbackModelCandidates(config) {
|
|
4695
|
+
const candidates = /* @__PURE__ */ new Map();
|
|
4696
|
+
addFallbackModelCandidate(candidates, config.model);
|
|
4697
|
+
addFallbackModelCandidate(candidates, config.small_model);
|
|
4698
|
+
for (const agentConfig of Object.values(config.agent ?? {})) {
|
|
4699
|
+
if (!isRecord(agentConfig)) continue;
|
|
4700
|
+
const model = agentConfig.model;
|
|
4701
|
+
if (typeof model === "string") addFallbackModelCandidate(candidates, model);
|
|
4702
|
+
}
|
|
4703
|
+
const enabledProviders = new Set(config.enabled_providers ?? []);
|
|
4704
|
+
const disabledProviders = new Set(config.disabled_providers ?? []);
|
|
4705
|
+
const isProviderEnabled = (providerID) => {
|
|
4706
|
+
if (enabledProviders.size > 0 && !enabledProviders.has(providerID)) return false;
|
|
4707
|
+
return !disabledProviders.has(providerID);
|
|
4708
|
+
};
|
|
4709
|
+
for (const [providerID, providerConfig] of Object.entries(config.provider ?? {})) {
|
|
4710
|
+
if (!isProviderEnabled(providerID) || !isRecord(providerConfig)) continue;
|
|
4711
|
+
const models = providerConfig.models;
|
|
4712
|
+
if (!isRecord(models)) continue;
|
|
4713
|
+
for (const modelID of Object.keys(models)) {
|
|
4714
|
+
addFallbackModelCandidate(candidates, `${providerID}/${modelID}`);
|
|
4715
|
+
}
|
|
4716
|
+
}
|
|
4717
|
+
return sortFallbackModelsByCapability([...candidates.values()]);
|
|
4718
|
+
}
|
|
4659
4719
|
function canCreateFallbackForAgent(agentID, agentConfig) {
|
|
4660
4720
|
if (agentID.endsWith(FALLBACK_AGENT_SUFFIX)) return false;
|
|
4661
4721
|
if (!isRecord(agentConfig)) return false;
|
|
@@ -4686,11 +4746,12 @@ function allowFallbackAgentInTaskPermissions(config, baseAgentID, fallbackAgentI
|
|
|
4686
4746
|
function createConfigHandler(state) {
|
|
4687
4747
|
return async (config) => {
|
|
4688
4748
|
if (!config.agent) return;
|
|
4749
|
+
state.fallbackModelCandidates = collectFallbackModelCandidates(config);
|
|
4689
4750
|
for (const [agentID, agentConfig] of Object.entries(config.agent)) {
|
|
4690
4751
|
if (!canCreateFallbackForAgent(agentID, agentConfig)) continue;
|
|
4691
4752
|
const baseModel = parseModelRef(agentConfig.model);
|
|
4692
|
-
const fallbackModel = selectFallbackModel(baseModel);
|
|
4693
|
-
if (!baseModel || isSameModel(baseModel, fallbackModel)) continue;
|
|
4753
|
+
const fallbackModel = selectFallbackModel(state, baseModel);
|
|
4754
|
+
if (!baseModel || !fallbackModel || isSameModel(baseModel, fallbackModel)) continue;
|
|
4694
4755
|
const fallbackAgentID = `${agentID}${FALLBACK_AGENT_SUFFIX}`;
|
|
4695
4756
|
if (config.agent[fallbackAgentID]) continue;
|
|
4696
4757
|
config.agent[fallbackAgentID] = {
|
|
@@ -5503,9 +5564,12 @@ function createEventHandler(state) {
|
|
|
5503
5564
|
state.sessionErrorRetryCount.set(fallbackSessionID, count + 1);
|
|
5504
5565
|
if (shouldUseFallbackForManagedError(error, managedSession, count)) {
|
|
5505
5566
|
const fallbackModel = selectFallbackModel(
|
|
5567
|
+
state,
|
|
5506
5568
|
getFailedModelFromError(error) ?? managedSession.currentModel
|
|
5507
5569
|
);
|
|
5508
|
-
|
|
5570
|
+
if (fallbackModel) {
|
|
5571
|
+
await setSessionFallbackModel(state, fallbackSessionID, fallbackModel);
|
|
5572
|
+
}
|
|
5509
5573
|
}
|
|
5510
5574
|
pluginLog.warn(
|
|
5511
5575
|
`[opencode-immune] session.error without sessionID matched retryable error. Retrying sole managed root session ${fallbackSessionID}.`
|
|
@@ -5561,8 +5625,10 @@ function createEventHandler(state) {
|
|
|
5561
5625
|
return;
|
|
5562
5626
|
} else if (isRoot && shouldUseFallbackForManagedError(error, managedSession, count)) {
|
|
5563
5627
|
const selectedFallbackModel = selectFallbackModel(
|
|
5628
|
+
state,
|
|
5564
5629
|
getFailedModelFromError(error) ?? managedSession.currentModel
|
|
5565
5630
|
);
|
|
5631
|
+
if (!selectedFallbackModel) return;
|
|
5566
5632
|
await setSessionFallbackModel(state, sessionID, selectedFallbackModel);
|
|
5567
5633
|
const errorType = getRetryableErrorType(error);
|
|
5568
5634
|
pluginLog.info(
|
|
@@ -5709,6 +5775,7 @@ function createTextCompleteHandler(state) {
|
|
|
5709
5775
|
cancelProviderRetryWatchdog(state, sessionID, "assistant text completed");
|
|
5710
5776
|
if (isProviderRetryBanner(text) && isManagedRootSession) {
|
|
5711
5777
|
const fallbackModel = getSessionFallbackModel(state, sessionID);
|
|
5778
|
+
if (!fallbackModel) return;
|
|
5712
5779
|
await setSessionFallbackModel(state, sessionID, fallbackModel);
|
|
5713
5780
|
scheduleManagedSessionRetry(state, sessionID, {
|
|
5714
5781
|
delayMs: 1e3,
|
|
@@ -5835,6 +5902,7 @@ function createMultiCycleHandler(state) {
|
|
|
5835
5902
|
if (sessionID && RATE_LIMIT_MESSAGE_PATTERN.test(messageContent)) {
|
|
5836
5903
|
if (managedSession && !managedSession.fallbackModel) {
|
|
5837
5904
|
const fallbackModel = getSessionFallbackModel(state, sessionID);
|
|
5905
|
+
if (!fallbackModel) return;
|
|
5838
5906
|
await setSessionFallbackModel(state, sessionID, fallbackModel);
|
|
5839
5907
|
pluginLog.info(
|
|
5840
5908
|
`[opencode-immune] Rate limit message detected in chat output for session ${sessionID}. Fallback model pinned to ${fallbackModel.providerID}/${fallbackModel.modelID}.`
|