openfleet 0.3.13 → 0.3.15
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/index.js +122 -9
- package/dist/lib/fallback.d.ts +23 -0
- package/dist/models.d.ts +1 -0
- package/dist/transcript/hooks.d.ts +17 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -59,6 +59,7 @@ var models = {
|
|
|
59
59
|
var defaultModel = models.anthropic.sonnet;
|
|
60
60
|
var bigModel = defaultModel;
|
|
61
61
|
var smallModel = defaultModel;
|
|
62
|
+
var fallbackModel = models.freeModels.minimaxM25Free;
|
|
62
63
|
|
|
63
64
|
// src/agents/names.ts
|
|
64
65
|
var AGENT_NAMES = {
|
|
@@ -1125,6 +1126,83 @@ import { tool } from "@opencode-ai/plugin";
|
|
|
1125
1126
|
import { existsSync as existsSync3, readFileSync } from "fs";
|
|
1126
1127
|
import path3 from "path";
|
|
1127
1128
|
|
|
1129
|
+
// src/lib/fallback.ts
|
|
1130
|
+
var CREDIT_BALANCE_PATTERNS = [
|
|
1131
|
+
"credit balance is too low",
|
|
1132
|
+
"insufficient credits",
|
|
1133
|
+
"please go to plans & billing",
|
|
1134
|
+
"purchase credits"
|
|
1135
|
+
];
|
|
1136
|
+
var fallbackInProgress = new Set;
|
|
1137
|
+
var fallbackSessions = new Set;
|
|
1138
|
+
var lastFallbackTime = new Map;
|
|
1139
|
+
var COOLDOWN_MS = 30000;
|
|
1140
|
+
function isCreditBalanceError(message) {
|
|
1141
|
+
const lower = message.toLowerCase();
|
|
1142
|
+
return CREDIT_BALANCE_PATTERNS.some((p) => lower.includes(p));
|
|
1143
|
+
}
|
|
1144
|
+
function isSessionInFallback(sessionID) {
|
|
1145
|
+
return fallbackSessions.has(sessionID);
|
|
1146
|
+
}
|
|
1147
|
+
function markSessionFallback(sessionID) {
|
|
1148
|
+
fallbackSessions.add(sessionID);
|
|
1149
|
+
}
|
|
1150
|
+
function getFallbackModelOverride() {
|
|
1151
|
+
const [providerID, modelID] = fallbackModel.split("/");
|
|
1152
|
+
return { providerID, modelID };
|
|
1153
|
+
}
|
|
1154
|
+
async function handleCreditBalanceFallback(client, sessionID) {
|
|
1155
|
+
if (fallbackInProgress.has(sessionID))
|
|
1156
|
+
return;
|
|
1157
|
+
const last = lastFallbackTime.get(sessionID);
|
|
1158
|
+
if (last && Date.now() - last < COOLDOWN_MS)
|
|
1159
|
+
return;
|
|
1160
|
+
fallbackInProgress.add(sessionID);
|
|
1161
|
+
fallbackSessions.add(sessionID);
|
|
1162
|
+
lastFallbackTime.set(sessionID, Date.now());
|
|
1163
|
+
try {
|
|
1164
|
+
await client.session.abort({ path: { id: sessionID } });
|
|
1165
|
+
const { data: messages } = await client.session.messages({
|
|
1166
|
+
path: { id: sessionID }
|
|
1167
|
+
});
|
|
1168
|
+
if (!messages || messages.length === 0) {
|
|
1169
|
+
throw new Error("No messages found after abort");
|
|
1170
|
+
}
|
|
1171
|
+
const lastUserMsg = [...messages].reverse().find((m) => m.info.role === "user");
|
|
1172
|
+
if (!lastUserMsg) {
|
|
1173
|
+
throw new Error("No user message found to revert to");
|
|
1174
|
+
}
|
|
1175
|
+
const textPart = lastUserMsg.parts.find((p) => p.type === "text");
|
|
1176
|
+
if (!textPart || textPart.type !== "text")
|
|
1177
|
+
return;
|
|
1178
|
+
const messageID = lastUserMsg.info.id;
|
|
1179
|
+
const text = textPart.text;
|
|
1180
|
+
await client.session.revert({
|
|
1181
|
+
path: { id: sessionID },
|
|
1182
|
+
body: { messageID }
|
|
1183
|
+
});
|
|
1184
|
+
const [providerID, modelID] = fallbackModel.split("/");
|
|
1185
|
+
await client.session.prompt({
|
|
1186
|
+
path: { id: sessionID },
|
|
1187
|
+
body: {
|
|
1188
|
+
model: { providerID, modelID },
|
|
1189
|
+
parts: [{ type: "text", text }]
|
|
1190
|
+
}
|
|
1191
|
+
});
|
|
1192
|
+
logger.info("Credit balance fallback triggered", { sessionID, fallbackModel });
|
|
1193
|
+
await client.tui.showToast({
|
|
1194
|
+
body: {
|
|
1195
|
+
message: "\u26A0\uFE0F Anthropic credit balance low \u2014 switched to Minimax M2.5 Free",
|
|
1196
|
+
variant: "warning"
|
|
1197
|
+
}
|
|
1198
|
+
});
|
|
1199
|
+
} catch (err) {
|
|
1200
|
+
logger.error("Credit balance fallback failed", { sessionID, err });
|
|
1201
|
+
} finally {
|
|
1202
|
+
fallbackInProgress.delete(sessionID);
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1128
1206
|
// src/transcript/writer.ts
|
|
1129
1207
|
import { existsSync as existsSync2 } from "fs";
|
|
1130
1208
|
import { appendFile, mkdir } from "fs/promises";
|
|
@@ -1329,6 +1407,11 @@ function createTranscriptHooks(ctx) {
|
|
|
1329
1407
|
const session = await getSessionInfo(ctx, input.sessionID);
|
|
1330
1408
|
await recordUserMessage(session, output.message, output.parts);
|
|
1331
1409
|
},
|
|
1410
|
+
"chat.model": async (input, output) => {
|
|
1411
|
+
if (isSessionInFallback(input.sessionID)) {
|
|
1412
|
+
output.model = getFallbackModelOverride();
|
|
1413
|
+
}
|
|
1414
|
+
},
|
|
1332
1415
|
"tool.execute.before": async (input, output) => {
|
|
1333
1416
|
const session = await getSessionInfo(ctx, input.sessionID);
|
|
1334
1417
|
await recordToolUse(session, input.tool, input.callID, output.args);
|
|
@@ -1358,6 +1441,34 @@ function createTranscriptHooks(ctx) {
|
|
|
1358
1441
|
err
|
|
1359
1442
|
});
|
|
1360
1443
|
}
|
|
1444
|
+
},
|
|
1445
|
+
event: async ({ event }) => {
|
|
1446
|
+
if (event.type === "session.created") {
|
|
1447
|
+
const { info } = event.properties;
|
|
1448
|
+
if (info.parentID && isSessionInFallback(info.parentID)) {
|
|
1449
|
+
markSessionFallback(info.id);
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
if (event.type === "session.status") {
|
|
1453
|
+
const { sessionID, status } = event.properties;
|
|
1454
|
+
if (status.type === "retry" && isCreditBalanceError(status.message)) {
|
|
1455
|
+
await handleCreditBalanceFallback(ctx.client, sessionID);
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
if (event.type === "session.error") {
|
|
1459
|
+
const { sessionID, error } = event.properties;
|
|
1460
|
+
if (sessionID && error && "message" in error.data && isCreditBalanceError(String(error.data.message))) {
|
|
1461
|
+
await handleCreditBalanceFallback(ctx.client, sessionID);
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
if (event.type === "message.updated") {
|
|
1465
|
+
const { info } = event.properties;
|
|
1466
|
+
if (info.role === "assistant" && info.error) {
|
|
1467
|
+
if ("message" in info.error.data && isCreditBalanceError(String(info.error.data.message))) {
|
|
1468
|
+
await handleCreditBalanceFallback(ctx.client, info.sessionID);
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1361
1472
|
}
|
|
1362
1473
|
};
|
|
1363
1474
|
}
|
|
@@ -1788,6 +1899,7 @@ var OpenfleetPlugin = async (ctx) => {
|
|
|
1788
1899
|
logger.info("Plugin loaded");
|
|
1789
1900
|
const saveConversation = createSaveConversationTool(ctx);
|
|
1790
1901
|
const transcriptHooks = createTranscriptHooks(ctx);
|
|
1902
|
+
const { event: transcriptEvent, ...otherTranscriptHooks } = transcriptHooks;
|
|
1791
1903
|
return {
|
|
1792
1904
|
tool: {
|
|
1793
1905
|
save_conversation: saveConversation
|
|
@@ -1796,16 +1908,17 @@ var OpenfleetPlugin = async (ctx) => {
|
|
|
1796
1908
|
configureAgents(config);
|
|
1797
1909
|
},
|
|
1798
1910
|
event: async ({ event }) => {
|
|
1799
|
-
if (event.type
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
}
|
|
1911
|
+
if (event.type === "session.created") {
|
|
1912
|
+
const props = event.properties;
|
|
1913
|
+
if (!props?.info?.parentID) {
|
|
1914
|
+
setTimeout(async () => {
|
|
1915
|
+
await showFleetToast(ctx);
|
|
1916
|
+
}, 0);
|
|
1917
|
+
}
|
|
1918
|
+
}
|
|
1919
|
+
await transcriptEvent({ event });
|
|
1807
1920
|
},
|
|
1808
|
-
...
|
|
1921
|
+
...otherTranscriptHooks
|
|
1809
1922
|
};
|
|
1810
1923
|
};
|
|
1811
1924
|
async function showFleetToast(ctx) {
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { PluginInput } from "@opencode-ai/plugin";
|
|
2
|
+
/** Returns true if the message contains a known Anthropic credit balance error pattern. */
|
|
3
|
+
export declare function isCreditBalanceError(message: string): boolean;
|
|
4
|
+
/** Returns true if the given session or any ancestor has fallen back to the free model. */
|
|
5
|
+
export declare function isSessionInFallback(sessionID: string): boolean;
|
|
6
|
+
/** Marks a session as requiring fallback (used for child session propagation). */
|
|
7
|
+
export declare function markSessionFallback(sessionID: string): void;
|
|
8
|
+
/** Returns the fallback model split into providerID and modelID. */
|
|
9
|
+
export declare function getFallbackModelOverride(): {
|
|
10
|
+
providerID: string;
|
|
11
|
+
modelID: string;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Handles automatic fallback to free model when Anthropic credit balance is low.
|
|
15
|
+
*
|
|
16
|
+
* This function:
|
|
17
|
+
* 1. Guards against re-entrant or rapid repeated fallbacks via in-progress set and cooldown
|
|
18
|
+
* 2. Aborts the current retry loop for the session
|
|
19
|
+
* 3. Finds and reverts to before the last user message
|
|
20
|
+
* 4. Re-sends that message using the fallback model
|
|
21
|
+
* 5. Shows a warning toast to the user
|
|
22
|
+
*/
|
|
23
|
+
export declare function handleCreditBalanceFallback(client: PluginInput["client"], sessionID: string): Promise<void>;
|
package/dist/models.d.ts
CHANGED
|
@@ -28,3 +28,4 @@ export declare const models: {
|
|
|
28
28
|
export declare const defaultModel: "anthropic/claude-sonnet-4-6";
|
|
29
29
|
export declare const bigModel: "anthropic/claude-sonnet-4-6";
|
|
30
30
|
export declare const smallModel: "anthropic/claude-sonnet-4-6";
|
|
31
|
+
export declare const fallbackModel: "opencode/minimax-m2.5-free";
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { PluginInput } from "@opencode-ai/plugin";
|
|
2
|
+
import type { Event } from "@opencode-ai/sdk";
|
|
2
3
|
export declare function createTranscriptHooks(ctx: PluginInput): {
|
|
3
4
|
"chat.message": (input: {
|
|
4
5
|
sessionID: string;
|
|
@@ -7,6 +8,19 @@ export declare function createTranscriptHooks(ctx: PluginInput): {
|
|
|
7
8
|
message: unknown;
|
|
8
9
|
parts: unknown[];
|
|
9
10
|
}) => Promise<void>;
|
|
11
|
+
"chat.model": (input: {
|
|
12
|
+
sessionID: string;
|
|
13
|
+
agent: string;
|
|
14
|
+
model: {
|
|
15
|
+
providerID: string;
|
|
16
|
+
modelID: string;
|
|
17
|
+
};
|
|
18
|
+
}, output: {
|
|
19
|
+
model: {
|
|
20
|
+
providerID: string;
|
|
21
|
+
modelID: string;
|
|
22
|
+
};
|
|
23
|
+
}) => Promise<void>;
|
|
10
24
|
"tool.execute.before": (input: {
|
|
11
25
|
sessionID: string;
|
|
12
26
|
tool: string;
|
|
@@ -26,4 +40,7 @@ export declare function createTranscriptHooks(ctx: PluginInput): {
|
|
|
26
40
|
"experimental.chat.system.transform": (input: {}, output: {
|
|
27
41
|
system: string[];
|
|
28
42
|
}) => Promise<void>;
|
|
43
|
+
event: ({ event }: {
|
|
44
|
+
event: Event;
|
|
45
|
+
}) => Promise<void>;
|
|
29
46
|
};
|