@skj1724/oh-my-opencode 3.19.6 → 3.19.7
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/cli/index.js +20 -2
- package/dist/config/schema.d.ts +21 -0
- package/dist/features/background-agent/types.d.ts +4 -0
- package/dist/hooks/runtime-fallback/index.d.ts +23 -0
- package/dist/hooks/runtime-fallback/index.test.d.ts +1 -0
- package/dist/index.js +458 -15
- package/dist/shared/index.d.ts +1 -0
- package/dist/shared/provider-error-classifier.d.ts +23 -0
- package/dist/shared/provider-error-classifier.test.d.ts +1 -0
- package/dist/shared/retry-strategy.d.ts +39 -0
- package/dist/shared/retry-strategy.test.d.ts +1 -0
- package/dist/shared/runtime-fallback.d.ts +46 -0
- package/dist/shared/runtime-fallback.test.d.ts +1 -0
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -6117,6 +6117,12 @@ var init_model_resolver = __esm(() => {
|
|
|
6117
6117
|
init_model_availability();
|
|
6118
6118
|
});
|
|
6119
6119
|
|
|
6120
|
+
// src/shared/runtime-fallback.ts
|
|
6121
|
+
var init_runtime_fallback = __esm(() => {
|
|
6122
|
+
init_model_availability();
|
|
6123
|
+
init_model_requirements();
|
|
6124
|
+
});
|
|
6125
|
+
|
|
6120
6126
|
// src/shared/perf-timer.ts
|
|
6121
6127
|
class PerfTimer {
|
|
6122
6128
|
marks = new Map;
|
|
@@ -6226,6 +6232,7 @@ var init_shared = __esm(() => {
|
|
|
6226
6232
|
init_model_requirements();
|
|
6227
6233
|
init_model_resolver();
|
|
6228
6234
|
init_model_availability();
|
|
6235
|
+
init_runtime_fallback();
|
|
6229
6236
|
init_perf_tracer();
|
|
6230
6237
|
init_fileio_monitor();
|
|
6231
6238
|
init_windows_reserved_names();
|
|
@@ -8460,7 +8467,7 @@ var import_picocolors2 = __toESM(require_picocolors(), 1);
|
|
|
8460
8467
|
// package.json
|
|
8461
8468
|
var package_default = {
|
|
8462
8469
|
name: "@skj1724/oh-my-opencode",
|
|
8463
|
-
version: "3.19.
|
|
8470
|
+
version: "3.19.7",
|
|
8464
8471
|
description: "The Best AI Agent Harness - Batteries-Included OpenCode Plugin with Multi-Model Orchestration, Parallel Background Agents, and Crafted LSP/AST Tools",
|
|
8465
8472
|
main: "dist/index.js",
|
|
8466
8473
|
types: "dist/index.d.ts",
|
|
@@ -24997,6 +25004,7 @@ var HookNameSchema = exports_external.enum([
|
|
|
24997
25004
|
"auto-slash-command",
|
|
24998
25005
|
"edit-error-recovery",
|
|
24999
25006
|
"delegate-task-retry",
|
|
25007
|
+
"runtime-fallback",
|
|
25000
25008
|
"prometheus-md-only",
|
|
25001
25009
|
"perf-profiler",
|
|
25002
25010
|
"start-work",
|
|
@@ -25183,6 +25191,15 @@ var GitMasterConfigSchema = exports_external.object({
|
|
|
25183
25191
|
commit_footer: exports_external.boolean().default(true),
|
|
25184
25192
|
include_co_authored_by: exports_external.boolean().default(true)
|
|
25185
25193
|
});
|
|
25194
|
+
var RuntimeFallbackConfigSchema = exports_external.object({
|
|
25195
|
+
enabled: exports_external.boolean().default(true),
|
|
25196
|
+
max_attempts: exports_external.number().min(0).default(3),
|
|
25197
|
+
initial_delay_ms: exports_external.number().min(0).default(2000),
|
|
25198
|
+
backoff_factor: exports_external.number().min(1).default(2),
|
|
25199
|
+
max_delay_ms: exports_external.number().min(0).default(30000),
|
|
25200
|
+
respect_retry_after: exports_external.boolean().default(true),
|
|
25201
|
+
jitter: exports_external.boolean().default(true)
|
|
25202
|
+
});
|
|
25186
25203
|
var OhMyOpenCodeConfigSchema = exports_external.object({
|
|
25187
25204
|
$schema: exports_external.string().optional(),
|
|
25188
25205
|
disabled_mcps: exports_external.array(AnyMcpNameSchema).optional(),
|
|
@@ -25201,7 +25218,8 @@ var OhMyOpenCodeConfigSchema = exports_external.object({
|
|
|
25201
25218
|
ralph_loop: RalphLoopConfigSchema.optional(),
|
|
25202
25219
|
background_task: BackgroundTaskConfigSchema.optional(),
|
|
25203
25220
|
notification: NotificationConfigSchema.optional(),
|
|
25204
|
-
git_master: GitMasterConfigSchema.optional()
|
|
25221
|
+
git_master: GitMasterConfigSchema.optional(),
|
|
25222
|
+
runtime_fallback: RuntimeFallbackConfigSchema.optional()
|
|
25205
25223
|
});
|
|
25206
25224
|
// src/cli/doctor/checks/config.ts
|
|
25207
25225
|
var USER_CONFIG_DIR2 = getOpenCodeConfigDir({ binary: "opencode" });
|
package/dist/config/schema.d.ts
CHANGED
|
@@ -70,6 +70,7 @@ export declare const HookNameSchema: z.ZodEnum<{
|
|
|
70
70
|
"auto-slash-command": "auto-slash-command";
|
|
71
71
|
"edit-error-recovery": "edit-error-recovery";
|
|
72
72
|
"delegate-task-retry": "delegate-task-retry";
|
|
73
|
+
"runtime-fallback": "runtime-fallback";
|
|
73
74
|
"prometheus-md-only": "prometheus-md-only";
|
|
74
75
|
"perf-profiler": "perf-profiler";
|
|
75
76
|
"start-work": "start-work";
|
|
@@ -1016,6 +1017,15 @@ export declare const GitMasterConfigSchema: z.ZodObject<{
|
|
|
1016
1017
|
commit_footer: z.ZodDefault<z.ZodBoolean>;
|
|
1017
1018
|
include_co_authored_by: z.ZodDefault<z.ZodBoolean>;
|
|
1018
1019
|
}, z.core.$strip>;
|
|
1020
|
+
export declare const RuntimeFallbackConfigSchema: z.ZodObject<{
|
|
1021
|
+
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
1022
|
+
max_attempts: z.ZodDefault<z.ZodNumber>;
|
|
1023
|
+
initial_delay_ms: z.ZodDefault<z.ZodNumber>;
|
|
1024
|
+
backoff_factor: z.ZodDefault<z.ZodNumber>;
|
|
1025
|
+
max_delay_ms: z.ZodDefault<z.ZodNumber>;
|
|
1026
|
+
respect_retry_after: z.ZodDefault<z.ZodBoolean>;
|
|
1027
|
+
jitter: z.ZodDefault<z.ZodBoolean>;
|
|
1028
|
+
}, z.core.$strip>;
|
|
1019
1029
|
export declare const OhMyOpenCodeConfigSchema: z.ZodObject<{
|
|
1020
1030
|
$schema: z.ZodOptional<z.ZodString>;
|
|
1021
1031
|
disabled_mcps: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
@@ -1064,6 +1074,7 @@ export declare const OhMyOpenCodeConfigSchema: z.ZodObject<{
|
|
|
1064
1074
|
"auto-slash-command": "auto-slash-command";
|
|
1065
1075
|
"edit-error-recovery": "edit-error-recovery";
|
|
1066
1076
|
"delegate-task-retry": "delegate-task-retry";
|
|
1077
|
+
"runtime-fallback": "runtime-fallback";
|
|
1067
1078
|
"prometheus-md-only": "prometheus-md-only";
|
|
1068
1079
|
"perf-profiler": "perf-profiler";
|
|
1069
1080
|
"start-work": "start-work";
|
|
@@ -1855,6 +1866,15 @@ export declare const OhMyOpenCodeConfigSchema: z.ZodObject<{
|
|
|
1855
1866
|
commit_footer: z.ZodDefault<z.ZodBoolean>;
|
|
1856
1867
|
include_co_authored_by: z.ZodDefault<z.ZodBoolean>;
|
|
1857
1868
|
}, z.core.$strip>>;
|
|
1869
|
+
runtime_fallback: z.ZodOptional<z.ZodObject<{
|
|
1870
|
+
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
1871
|
+
max_attempts: z.ZodDefault<z.ZodNumber>;
|
|
1872
|
+
initial_delay_ms: z.ZodDefault<z.ZodNumber>;
|
|
1873
|
+
backoff_factor: z.ZodDefault<z.ZodNumber>;
|
|
1874
|
+
max_delay_ms: z.ZodDefault<z.ZodNumber>;
|
|
1875
|
+
respect_retry_after: z.ZodDefault<z.ZodBoolean>;
|
|
1876
|
+
jitter: z.ZodDefault<z.ZodBoolean>;
|
|
1877
|
+
}, z.core.$strip>>;
|
|
1858
1878
|
}, z.core.$strip>;
|
|
1859
1879
|
export type OhMyOpenCodeConfig = z.infer<typeof OhMyOpenCodeConfigSchema>;
|
|
1860
1880
|
export type AgentOverrideConfig = z.infer<typeof AgentOverrideConfigSchema>;
|
|
@@ -1877,4 +1897,5 @@ export type CategoryConfig = z.infer<typeof CategoryConfigSchema>;
|
|
|
1877
1897
|
export type CategoriesConfig = z.infer<typeof CategoriesConfigSchema>;
|
|
1878
1898
|
export type BuiltinCategoryName = z.infer<typeof BuiltinCategoryNameSchema>;
|
|
1879
1899
|
export type GitMasterConfig = z.infer<typeof GitMasterConfigSchema>;
|
|
1900
|
+
export type RuntimeFallbackConfig = z.infer<typeof RuntimeFallbackConfigSchema>;
|
|
1880
1901
|
export { AnyMcpNameSchema, type AnyMcpName, McpNameSchema, type McpName } from "../mcp/types";
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { FallbackAttempt } from "../../shared/runtime-fallback";
|
|
2
|
+
export type { FallbackAttempt };
|
|
1
3
|
export type BackgroundTaskStatus = "pending" | "running" | "completed" | "error" | "cancelled";
|
|
2
4
|
export interface PhaseTiming {
|
|
3
5
|
/** queuedAt -> startedAt in ms */
|
|
@@ -65,6 +67,8 @@ export interface BackgroundTask {
|
|
|
65
67
|
maxRuntimeMs?: number;
|
|
66
68
|
/** Max duration of a single step in ms (0 / undefined = unlimited) */
|
|
67
69
|
stepTimeoutMs?: number;
|
|
70
|
+
/** Fallback attempts history */
|
|
71
|
+
attempts?: FallbackAttempt[];
|
|
68
72
|
}
|
|
69
73
|
export interface LaunchInput {
|
|
70
74
|
description: string;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime Fallback Hook
|
|
3
|
+
*
|
|
4
|
+
* 处理 session.error 事件中的 provider 错误(quota、rate_limit),
|
|
5
|
+
* 在 retry 耗尽后自动切换到 fallback 模型。
|
|
6
|
+
*
|
|
7
|
+
* 不处理:context_overflow(由 context-window-recovery 处理)、auth、bad_request。
|
|
8
|
+
* 避让 sessionRecovery(可恢复错误优先由 sessionRecovery 处理)。
|
|
9
|
+
*/
|
|
10
|
+
import type { PluginInput } from "@opencode-ai/plugin";
|
|
11
|
+
export interface RuntimeFallbackOptions {
|
|
12
|
+
sessionRecovery?: {
|
|
13
|
+
isRecoverableError: (error: unknown) => boolean;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export declare function createRuntimeFallbackHook(ctx: PluginInput, options?: RuntimeFallbackOptions): {
|
|
17
|
+
handler: ({ event, }: {
|
|
18
|
+
event: {
|
|
19
|
+
type: string;
|
|
20
|
+
properties?: unknown;
|
|
21
|
+
};
|
|
22
|
+
}) => Promise<boolean>;
|
|
23
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/index.js
CHANGED
|
@@ -4859,7 +4859,7 @@ var init_agent_tool_restrictions = __esm(() => {
|
|
|
4859
4859
|
});
|
|
4860
4860
|
|
|
4861
4861
|
// src/shared/model-requirements.ts
|
|
4862
|
-
var AGENT_MODEL_REQUIREMENTS;
|
|
4862
|
+
var AGENT_MODEL_REQUIREMENTS, CATEGORY_MODEL_REQUIREMENTS;
|
|
4863
4863
|
var init_model_requirements = __esm(() => {
|
|
4864
4864
|
AGENT_MODEL_REQUIREMENTS = {
|
|
4865
4865
|
sisyphus: {
|
|
@@ -4928,6 +4928,58 @@ var init_model_requirements = __esm(() => {
|
|
|
4928
4928
|
]
|
|
4929
4929
|
}
|
|
4930
4930
|
};
|
|
4931
|
+
CATEGORY_MODEL_REQUIREMENTS = {
|
|
4932
|
+
"visual-engineering": {
|
|
4933
|
+
fallbackChain: [
|
|
4934
|
+
{ providers: ["google", "github-copilot", "opencode"], model: "gemini-3-pro" },
|
|
4935
|
+
{ providers: ["anthropic", "github-copilot", "opencode"], model: "claude-opus-4-5", variant: "max" },
|
|
4936
|
+
{ providers: ["openai", "github-copilot", "opencode"], model: "gpt-5.2", variant: "high" }
|
|
4937
|
+
]
|
|
4938
|
+
},
|
|
4939
|
+
ultrabrain: {
|
|
4940
|
+
fallbackChain: [
|
|
4941
|
+
{ providers: ["openai", "github-copilot", "opencode"], model: "gpt-5.2-codex", variant: "xhigh" },
|
|
4942
|
+
{ providers: ["anthropic", "github-copilot", "opencode"], model: "claude-opus-4-5", variant: "max" },
|
|
4943
|
+
{ providers: ["google", "github-copilot", "opencode"], model: "gemini-3-pro" }
|
|
4944
|
+
]
|
|
4945
|
+
},
|
|
4946
|
+
artistry: {
|
|
4947
|
+
fallbackChain: [
|
|
4948
|
+
{ providers: ["google", "github-copilot", "opencode"], model: "gemini-3-pro", variant: "max" },
|
|
4949
|
+
{ providers: ["anthropic", "github-copilot", "opencode"], model: "claude-opus-4-5", variant: "max" },
|
|
4950
|
+
{ providers: ["openai", "github-copilot", "opencode"], model: "gpt-5.2" }
|
|
4951
|
+
]
|
|
4952
|
+
},
|
|
4953
|
+
quick: {
|
|
4954
|
+
fallbackChain: [
|
|
4955
|
+
{ providers: ["anthropic", "github-copilot", "opencode"], model: "claude-haiku-4-5" },
|
|
4956
|
+
{ providers: ["google", "github-copilot", "opencode"], model: "gemini-3-flash" },
|
|
4957
|
+
{ providers: ["opencode"], model: "gpt-5-nano" }
|
|
4958
|
+
]
|
|
4959
|
+
},
|
|
4960
|
+
"unspecified-low": {
|
|
4961
|
+
fallbackChain: [
|
|
4962
|
+
{ providers: ["anthropic", "github-copilot", "opencode"], model: "claude-sonnet-4-5" },
|
|
4963
|
+
{ providers: ["openai", "github-copilot", "opencode"], model: "gpt-5.2-codex", variant: "medium" },
|
|
4964
|
+
{ providers: ["google", "github-copilot", "opencode"], model: "gemini-3-flash" }
|
|
4965
|
+
]
|
|
4966
|
+
},
|
|
4967
|
+
"unspecified-high": {
|
|
4968
|
+
fallbackChain: [
|
|
4969
|
+
{ providers: ["anthropic", "github-copilot", "opencode"], model: "claude-opus-4-5", variant: "max" },
|
|
4970
|
+
{ providers: ["openai", "github-copilot", "opencode"], model: "gpt-5.2", variant: "high" },
|
|
4971
|
+
{ providers: ["google", "github-copilot", "opencode"], model: "gemini-3-pro" }
|
|
4972
|
+
]
|
|
4973
|
+
},
|
|
4974
|
+
writing: {
|
|
4975
|
+
fallbackChain: [
|
|
4976
|
+
{ providers: ["google", "github-copilot", "opencode"], model: "gemini-3-flash" },
|
|
4977
|
+
{ providers: ["anthropic", "github-copilot", "opencode"], model: "claude-sonnet-4-5" },
|
|
4978
|
+
{ providers: ["zai-coding-plan"], model: "glm-4.7" },
|
|
4979
|
+
{ providers: ["openai", "github-copilot", "opencode"], model: "gpt-5.2" }
|
|
4980
|
+
]
|
|
4981
|
+
}
|
|
4982
|
+
};
|
|
4931
4983
|
});
|
|
4932
4984
|
|
|
4933
4985
|
// src/shared/model-availability.ts
|
|
@@ -5064,6 +5116,81 @@ var init_model_resolver = __esm(() => {
|
|
|
5064
5116
|
init_model_availability();
|
|
5065
5117
|
});
|
|
5066
5118
|
|
|
5119
|
+
// src/shared/runtime-fallback.ts
|
|
5120
|
+
function expandChain(chain) {
|
|
5121
|
+
const candidates = [];
|
|
5122
|
+
for (const entry of chain) {
|
|
5123
|
+
for (const provider of entry.providers) {
|
|
5124
|
+
candidates.push({
|
|
5125
|
+
providerID: provider,
|
|
5126
|
+
modelID: entry.model,
|
|
5127
|
+
variant: entry.variant
|
|
5128
|
+
});
|
|
5129
|
+
}
|
|
5130
|
+
}
|
|
5131
|
+
return candidates;
|
|
5132
|
+
}
|
|
5133
|
+
function modelKey(m) {
|
|
5134
|
+
return `${m.providerID}/${m.modelID}`;
|
|
5135
|
+
}
|
|
5136
|
+
function getChain(agent, category) {
|
|
5137
|
+
if (agent && AGENT_MODEL_REQUIREMENTS[agent]) {
|
|
5138
|
+
return AGENT_MODEL_REQUIREMENTS[agent].fallbackChain;
|
|
5139
|
+
}
|
|
5140
|
+
if (category && CATEGORY_MODEL_REQUIREMENTS[category]) {
|
|
5141
|
+
return CATEGORY_MODEL_REQUIREMENTS[category].fallbackChain;
|
|
5142
|
+
}
|
|
5143
|
+
throw new Error(`No fallback chain found for agent="${agent ?? ""}" category="${category ?? ""}"`);
|
|
5144
|
+
}
|
|
5145
|
+
function resolveNextFallbackModel(input) {
|
|
5146
|
+
const {
|
|
5147
|
+
agent,
|
|
5148
|
+
category,
|
|
5149
|
+
currentModel,
|
|
5150
|
+
attempts,
|
|
5151
|
+
availableModels,
|
|
5152
|
+
lastErrorClassification
|
|
5153
|
+
} = input;
|
|
5154
|
+
const chain = getChain(agent, category);
|
|
5155
|
+
const candidates = expandChain(chain);
|
|
5156
|
+
const skipKeys = new Set;
|
|
5157
|
+
skipKeys.add(modelKey(currentModel));
|
|
5158
|
+
for (const a of attempts) {
|
|
5159
|
+
skipKeys.add(modelKey(a.model));
|
|
5160
|
+
}
|
|
5161
|
+
const resultAttempts = [...attempts];
|
|
5162
|
+
const currentKey = modelKey(currentModel);
|
|
5163
|
+
const isInAttempts = attempts.some((a) => modelKey(a.model) === currentKey);
|
|
5164
|
+
if (!isInAttempts) {
|
|
5165
|
+
resultAttempts.push({ model: currentModel });
|
|
5166
|
+
}
|
|
5167
|
+
const hasAvailabilityFilter = availableModels != null && availableModels.size > 0;
|
|
5168
|
+
for (const candidate of candidates) {
|
|
5169
|
+
const key = modelKey(candidate);
|
|
5170
|
+
if (skipKeys.has(key))
|
|
5171
|
+
continue;
|
|
5172
|
+
if (hasAvailabilityFilter) {
|
|
5173
|
+
const match = fuzzyMatchModel(key, availableModels, [candidate.providerID]);
|
|
5174
|
+
if (!match)
|
|
5175
|
+
continue;
|
|
5176
|
+
}
|
|
5177
|
+
return {
|
|
5178
|
+
kind: "next",
|
|
5179
|
+
model: candidate,
|
|
5180
|
+
attempts: resultAttempts
|
|
5181
|
+
};
|
|
5182
|
+
}
|
|
5183
|
+
return {
|
|
5184
|
+
kind: "exhausted",
|
|
5185
|
+
attempts: resultAttempts,
|
|
5186
|
+
lastErrorClassification
|
|
5187
|
+
};
|
|
5188
|
+
}
|
|
5189
|
+
var init_runtime_fallback = __esm(() => {
|
|
5190
|
+
init_model_availability();
|
|
5191
|
+
init_model_requirements();
|
|
5192
|
+
});
|
|
5193
|
+
|
|
5067
5194
|
// src/shared/perf-timer.ts
|
|
5068
5195
|
class PerfTimer {
|
|
5069
5196
|
marks = new Map;
|
|
@@ -5348,6 +5475,7 @@ var init_shared = __esm(() => {
|
|
|
5348
5475
|
init_model_requirements();
|
|
5349
5476
|
init_model_resolver();
|
|
5350
5477
|
init_model_availability();
|
|
5478
|
+
init_runtime_fallback();
|
|
5351
5479
|
init_perf_tracer();
|
|
5352
5480
|
init_fileio_monitor();
|
|
5353
5481
|
init_windows_reserved_names();
|
|
@@ -43257,6 +43385,256 @@ function initTaskToastManager(client2, concurrencyManager) {
|
|
|
43257
43385
|
}
|
|
43258
43386
|
// src/tools/delegate-task/tools.ts
|
|
43259
43387
|
init_shared();
|
|
43388
|
+
|
|
43389
|
+
// src/shared/provider-error-classifier.ts
|
|
43390
|
+
function extractErrorInfo(error45) {
|
|
43391
|
+
if (typeof error45 === "string") {
|
|
43392
|
+
return { message: error45 };
|
|
43393
|
+
}
|
|
43394
|
+
if (error45 instanceof Error) {
|
|
43395
|
+
const anyErr = error45;
|
|
43396
|
+
return {
|
|
43397
|
+
statusCode: anyErr.status ?? anyErr.statusCode ?? anyErr.httpStatus,
|
|
43398
|
+
code: anyErr.code ?? anyErr.error?.code,
|
|
43399
|
+
type: anyErr.type ?? anyErr.error?.type,
|
|
43400
|
+
message: error45.message,
|
|
43401
|
+
status: anyErr.status ?? anyErr.error?.status,
|
|
43402
|
+
headers: anyErr.headers
|
|
43403
|
+
};
|
|
43404
|
+
}
|
|
43405
|
+
if (typeof error45 === "object" && error45 !== null) {
|
|
43406
|
+
const obj = error45;
|
|
43407
|
+
const inner = obj.error ?? {};
|
|
43408
|
+
return {
|
|
43409
|
+
statusCode: obj.status ?? obj.statusCode ?? inner.status,
|
|
43410
|
+
code: inner.code ?? obj.code,
|
|
43411
|
+
type: inner.type ?? obj.type,
|
|
43412
|
+
message: inner.message ?? obj.message ?? String(error45),
|
|
43413
|
+
status: inner.status ?? obj.status,
|
|
43414
|
+
headers: obj.headers
|
|
43415
|
+
};
|
|
43416
|
+
}
|
|
43417
|
+
return { message: String(error45) };
|
|
43418
|
+
}
|
|
43419
|
+
function parseRetryAfterMs(headers) {
|
|
43420
|
+
if (!headers)
|
|
43421
|
+
return;
|
|
43422
|
+
const retryAfter = headers["retry-after"] ?? headers["Retry-After"];
|
|
43423
|
+
if (retryAfter) {
|
|
43424
|
+
const seconds = Number(retryAfter);
|
|
43425
|
+
if (!isNaN(seconds) && seconds > 0) {
|
|
43426
|
+
return seconds * 1000;
|
|
43427
|
+
}
|
|
43428
|
+
}
|
|
43429
|
+
const reset = headers["x-ratelimit-reset"] ?? headers["X-Ratelimit-Reset"];
|
|
43430
|
+
if (reset) {
|
|
43431
|
+
const resetTimestamp = Number(reset);
|
|
43432
|
+
if (!isNaN(resetTimestamp)) {
|
|
43433
|
+
const resetMs = resetTimestamp > 1000000000000 ? resetTimestamp : resetTimestamp * 1000;
|
|
43434
|
+
const delayMs = resetMs - Date.now();
|
|
43435
|
+
return delayMs > 0 ? delayMs : 0;
|
|
43436
|
+
}
|
|
43437
|
+
}
|
|
43438
|
+
return;
|
|
43439
|
+
}
|
|
43440
|
+
function isContextOverflow(message, code) {
|
|
43441
|
+
const lowerMessage = message.toLowerCase();
|
|
43442
|
+
return lowerMessage.includes("context_length_exceeded") || lowerMessage.includes("prompt is too long") || lowerMessage.includes("maximum context length") || code === "context_length_exceeded";
|
|
43443
|
+
}
|
|
43444
|
+
function isZhipuQuotaCode(code) {
|
|
43445
|
+
if (typeof code !== "number")
|
|
43446
|
+
return false;
|
|
43447
|
+
return [1113, 1304, 1308, 1309].includes(code);
|
|
43448
|
+
}
|
|
43449
|
+
function isZhipuRateLimitCode(code) {
|
|
43450
|
+
if (typeof code !== "number")
|
|
43451
|
+
return false;
|
|
43452
|
+
return [1302, 1303].includes(code);
|
|
43453
|
+
}
|
|
43454
|
+
function isZhipuOverloadedCode(code) {
|
|
43455
|
+
if (typeof code !== "number")
|
|
43456
|
+
return false;
|
|
43457
|
+
return code === 1312;
|
|
43458
|
+
}
|
|
43459
|
+
function isGeminiQuotaDetails(details) {
|
|
43460
|
+
if (!Array.isArray(details))
|
|
43461
|
+
return false;
|
|
43462
|
+
return details.some((d) => d?.["@type"]?.includes("QuotaFailure") || d?.["@type"]?.includes("google.rpc.QuotaFailure"));
|
|
43463
|
+
}
|
|
43464
|
+
function isGeminiPerMinuteLimit(message, details) {
|
|
43465
|
+
const lowerMessage = message.toLowerCase();
|
|
43466
|
+
if (lowerMessage.includes("per_minute") || lowerMessage.includes("per minute")) {
|
|
43467
|
+
return true;
|
|
43468
|
+
}
|
|
43469
|
+
if (Array.isArray(details)) {
|
|
43470
|
+
return details.some((d) => d?.["@type"]?.includes("RetryInfo"));
|
|
43471
|
+
}
|
|
43472
|
+
return false;
|
|
43473
|
+
}
|
|
43474
|
+
function classifyProviderError(error45) {
|
|
43475
|
+
const info = extractErrorInfo(error45);
|
|
43476
|
+
const { statusCode, code, type: type2, message, status, headers } = info;
|
|
43477
|
+
if (isContextOverflow(message, code)) {
|
|
43478
|
+
return {
|
|
43479
|
+
category: "context_overflow",
|
|
43480
|
+
retryable: false,
|
|
43481
|
+
shouldFallback: false,
|
|
43482
|
+
statusCode,
|
|
43483
|
+
reason: "Context length exceeded, prompt too long for model"
|
|
43484
|
+
};
|
|
43485
|
+
}
|
|
43486
|
+
if (statusCode === 401 || statusCode === 403) {
|
|
43487
|
+
return {
|
|
43488
|
+
category: "auth",
|
|
43489
|
+
retryable: false,
|
|
43490
|
+
shouldFallback: false,
|
|
43491
|
+
statusCode,
|
|
43492
|
+
reason: statusCode === 401 ? "Invalid API key or authentication" : "Permission denied"
|
|
43493
|
+
};
|
|
43494
|
+
}
|
|
43495
|
+
if (statusCode === 400) {
|
|
43496
|
+
return {
|
|
43497
|
+
category: "bad_request",
|
|
43498
|
+
retryable: false,
|
|
43499
|
+
shouldFallback: false,
|
|
43500
|
+
statusCode,
|
|
43501
|
+
reason: "Invalid request parameters"
|
|
43502
|
+
};
|
|
43503
|
+
}
|
|
43504
|
+
if (statusCode === 429 && (code === "insufficient_quota" || type2 === "insufficient_quota")) {
|
|
43505
|
+
return {
|
|
43506
|
+
category: "quota",
|
|
43507
|
+
retryable: false,
|
|
43508
|
+
shouldFallback: true,
|
|
43509
|
+
statusCode,
|
|
43510
|
+
providerGuess: "openai",
|
|
43511
|
+
reason: "OpenAI quota exceeded, billing issue"
|
|
43512
|
+
};
|
|
43513
|
+
}
|
|
43514
|
+
if (statusCode === 402 && type2 === "billing_error") {
|
|
43515
|
+
return {
|
|
43516
|
+
category: "quota",
|
|
43517
|
+
retryable: false,
|
|
43518
|
+
shouldFallback: true,
|
|
43519
|
+
statusCode,
|
|
43520
|
+
providerGuess: "anthropic",
|
|
43521
|
+
reason: "Anthropic billing error, payment required"
|
|
43522
|
+
};
|
|
43523
|
+
}
|
|
43524
|
+
if (statusCode === 429 && status === "RESOURCE_EXHAUSTED" && isGeminiQuotaDetails(info.headers ? undefined : error45?.error?.details)) {
|
|
43525
|
+
return {
|
|
43526
|
+
category: "quota",
|
|
43527
|
+
retryable: false,
|
|
43528
|
+
shouldFallback: true,
|
|
43529
|
+
statusCode,
|
|
43530
|
+
providerGuess: "gemini",
|
|
43531
|
+
reason: "Gemini daily quota exceeded"
|
|
43532
|
+
};
|
|
43533
|
+
}
|
|
43534
|
+
if (statusCode === 429 && isZhipuQuotaCode(code)) {
|
|
43535
|
+
const quotaReasons = {
|
|
43536
|
+
1113: "\u8D26\u6237\u6B20\u8D39",
|
|
43537
|
+
1304: "\u8C03\u7528\u6B21\u6570\u8D85\u8FC7\u9650\u989D",
|
|
43538
|
+
1308: "\u4F7F\u7528\u91CF\u8D85\u8FC7\u4E0A\u9650",
|
|
43539
|
+
1309: "\u5957\u9910\u5DF2\u5230\u671F"
|
|
43540
|
+
};
|
|
43541
|
+
return {
|
|
43542
|
+
category: "quota",
|
|
43543
|
+
retryable: false,
|
|
43544
|
+
shouldFallback: true,
|
|
43545
|
+
statusCode,
|
|
43546
|
+
providerGuess: "zhipu",
|
|
43547
|
+
reason: `Zhipu/GLM: ${quotaReasons[code] ?? "quota exceeded"}`
|
|
43548
|
+
};
|
|
43549
|
+
}
|
|
43550
|
+
if (statusCode === 529 && type2 === "overloaded_error") {
|
|
43551
|
+
return {
|
|
43552
|
+
category: "overloaded",
|
|
43553
|
+
retryable: true,
|
|
43554
|
+
shouldFallback: false,
|
|
43555
|
+
statusCode,
|
|
43556
|
+
providerGuess: "anthropic",
|
|
43557
|
+
reason: "Anthropic API overloaded"
|
|
43558
|
+
};
|
|
43559
|
+
}
|
|
43560
|
+
if (statusCode === 429 && isZhipuOverloadedCode(code)) {
|
|
43561
|
+
return {
|
|
43562
|
+
category: "overloaded",
|
|
43563
|
+
retryable: true,
|
|
43564
|
+
shouldFallback: false,
|
|
43565
|
+
statusCode,
|
|
43566
|
+
providerGuess: "zhipu",
|
|
43567
|
+
reason: "Zhipu/GLM: \u5F53\u524D\u8D1F\u8F7D\u8FC7\u9AD8"
|
|
43568
|
+
};
|
|
43569
|
+
}
|
|
43570
|
+
if (statusCode === 429 && type2 === "rate_limit_error") {
|
|
43571
|
+
return {
|
|
43572
|
+
category: "rate_limit",
|
|
43573
|
+
retryable: true,
|
|
43574
|
+
shouldFallback: false,
|
|
43575
|
+
statusCode,
|
|
43576
|
+
providerGuess: "anthropic",
|
|
43577
|
+
retryAfterMs: parseRetryAfterMs(headers),
|
|
43578
|
+
reason: "Anthropic rate limit exceeded"
|
|
43579
|
+
};
|
|
43580
|
+
}
|
|
43581
|
+
if (statusCode === 429 && code === "rate_limit_exceeded") {
|
|
43582
|
+
return {
|
|
43583
|
+
category: "rate_limit",
|
|
43584
|
+
retryable: true,
|
|
43585
|
+
shouldFallback: false,
|
|
43586
|
+
statusCode,
|
|
43587
|
+
providerGuess: "openai",
|
|
43588
|
+
retryAfterMs: parseRetryAfterMs(headers),
|
|
43589
|
+
reason: "OpenAI rate limit exceeded"
|
|
43590
|
+
};
|
|
43591
|
+
}
|
|
43592
|
+
if (statusCode === 429 && status === "RESOURCE_EXHAUSTED" && isGeminiPerMinuteLimit(message, error45?.error?.details)) {
|
|
43593
|
+
return {
|
|
43594
|
+
category: "rate_limit",
|
|
43595
|
+
retryable: true,
|
|
43596
|
+
shouldFallback: false,
|
|
43597
|
+
statusCode,
|
|
43598
|
+
providerGuess: "gemini",
|
|
43599
|
+
retryAfterMs: parseRetryAfterMs(headers),
|
|
43600
|
+
reason: "Gemini per-minute rate limit exceeded"
|
|
43601
|
+
};
|
|
43602
|
+
}
|
|
43603
|
+
if (statusCode === 429 && isZhipuRateLimitCode(code)) {
|
|
43604
|
+
const rateLimitReasons = {
|
|
43605
|
+
1302: "\u5E76\u53D1\u8BF7\u6C42\u8D85\u8FC7\u9650\u5236",
|
|
43606
|
+
1303: "\u8BF7\u6C42\u9891\u7387\u8D85\u8FC7\u9650\u5236"
|
|
43607
|
+
};
|
|
43608
|
+
return {
|
|
43609
|
+
category: "rate_limit",
|
|
43610
|
+
retryable: true,
|
|
43611
|
+
shouldFallback: false,
|
|
43612
|
+
statusCode,
|
|
43613
|
+
providerGuess: "zhipu",
|
|
43614
|
+
retryAfterMs: parseRetryAfterMs(headers),
|
|
43615
|
+
reason: `Zhipu/GLM: ${rateLimitReasons[code] ?? "rate limit exceeded"}`
|
|
43616
|
+
};
|
|
43617
|
+
}
|
|
43618
|
+
if (statusCode === 429) {
|
|
43619
|
+
return {
|
|
43620
|
+
category: "rate_limit",
|
|
43621
|
+
retryable: true,
|
|
43622
|
+
shouldFallback: false,
|
|
43623
|
+
statusCode,
|
|
43624
|
+
retryAfterMs: parseRetryAfterMs(headers),
|
|
43625
|
+
reason: "Rate limit exceeded (generic 429)"
|
|
43626
|
+
};
|
|
43627
|
+
}
|
|
43628
|
+
return {
|
|
43629
|
+
category: "unknown",
|
|
43630
|
+
retryable: false,
|
|
43631
|
+
shouldFallback: false,
|
|
43632
|
+
statusCode,
|
|
43633
|
+
reason: `Unknown error: ${message.substring(0, 100)}`
|
|
43634
|
+
};
|
|
43635
|
+
}
|
|
43636
|
+
|
|
43637
|
+
// src/tools/delegate-task/tools.ts
|
|
43260
43638
|
var SISYPHUS_JUNIOR_AGENT = "sisyphus-junior";
|
|
43261
43639
|
function parseModelString(model) {
|
|
43262
43640
|
const parts = model.split("/");
|
|
@@ -43868,7 +44246,12 @@ Session ID: ${sessionID}`;
|
|
|
43868
44246
|
path: { id: sessionID }
|
|
43869
44247
|
});
|
|
43870
44248
|
if (messagesResult.error) {
|
|
43871
|
-
|
|
44249
|
+
const classification = classifyProviderError(messagesResult.error);
|
|
44250
|
+
const diagnosis = classification.category !== "unknown" ? `
|
|
44251
|
+
|
|
44252
|
+
\uD83D\uDD0D **\u9519\u8BEF\u5206\u7C7B**: ${classification.reason}
|
|
44253
|
+
${classification.shouldFallback ? "\uD83D\uDCA1 \u6B64\u9519\u8BEF\u53EF\u901A\u8FC7 runtime fallback \u81EA\u52A8\u5904\u7406\u3002" : classification.retryable ? "\u23F3 \u6B64\u9519\u8BEF\u53EF\u91CD\u8BD5\u3002" : ""}` : "";
|
|
44254
|
+
return `Error fetching result: ${messagesResult.error}${diagnosis}
|
|
43872
44255
|
|
|
43873
44256
|
Session ID: ${sessionID}`;
|
|
43874
44257
|
}
|
|
@@ -43906,13 +44289,14 @@ ${textContent || "(\u65E0\u6587\u672C\u8F93\u51FA)"}
|
|
|
43906
44289
|
if (syncSessionID) {
|
|
43907
44290
|
subagentSessions.delete(syncSessionID);
|
|
43908
44291
|
}
|
|
43909
|
-
|
|
43910
|
-
|
|
43911
|
-
|
|
43912
|
-
|
|
43913
|
-
|
|
43914
|
-
|
|
43915
|
-
|
|
44292
|
+
const classification = classifyProviderError(error45);
|
|
44293
|
+
const diagnosis = classification.category !== "unknown" ? `
|
|
44294
|
+
|
|
44295
|
+
\uD83D\uDD0D **\u9519\u8BEF\u5206\u7C7B**: ${classification.reason}
|
|
44296
|
+
${classification.shouldFallback ? "\uD83D\uDCA1 \u6B64\u9519\u8BEF\u53EF\u901A\u8FC7 runtime fallback \u81EA\u52A8\u5904\u7406\u3002" : classification.retryable ? "\u23F3 \u6B64\u9519\u8BEF\u53EF\u91CD\u8BD5\u3002" : ""}` : "";
|
|
44297
|
+
return `\u4EFB\u52A1\u6267\u884C\u5931\u8D25: ${error45 instanceof Error ? error45.message : String(error45)}${diagnosis}
|
|
44298
|
+
|
|
44299
|
+
Session ID: ${syncSessionID ?? "unknown"}`;
|
|
43916
44300
|
}
|
|
43917
44301
|
}
|
|
43918
44302
|
});
|
|
@@ -44041,6 +44425,9 @@ class ConcurrencyManager {
|
|
|
44041
44425
|
}
|
|
44042
44426
|
}
|
|
44043
44427
|
|
|
44428
|
+
// src/features/background-agent/manager.ts
|
|
44429
|
+
init_runtime_fallback();
|
|
44430
|
+
|
|
44044
44431
|
// src/features/background-agent/perf-aggregator.ts
|
|
44045
44432
|
function percentile(sorted, p) {
|
|
44046
44433
|
if (sorted.length === 0)
|
|
@@ -44293,13 +44680,58 @@ class BackgroundManager {
|
|
|
44293
44680
|
log("[background-agent] promptAsync error:", error45);
|
|
44294
44681
|
const existingTask = this.findBySession(sessionID);
|
|
44295
44682
|
if (existingTask) {
|
|
44296
|
-
|
|
44297
|
-
|
|
44298
|
-
|
|
44299
|
-
|
|
44683
|
+
const classification = classifyProviderError(error45);
|
|
44684
|
+
if (classification.retryable || classification.shouldFallback) {
|
|
44685
|
+
const attempts = existingTask.attempts ?? [];
|
|
44686
|
+
const currentModel = input.model ?? { providerID: "", modelID: "" };
|
|
44687
|
+
const fallbackResult = resolveNextFallbackModel({
|
|
44688
|
+
agent: input.agent,
|
|
44689
|
+
currentModel,
|
|
44690
|
+
attempts,
|
|
44691
|
+
lastErrorClassification: classification
|
|
44692
|
+
});
|
|
44693
|
+
if (fallbackResult.kind === "next") {
|
|
44694
|
+
existingTask.attempts = [...attempts, {
|
|
44695
|
+
model: fallbackResult.model,
|
|
44696
|
+
error: classification
|
|
44697
|
+
}];
|
|
44698
|
+
log("[background-agent] Fallback to model:", fallbackResult.model);
|
|
44699
|
+
this.client.session.prompt({
|
|
44700
|
+
path: { id: sessionID },
|
|
44701
|
+
body: {
|
|
44702
|
+
agent: input.agent,
|
|
44703
|
+
model: fallbackResult.model,
|
|
44704
|
+
parts: [{ type: "text", text: input.prompt }]
|
|
44705
|
+
}
|
|
44706
|
+
}).catch((retryError) => {
|
|
44707
|
+
log("[background-agent] Fallback prompt error:", retryError);
|
|
44708
|
+
const task2 = this.findBySession(sessionID);
|
|
44709
|
+
if (task2) {
|
|
44710
|
+
task2.status = "error";
|
|
44711
|
+
task2.error = `Fallback failed: ${retryError instanceof Error ? retryError.message : String(retryError)}`;
|
|
44712
|
+
task2.completedAt = new Date;
|
|
44713
|
+
if (task2.concurrencyKey) {
|
|
44714
|
+
this.concurrencyManager.release(task2.concurrencyKey);
|
|
44715
|
+
task2.concurrencyKey = undefined;
|
|
44716
|
+
}
|
|
44717
|
+
this.markForNotification(task2);
|
|
44718
|
+
this.notifyParentSession(task2).catch((err) => {
|
|
44719
|
+
log("[background-agent] Failed to notify on fallback error:", err);
|
|
44720
|
+
});
|
|
44721
|
+
}
|
|
44722
|
+
});
|
|
44723
|
+
return;
|
|
44724
|
+
}
|
|
44725
|
+
existingTask.error = `All fallback models exhausted. Last error: ${classification.reason}`;
|
|
44300
44726
|
} else {
|
|
44301
|
-
|
|
44727
|
+
const errorMessage = error45 instanceof Error ? error45.message : String(error45);
|
|
44728
|
+
if (errorMessage.includes("agent.name") || errorMessage.includes("undefined")) {
|
|
44729
|
+
existingTask.error = `Agent "${input.agent}" not found. Make sure the agent is registered in your opencode.json or provided by a plugin.`;
|
|
44730
|
+
} else {
|
|
44731
|
+
existingTask.error = errorMessage;
|
|
44732
|
+
}
|
|
44302
44733
|
}
|
|
44734
|
+
existingTask.status = "error";
|
|
44303
44735
|
existingTask.completedAt = new Date;
|
|
44304
44736
|
if (existingTask.concurrencyKey) {
|
|
44305
44737
|
this.concurrencyManager.release(existingTask.concurrencyKey);
|
|
@@ -63126,6 +63558,7 @@ var HookNameSchema = exports_external2.enum([
|
|
|
63126
63558
|
"auto-slash-command",
|
|
63127
63559
|
"edit-error-recovery",
|
|
63128
63560
|
"delegate-task-retry",
|
|
63561
|
+
"runtime-fallback",
|
|
63129
63562
|
"prometheus-md-only",
|
|
63130
63563
|
"perf-profiler",
|
|
63131
63564
|
"start-work",
|
|
@@ -63312,6 +63745,15 @@ var GitMasterConfigSchema = exports_external2.object({
|
|
|
63312
63745
|
commit_footer: exports_external2.boolean().default(true),
|
|
63313
63746
|
include_co_authored_by: exports_external2.boolean().default(true)
|
|
63314
63747
|
});
|
|
63748
|
+
var RuntimeFallbackConfigSchema = exports_external2.object({
|
|
63749
|
+
enabled: exports_external2.boolean().default(true),
|
|
63750
|
+
max_attempts: exports_external2.number().min(0).default(3),
|
|
63751
|
+
initial_delay_ms: exports_external2.number().min(0).default(2000),
|
|
63752
|
+
backoff_factor: exports_external2.number().min(1).default(2),
|
|
63753
|
+
max_delay_ms: exports_external2.number().min(0).default(30000),
|
|
63754
|
+
respect_retry_after: exports_external2.boolean().default(true),
|
|
63755
|
+
jitter: exports_external2.boolean().default(true)
|
|
63756
|
+
});
|
|
63315
63757
|
var OhMyOpenCodeConfigSchema = exports_external2.object({
|
|
63316
63758
|
$schema: exports_external2.string().optional(),
|
|
63317
63759
|
disabled_mcps: exports_external2.array(AnyMcpNameSchema).optional(),
|
|
@@ -63330,7 +63772,8 @@ var OhMyOpenCodeConfigSchema = exports_external2.object({
|
|
|
63330
63772
|
ralph_loop: RalphLoopConfigSchema.optional(),
|
|
63331
63773
|
background_task: BackgroundTaskConfigSchema.optional(),
|
|
63332
63774
|
notification: NotificationConfigSchema.optional(),
|
|
63333
|
-
git_master: GitMasterConfigSchema.optional()
|
|
63775
|
+
git_master: GitMasterConfigSchema.optional(),
|
|
63776
|
+
runtime_fallback: RuntimeFallbackConfigSchema.optional()
|
|
63334
63777
|
});
|
|
63335
63778
|
// src/plugin-config.ts
|
|
63336
63779
|
init_shared();
|
package/dist/shared/index.d.ts
CHANGED
|
@@ -28,6 +28,7 @@ export * from "./agent-tool-restrictions";
|
|
|
28
28
|
export * from "./model-requirements";
|
|
29
29
|
export * from "./model-resolver";
|
|
30
30
|
export * from "./model-availability";
|
|
31
|
+
export * from "./runtime-fallback";
|
|
31
32
|
export * from "./perf-timer";
|
|
32
33
|
export * from "./perf-tracer";
|
|
33
34
|
export * from "./case-insensitive";
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider Error Classifier
|
|
3
|
+
*
|
|
4
|
+
* 统一的 provider 错误分类逻辑,用于判断错误类型和是否可重试/fallback。
|
|
5
|
+
* 支持 OpenAI、Anthropic、Gemini、xAI、Zhipu 等主流 provider。
|
|
6
|
+
*/
|
|
7
|
+
export type ErrorCategory = "rate_limit" | "quota" | "overloaded" | "context_overflow" | "auth" | "bad_request" | "unknown";
|
|
8
|
+
export interface ProviderErrorClassification {
|
|
9
|
+
category: ErrorCategory;
|
|
10
|
+
retryable: boolean;
|
|
11
|
+
shouldFallback: boolean;
|
|
12
|
+
statusCode?: number;
|
|
13
|
+
providerGuess?: string;
|
|
14
|
+
retryAfterMs?: number;
|
|
15
|
+
reason: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* 分类 provider 错误
|
|
19
|
+
*
|
|
20
|
+
* @param error - 未知类型的错误对象
|
|
21
|
+
* @returns 包含错误分类、可重试性、是否应该 fallback 等信息
|
|
22
|
+
*/
|
|
23
|
+
export declare function classifyProviderError(error: unknown): ProviderErrorClassification;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retry/backoff 策略服务
|
|
3
|
+
*
|
|
4
|
+
* 提供纯函数式的重试决策计算,支持指数退避、Retry-After 和 Jitter。
|
|
5
|
+
*/
|
|
6
|
+
export interface RetryConfig {
|
|
7
|
+
/** 最大重试次数 */
|
|
8
|
+
max_attempts: number;
|
|
9
|
+
/** 初始延迟(毫秒) */
|
|
10
|
+
initial_delay_ms: number;
|
|
11
|
+
/** 退避因子 */
|
|
12
|
+
backoff_factor: number;
|
|
13
|
+
/** 最大延迟(毫秒) */
|
|
14
|
+
max_delay_ms: number;
|
|
15
|
+
/** 是否启用 jitter */
|
|
16
|
+
jitter: boolean;
|
|
17
|
+
/** 是否尊重 Retry-After 头 */
|
|
18
|
+
respect_retry_after: boolean;
|
|
19
|
+
}
|
|
20
|
+
export interface RetryDecision {
|
|
21
|
+
/** 是否可重试 */
|
|
22
|
+
retryable: boolean;
|
|
23
|
+
/** 延迟时间(毫秒) */
|
|
24
|
+
delay_ms: number;
|
|
25
|
+
/** 当前尝试次数 */
|
|
26
|
+
attempt: number;
|
|
27
|
+
/** 决策原因 */
|
|
28
|
+
reason: string;
|
|
29
|
+
}
|
|
30
|
+
export declare const DEFAULT_RETRY_CONFIG: RetryConfig;
|
|
31
|
+
/**
|
|
32
|
+
* 计算重试延迟
|
|
33
|
+
*
|
|
34
|
+
* @param attempt - 当前尝试次数(从 0 开始)
|
|
35
|
+
* @param config - 重试配置
|
|
36
|
+
* @param retryAfterMs - 可选的 Retry-After 值(毫秒)
|
|
37
|
+
* @returns 重试决策
|
|
38
|
+
*/
|
|
39
|
+
export declare function calculateRetryDelay(attempt: number, config: RetryConfig, retryAfterMs?: number): RetryDecision;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime Fallback Decision Service
|
|
3
|
+
*
|
|
4
|
+
* 纯函数:根据 agent/category 的 fallback chain,结合当前失败状态和可用模型,
|
|
5
|
+
* 决定下一个要尝试的模型。
|
|
6
|
+
*/
|
|
7
|
+
import type { ProviderErrorClassification } from "./provider-error-classifier";
|
|
8
|
+
export interface FallbackModel {
|
|
9
|
+
providerID: string;
|
|
10
|
+
modelID: string;
|
|
11
|
+
variant?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface FallbackAttempt {
|
|
14
|
+
model: FallbackModel;
|
|
15
|
+
error?: ProviderErrorClassification;
|
|
16
|
+
}
|
|
17
|
+
export interface FallbackNextResult {
|
|
18
|
+
kind: "next";
|
|
19
|
+
model: FallbackModel;
|
|
20
|
+
attempts: FallbackAttempt[];
|
|
21
|
+
}
|
|
22
|
+
export interface FallbackExhaustedResult {
|
|
23
|
+
kind: "exhausted";
|
|
24
|
+
attempts: FallbackAttempt[];
|
|
25
|
+
lastErrorClassification?: ProviderErrorClassification;
|
|
26
|
+
}
|
|
27
|
+
export type FallbackResult = FallbackNextResult | FallbackExhaustedResult;
|
|
28
|
+
export interface RuntimeFallbackInput {
|
|
29
|
+
agent?: string;
|
|
30
|
+
category?: string;
|
|
31
|
+
currentModel: FallbackModel;
|
|
32
|
+
attempts: FallbackAttempt[];
|
|
33
|
+
availableModels?: Set<string>;
|
|
34
|
+
lastErrorClassification?: ProviderErrorClassification;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* 解析下一个 fallback 模型
|
|
38
|
+
*
|
|
39
|
+
* 逻辑:
|
|
40
|
+
* 1. 从 AGENT_MODEL_REQUIREMENTS 或 CATEGORY_MODEL_REQUIREMENTS 获取 fallbackChain
|
|
41
|
+
* 2. 将 chain 展开为候选列表(每个 provider × model 组合,保持顺序)
|
|
42
|
+
* 3. 跳过 currentModel 和 attempts 中的 model
|
|
43
|
+
* 4. 如果 availableModels 非空,使用 fuzzyMatchModel 检查可用性
|
|
44
|
+
* 5. 返回第一个有效候选,或 exhausted
|
|
45
|
+
*/
|
|
46
|
+
export declare function resolveNextFallbackModel(input: RuntimeFallbackInput): FallbackResult;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skj1724/oh-my-opencode",
|
|
3
|
-
"version": "3.19.
|
|
3
|
+
"version": "3.19.7",
|
|
4
4
|
"description": "The Best AI Agent Harness - Batteries-Included OpenCode Plugin with Multi-Model Orchestration, Parallel Background Agents, and Crafted LSP/AST Tools",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|