opencode-copilot-failover 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +65 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/constants.d.ts +11 -0
- package/dist/lib/constants.d.ts.map +1 -0
- package/dist/lib/constants.js +11 -0
- package/dist/lib/constants.js.map +1 -0
- package/dist/lib/error-detector.d.ts +12 -0
- package/dist/lib/error-detector.d.ts.map +1 -0
- package/dist/lib/error-detector.js +97 -0
- package/dist/lib/error-detector.js.map +1 -0
- package/dist/lib/failover-engine.d.ts +29 -0
- package/dist/lib/failover-engine.d.ts.map +1 -0
- package/dist/lib/failover-engine.js +199 -0
- package/dist/lib/failover-engine.js.map +1 -0
- package/dist/lib/model-mapper.d.ts +49 -0
- package/dist/lib/model-mapper.d.ts.map +1 -0
- package/dist/lib/model-mapper.js +140 -0
- package/dist/lib/model-mapper.js.map +1 -0
- package/package.json +51 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025-2026 pl4fun
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# opencode-copilot-failover
|
|
2
|
+
|
|
3
|
+
Automatic provider failover plugin for opencode — switches to GitHub Copilot when primary providers fail.
|
|
4
|
+
|
|
5
|
+
## How it works
|
|
6
|
+
|
|
7
|
+
1. Listens for `session.error` events after all built-in retries are exhausted
|
|
8
|
+
2. Detects retryable errors (429, 500, 502, 503, 529, unknown errors)
|
|
9
|
+
3. Maps the failed model to its GitHub Copilot equivalent
|
|
10
|
+
4. Re-prompts the session via the copilot provider
|
|
11
|
+
5. Shows a toast notification in the TUI
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
Add to your `opencode.json`:
|
|
16
|
+
|
|
17
|
+
```json
|
|
18
|
+
{
|
|
19
|
+
"plugin": ["opencode-copilot-failover"]
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Or alongside other plugins:
|
|
24
|
+
|
|
25
|
+
```json
|
|
26
|
+
{
|
|
27
|
+
"plugin": ["oh-my-opencode@latest", "opencode-openai-codex-auth", "opencode-copilot-failover"]
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Supported Models
|
|
32
|
+
|
|
33
|
+
| Source Model | Copilot Model |
|
|
34
|
+
| --- | --- |
|
|
35
|
+
| `claude-opus-4-6` | `claude-opus-4.6` |
|
|
36
|
+
| `claude-opus-4-5` | `claude-opus-4.5` |
|
|
37
|
+
| `claude-sonnet-4-5` | `claude-sonnet-4.5` |
|
|
38
|
+
| `claude-sonnet-4` | `claude-sonnet-4` |
|
|
39
|
+
| `claude-haiku-4-5` | `claude-haiku-4.5` |
|
|
40
|
+
| `gpt-5.3-codex` | `gpt-5.3-codex` |
|
|
41
|
+
| `gpt-5.2-codex` | `gpt-5.2-codex` |
|
|
42
|
+
| `gpt-5.2` | `gpt-5.2` |
|
|
43
|
+
| `gpt-5.1-codex-max` | `gpt-5.1-codex-max` |
|
|
44
|
+
| `gpt-5.1-codex` | `gpt-5.1-codex` |
|
|
45
|
+
| `gpt-5.1-codex-mini` | `gpt-5.1-codex-mini` |
|
|
46
|
+
| `gpt-5.1` | `gpt-5.1` |
|
|
47
|
+
| `gpt-4.1` | `gpt-4.1` |
|
|
48
|
+
| `gpt-5` | `gpt-5` |
|
|
49
|
+
| `gpt-5-mini` | `gpt-5-mini` |
|
|
50
|
+
| `gemini-2.5-pro` | `gemini-2.5-pro` |
|
|
51
|
+
| `gemini-3-flash` | `gemini-3-flash` |
|
|
52
|
+
| `gemini-3-pro` | `gemini-3-pro` |
|
|
53
|
+
|
|
54
|
+
## Behavior
|
|
55
|
+
|
|
56
|
+
- **Zero-config** — works out of the box
|
|
57
|
+
- **Per-request failover** — always tries the primary provider first
|
|
58
|
+
- **Prevents infinite loops** — won't failover if already on copilot
|
|
59
|
+
- **Prevents duplicate failovers** — won't retry the same message twice
|
|
60
|
+
|
|
61
|
+
## Limitations
|
|
62
|
+
|
|
63
|
+
- GitHub Copilot must be authenticated in opencode
|
|
64
|
+
- Not all models are available on copilot (unmapped models are skipped)
|
|
65
|
+
- If copilot also fails, no further failover occurs
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAIlD,QAAA,MAAM,MAAM,EAAE,MAUb,CAAC;AAEF,eAAe,MAAM,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { CopilotFailoverEngine } from "./lib/failover-engine.js";
|
|
2
|
+
const plugin = async (ctx) => {
|
|
3
|
+
const engine = new CopilotFailoverEngine(ctx.client);
|
|
4
|
+
return {
|
|
5
|
+
event: async ({ event }) => {
|
|
6
|
+
if (event.type === "session.error") {
|
|
7
|
+
await engine.handleSessionError(event);
|
|
8
|
+
}
|
|
9
|
+
},
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
export default plugin;
|
|
13
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AAEjE,MAAM,MAAM,GAAW,KAAK,EAAE,GAAG,EAAE,EAAE;IACnC,MAAM,MAAM,GAAG,IAAI,qBAAqB,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IAErD,OAAO;QACL,KAAK,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE;YACzB,IAAI,KAAK,CAAC,IAAI,KAAK,eAAe,EAAE,CAAC;gBACnC,MAAM,MAAM,CAAC,kBAAkB,CAAC,KAA0B,CAAC,CAAC;YAC9D,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC,CAAC;AAEF,eAAe,MAAM,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Plugin identifier for logging and toast notifications */
|
|
2
|
+
export declare const PLUGIN_NAME: "opencode-copilot-failover";
|
|
3
|
+
/** The target failover provider ID */
|
|
4
|
+
export declare const COPILOT_PROVIDER_ID: "github-copilot";
|
|
5
|
+
/** Providers eligible for failover (any non-copilot provider) */
|
|
6
|
+
export declare const FAILOVER_PROVIDERS: readonly ["anthropic", "openai"];
|
|
7
|
+
/** HTTP status codes that trigger failover */
|
|
8
|
+
export declare const RETRYABLE_STATUS_CODES: readonly [429, 500, 502, 503, 529];
|
|
9
|
+
/** Log prefix for console messages */
|
|
10
|
+
export declare const LOG_PREFIX: "[copilot-failover]";
|
|
11
|
+
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../lib/constants.ts"],"names":[],"mappings":"AAAA,4DAA4D;AAC5D,eAAO,MAAM,WAAW,EAAG,2BAAoC,CAAC;AAEhE,sCAAsC;AACtC,eAAO,MAAM,mBAAmB,EAAG,gBAAyB,CAAC;AAE7D,iEAAiE;AACjE,eAAO,MAAM,kBAAkB,kCAAmC,CAAC;AAEnE,8CAA8C;AAC9C,eAAO,MAAM,sBAAsB,oCAAqC,CAAC;AAEzE,sCAAsC;AACtC,eAAO,MAAM,UAAU,EAAG,oBAA6B,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Plugin identifier for logging and toast notifications */
|
|
2
|
+
export const PLUGIN_NAME = "opencode-copilot-failover";
|
|
3
|
+
/** The target failover provider ID */
|
|
4
|
+
export const COPILOT_PROVIDER_ID = "github-copilot";
|
|
5
|
+
/** Providers eligible for failover (any non-copilot provider) */
|
|
6
|
+
export const FAILOVER_PROVIDERS = ["anthropic", "openai"];
|
|
7
|
+
/** HTTP status codes that trigger failover */
|
|
8
|
+
export const RETRYABLE_STATUS_CODES = [429, 500, 502, 503, 529];
|
|
9
|
+
/** Log prefix for console messages */
|
|
10
|
+
export const LOG_PREFIX = "[copilot-failover]";
|
|
11
|
+
//# sourceMappingURL=constants.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../../lib/constants.ts"],"names":[],"mappings":"AAAA,4DAA4D;AAC5D,MAAM,CAAC,MAAM,WAAW,GAAG,2BAAoC,CAAC;AAEhE,sCAAsC;AACtC,MAAM,CAAC,MAAM,mBAAmB,GAAG,gBAAyB,CAAC;AAE7D,iEAAiE;AACjE,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,WAAW,EAAE,QAAQ,CAAU,CAAC;AAEnE,8CAA8C;AAC9C,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAU,CAAC;AAEzE,sCAAsC;AACtC,MAAM,CAAC,MAAM,UAAU,GAAG,oBAA6B,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ApiError, EventSessionError, MessageAbortedError, MessageOutputLengthError, ProviderAuthError, UnknownError } from "@opencode-ai/sdk";
|
|
2
|
+
export type FailoverTrigger = {
|
|
3
|
+
reason: string;
|
|
4
|
+
statusCode?: number;
|
|
5
|
+
provider: string;
|
|
6
|
+
model: string;
|
|
7
|
+
};
|
|
8
|
+
type SessionError = ProviderAuthError | UnknownError | MessageOutputLengthError | MessageAbortedError | ApiError;
|
|
9
|
+
export declare function isRetryableProviderError(error: SessionError): boolean;
|
|
10
|
+
export declare function extractFailoverTrigger(event: EventSessionError): FailoverTrigger | null;
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=error-detector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-detector.d.ts","sourceRoot":"","sources":["../../lib/error-detector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,QAAQ,EACR,iBAAiB,EACjB,mBAAmB,EACnB,wBAAwB,EACxB,iBAAiB,EACjB,YAAY,EACb,MAAM,kBAAkB,CAAC;AAI1B,MAAM,MAAM,eAAe,GAAG;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,KAAK,YAAY,GACb,iBAAiB,GACjB,YAAY,GACZ,wBAAwB,GACxB,mBAAmB,GACnB,QAAQ,CAAC;AA2Bb,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAoCrE;AAYD,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,iBAAiB,GACvB,eAAe,GAAG,IAAI,CAiBxB"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { RETRYABLE_STATUS_CODES } from "./constants.js";
|
|
2
|
+
const RETRYABLE_TEXT_SIGNALS = [
|
|
3
|
+
"rate limit",
|
|
4
|
+
"too many requests",
|
|
5
|
+
"quota",
|
|
6
|
+
"usage limit",
|
|
7
|
+
"billing limit",
|
|
8
|
+
"capacity",
|
|
9
|
+
"overloaded",
|
|
10
|
+
"service unavailable",
|
|
11
|
+
"temporarily unavailable",
|
|
12
|
+
"timeout",
|
|
13
|
+
"timed out",
|
|
14
|
+
"deadline exceeded",
|
|
15
|
+
];
|
|
16
|
+
const USER_ABORT_TEXT_SIGNALS = [
|
|
17
|
+
"user cancelled",
|
|
18
|
+
"user canceled",
|
|
19
|
+
"cancelled by user",
|
|
20
|
+
"canceled by user",
|
|
21
|
+
"aborted by user",
|
|
22
|
+
"ctrl+c",
|
|
23
|
+
"ctrl-c",
|
|
24
|
+
];
|
|
25
|
+
export function isRetryableProviderError(error) {
|
|
26
|
+
switch (error.name) {
|
|
27
|
+
case "APIError": {
|
|
28
|
+
if (error.data.isRetryable)
|
|
29
|
+
return true;
|
|
30
|
+
if (error.data.statusCode === 408)
|
|
31
|
+
return true;
|
|
32
|
+
if (error.data.statusCode !== undefined &&
|
|
33
|
+
RETRYABLE_STATUS_CODES.includes(error.data.statusCode)) {
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
if (hasRetryableTextSignal(error.data.message, error.data.responseBody)) {
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
case "ProviderAuthError":
|
|
42
|
+
return false;
|
|
43
|
+
case "MessageAbortedError":
|
|
44
|
+
return !isLikelyUserAbort(error.data.message);
|
|
45
|
+
case "MessageOutputLengthError":
|
|
46
|
+
return false;
|
|
47
|
+
case "UnknownError":
|
|
48
|
+
return true;
|
|
49
|
+
default: {
|
|
50
|
+
const _exhaustive = error;
|
|
51
|
+
return _exhaustive;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function hasRetryableTextSignal(message, responseBody) {
|
|
56
|
+
const haystack = `${message}\n${responseBody ?? ""}`.toLowerCase();
|
|
57
|
+
return RETRYABLE_TEXT_SIGNALS.some((signal) => haystack.includes(signal));
|
|
58
|
+
}
|
|
59
|
+
function isLikelyUserAbort(message) {
|
|
60
|
+
const normalized = message.toLowerCase();
|
|
61
|
+
return USER_ABORT_TEXT_SIGNALS.some((signal) => normalized.includes(signal));
|
|
62
|
+
}
|
|
63
|
+
export function extractFailoverTrigger(event) {
|
|
64
|
+
const error = event.properties.error;
|
|
65
|
+
if (!error)
|
|
66
|
+
return null;
|
|
67
|
+
if (!isRetryableProviderError(error))
|
|
68
|
+
return null;
|
|
69
|
+
const trigger = {
|
|
70
|
+
reason: buildReason(error),
|
|
71
|
+
provider: "",
|
|
72
|
+
model: "",
|
|
73
|
+
};
|
|
74
|
+
if (error.name === "APIError" && error.data.statusCode !== undefined) {
|
|
75
|
+
trigger.statusCode = error.data.statusCode;
|
|
76
|
+
}
|
|
77
|
+
return trigger;
|
|
78
|
+
}
|
|
79
|
+
function buildReason(error) {
|
|
80
|
+
switch (error.name) {
|
|
81
|
+
case "APIError":
|
|
82
|
+
return error.data.statusCode
|
|
83
|
+
? `API error ${error.data.statusCode}: ${error.data.message}`
|
|
84
|
+
: `API error: ${error.data.message}`;
|
|
85
|
+
case "UnknownError":
|
|
86
|
+
return `Unknown error: ${error.data.message}`;
|
|
87
|
+
case "ProviderAuthError":
|
|
88
|
+
case "MessageAbortedError":
|
|
89
|
+
case "MessageOutputLengthError":
|
|
90
|
+
return error.name;
|
|
91
|
+
default: {
|
|
92
|
+
const _exhaustive = error;
|
|
93
|
+
return _exhaustive;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=error-detector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-detector.js","sourceRoot":"","sources":["../../lib/error-detector.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { EventSessionError, OpencodeClient } from "@opencode-ai/sdk";
|
|
2
|
+
/**
|
|
3
|
+
* Detects provider errors, maps models to github-copilot equivalents,
|
|
4
|
+
* and re-prompts sessions via the SDK client. Must NEVER throw.
|
|
5
|
+
*/
|
|
6
|
+
export declare class CopilotFailoverEngine {
|
|
7
|
+
private readonly client;
|
|
8
|
+
private readonly dedup;
|
|
9
|
+
private cachedCopilotModels;
|
|
10
|
+
constructor(client: OpencodeClient);
|
|
11
|
+
/**
|
|
12
|
+
* Discover which models are available in the github-copilot provider.
|
|
13
|
+
* Result is cached for the lifetime of the engine instance.
|
|
14
|
+
*/
|
|
15
|
+
private discoverCopilotModels;
|
|
16
|
+
handleSessionError(event: EventSessionError): Promise<void>;
|
|
17
|
+
private processError;
|
|
18
|
+
private emitFailoverToast;
|
|
19
|
+
private emitToast;
|
|
20
|
+
/**
|
|
21
|
+
* Convert output Part[] (session.messages response) to input PartInput[]
|
|
22
|
+
* (session.prompt request). Only TextPart and FilePart map to user-input
|
|
23
|
+
* equivalents; assistant-side parts (tool, reasoning, step, etc.) are skipped.
|
|
24
|
+
*/
|
|
25
|
+
private convertPartsToInput;
|
|
26
|
+
private isDuplicate;
|
|
27
|
+
private recordDedup;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=failover-engine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"failover-engine.d.ts","sourceRoot":"","sources":["../../lib/failover-engine.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,iBAAiB,EAOjB,cAAc,EACd,MAAM,kBAAkB,CAAC;AAe1B;;;GAGG;AACH,qBAAa,qBAAqB;IAIrB,OAAO,CAAC,QAAQ,CAAC,MAAM;IAHnC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAkC;IACxD,OAAO,CAAC,mBAAmB,CAA4B;gBAE1B,MAAM,EAAE,cAAc;IAEnD;;;OAGG;YACW,qBAAqB;IAuB7B,kBAAkB,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;YAYnD,YAAY;YAiFZ,iBAAiB;YAajB,SAAS;IAgCvB;;;;OAIG;IACH,OAAO,CAAC,mBAAmB;IA4B3B,OAAO,CAAC,WAAW;IAKnB,OAAO,CAAC,WAAW;CAQnB"}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { COPILOT_PROVIDER_ID } from "./constants.js";
|
|
2
|
+
import { extractFailoverTrigger } from "./error-detector.js";
|
|
3
|
+
import { resolveWithFallback } from "./model-mapper.js";
|
|
4
|
+
/**
|
|
5
|
+
* Detects provider errors, maps models to github-copilot equivalents,
|
|
6
|
+
* and re-prompts sessions via the SDK client. Must NEVER throw.
|
|
7
|
+
*/
|
|
8
|
+
export class CopilotFailoverEngine {
|
|
9
|
+
client;
|
|
10
|
+
dedup = new Map();
|
|
11
|
+
cachedCopilotModels = null;
|
|
12
|
+
constructor(client) {
|
|
13
|
+
this.client = client;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Discover which models are available in the github-copilot provider.
|
|
17
|
+
* Result is cached for the lifetime of the engine instance.
|
|
18
|
+
*/
|
|
19
|
+
async discoverCopilotModels() {
|
|
20
|
+
if (this.cachedCopilotModels)
|
|
21
|
+
return this.cachedCopilotModels;
|
|
22
|
+
try {
|
|
23
|
+
const result = await this.client.config.providers();
|
|
24
|
+
const providers = result.data?.providers;
|
|
25
|
+
if (!providers)
|
|
26
|
+
return null;
|
|
27
|
+
const copilotProvider = providers.find((p) => p.id === COPILOT_PROVIDER_ID);
|
|
28
|
+
if (!copilotProvider?.models)
|
|
29
|
+
return null;
|
|
30
|
+
this.cachedCopilotModels = new Set(Object.keys(copilotProvider.models));
|
|
31
|
+
return this.cachedCopilotModels;
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
// If the providers API fails, proceed without availability filtering
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
async handleSessionError(event) {
|
|
39
|
+
try {
|
|
40
|
+
await this.processError(event);
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
await this.emitToast({
|
|
44
|
+
title: "Copilot Failover Error",
|
|
45
|
+
message: err instanceof Error ? err.message : String(err),
|
|
46
|
+
variant: "error",
|
|
47
|
+
}).catch(() => { });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
async processError(event) {
|
|
51
|
+
const error = event.properties.error;
|
|
52
|
+
if (!error)
|
|
53
|
+
return;
|
|
54
|
+
const sessionID = event.properties.sessionID;
|
|
55
|
+
if (!sessionID)
|
|
56
|
+
return;
|
|
57
|
+
const trigger = extractFailoverTrigger(event);
|
|
58
|
+
if (!trigger)
|
|
59
|
+
return;
|
|
60
|
+
const messagesResult = await this.client.session.messages({
|
|
61
|
+
path: { id: sessionID },
|
|
62
|
+
});
|
|
63
|
+
const messages = messagesResult.data;
|
|
64
|
+
if (!messages || messages.length === 0)
|
|
65
|
+
return;
|
|
66
|
+
const failedEntry = [...messages]
|
|
67
|
+
.reverse()
|
|
68
|
+
.find((m) => m.info.role === "assistant" && m.info.error);
|
|
69
|
+
if (!failedEntry)
|
|
70
|
+
return;
|
|
71
|
+
const failedAssistant = failedEntry.info;
|
|
72
|
+
const originalProvider = failedAssistant.providerID;
|
|
73
|
+
const originalModel = failedAssistant.modelID;
|
|
74
|
+
if (originalProvider === COPILOT_PROVIDER_ID) {
|
|
75
|
+
await this.emitToast({
|
|
76
|
+
title: "Copilot Failover Failed",
|
|
77
|
+
message: `${COPILOT_PROVIDER_ID}/${originalModel} also errored — no further fallback available`,
|
|
78
|
+
variant: "error",
|
|
79
|
+
});
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const parentID = failedAssistant.parentID;
|
|
83
|
+
if (this.isDuplicate(sessionID, parentID))
|
|
84
|
+
return;
|
|
85
|
+
const availableModels = await this.discoverCopilotModels();
|
|
86
|
+
const copilotModelID = resolveWithFallback(originalModel, availableModels);
|
|
87
|
+
if (!copilotModelID) {
|
|
88
|
+
await this.emitToast({
|
|
89
|
+
title: "Copilot Failover Unavailable",
|
|
90
|
+
message: `No copilot mapping for ${originalProvider}/${originalModel}`,
|
|
91
|
+
variant: "error",
|
|
92
|
+
});
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const userEntry = messages.find((m) => m.info.id === parentID);
|
|
96
|
+
if (!userEntry)
|
|
97
|
+
return;
|
|
98
|
+
const inputParts = this.convertPartsToInput(userEntry.parts);
|
|
99
|
+
if (inputParts.length === 0)
|
|
100
|
+
return;
|
|
101
|
+
const userInfo = userEntry.info;
|
|
102
|
+
await this.client.session.promptAsync({
|
|
103
|
+
path: { id: sessionID },
|
|
104
|
+
body: {
|
|
105
|
+
parts: inputParts,
|
|
106
|
+
model: {
|
|
107
|
+
providerID: COPILOT_PROVIDER_ID,
|
|
108
|
+
modelID: copilotModelID,
|
|
109
|
+
},
|
|
110
|
+
agent: userInfo.agent,
|
|
111
|
+
system: userInfo.system,
|
|
112
|
+
tools: userInfo.tools,
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
await this.emitFailoverToast(originalProvider, originalModel, copilotModelID, trigger.reason);
|
|
116
|
+
this.recordDedup(sessionID, parentID);
|
|
117
|
+
}
|
|
118
|
+
async emitFailoverToast(fromProvider, fromModel, toModel, reason) {
|
|
119
|
+
await this.emitToast({
|
|
120
|
+
title: "Provider switched to GitHub Copilot",
|
|
121
|
+
message: `${fromProvider}/${fromModel} → ${COPILOT_PROVIDER_ID}/${toModel} (${reason})`,
|
|
122
|
+
variant: "warning",
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
async emitToast(payload) {
|
|
126
|
+
const toastEvent = {
|
|
127
|
+
type: "tui.toast.show",
|
|
128
|
+
properties: {
|
|
129
|
+
title: payload.title,
|
|
130
|
+
message: payload.message,
|
|
131
|
+
variant: payload.variant,
|
|
132
|
+
duration: payload.duration,
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
try {
|
|
136
|
+
await this.client.tui.publish({ body: toastEvent });
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
// publish unavailable, fall through to showToast
|
|
141
|
+
}
|
|
142
|
+
try {
|
|
143
|
+
await this.client.tui.showToast({
|
|
144
|
+
body: {
|
|
145
|
+
title: payload.title,
|
|
146
|
+
message: payload.message,
|
|
147
|
+
variant: payload.variant,
|
|
148
|
+
duration: payload.duration,
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
// both toast methods failed — nothing we can do without polluting TUI
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Convert output Part[] (session.messages response) to input PartInput[]
|
|
158
|
+
* (session.prompt request). Only TextPart and FilePart map to user-input
|
|
159
|
+
* equivalents; assistant-side parts (tool, reasoning, step, etc.) are skipped.
|
|
160
|
+
*/
|
|
161
|
+
convertPartsToInput(parts) {
|
|
162
|
+
const result = [];
|
|
163
|
+
for (const part of parts) {
|
|
164
|
+
switch (part.type) {
|
|
165
|
+
case "text": {
|
|
166
|
+
result.push({
|
|
167
|
+
type: "text",
|
|
168
|
+
text: part.text,
|
|
169
|
+
});
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
case "file": {
|
|
173
|
+
result.push({
|
|
174
|
+
type: "file",
|
|
175
|
+
mime: part.mime,
|
|
176
|
+
url: part.url,
|
|
177
|
+
...(part.filename ? { filename: part.filename } : {}),
|
|
178
|
+
...(part.source ? { source: part.source } : {}),
|
|
179
|
+
});
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return result;
|
|
185
|
+
}
|
|
186
|
+
isDuplicate(sessionID, parentID) {
|
|
187
|
+
const sessionSet = this.dedup.get(sessionID);
|
|
188
|
+
return sessionSet !== undefined && sessionSet.has(parentID);
|
|
189
|
+
}
|
|
190
|
+
recordDedup(sessionID, parentID) {
|
|
191
|
+
let sessionSet = this.dedup.get(sessionID);
|
|
192
|
+
if (!sessionSet) {
|
|
193
|
+
sessionSet = new Set();
|
|
194
|
+
this.dedup.set(sessionID, sessionSet);
|
|
195
|
+
}
|
|
196
|
+
sessionSet.add(parentID);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
//# sourceMappingURL=failover-engine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"failover-engine.js","sourceRoot":"","sources":["../../lib/failover-engine.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model Mapper — maps source provider model IDs to github-copilot model IDs
|
|
3
|
+
* and provides a ranked fallback chain when the exact model isn't available.
|
|
4
|
+
*
|
|
5
|
+
* GitHub Copilot uses DOTS in version numbers where Anthropic uses DASHES.
|
|
6
|
+
* e.g. anthropic: "claude-opus-4-6" -> copilot: "claude-opus-4.6"
|
|
7
|
+
*
|
|
8
|
+
* Verified against `opencode models github-copilot` output (2026-02-10).
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Maps source provider model IDs to their github-copilot equivalents.
|
|
12
|
+
*
|
|
13
|
+
* Key: model ID as used by the source provider (anthropic, openai, google, etc.)
|
|
14
|
+
* Value: model ID as accepted by the github-copilot API
|
|
15
|
+
*/
|
|
16
|
+
export declare const COPILOT_MODEL_MAP: Record<string, string>;
|
|
17
|
+
/**
|
|
18
|
+
* Fallback chain — ordered list of model IDs to try when the original
|
|
19
|
+
* model isn't available in copilot. Walked top-to-bottom, first match wins.
|
|
20
|
+
*
|
|
21
|
+
* Order per user spec:
|
|
22
|
+
* 1. claude-opus (latest)
|
|
23
|
+
* 2. openai codex (latest)
|
|
24
|
+
* 3. claude-sonnet (latest)
|
|
25
|
+
* 4. kimi-2.5
|
|
26
|
+
* 5. any remaining
|
|
27
|
+
*/
|
|
28
|
+
export declare const FALLBACK_CHAIN: readonly string[];
|
|
29
|
+
export declare function parseModelString(fullModelID: string): {
|
|
30
|
+
providerID: string;
|
|
31
|
+
modelID: string;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Map a source-provider model ID to its github-copilot equivalent.
|
|
35
|
+
* Returns `null` if no direct mapping exists (use `resolveWithFallback` for chain).
|
|
36
|
+
*/
|
|
37
|
+
export declare function mapModelToCopilot(modelID: string): string | null;
|
|
38
|
+
/**
|
|
39
|
+
* Resolve a model to a copilot equivalent, falling back through the
|
|
40
|
+
* ranked chain if no direct mapping exists.
|
|
41
|
+
*
|
|
42
|
+
* @param modelID - The original model ID (bare or fully-qualified)
|
|
43
|
+
* @param availableModels - Set of model IDs currently available in copilot.
|
|
44
|
+
* If provided, both direct mapping and fallback are validated against it.
|
|
45
|
+
* If null/undefined, the hardcoded map and chain are trusted as-is.
|
|
46
|
+
*/
|
|
47
|
+
export declare function resolveWithFallback(modelID: string, availableModels?: ReadonlySet<string> | null): string | null;
|
|
48
|
+
export declare function isModelCopilotAvailable(modelID: string): boolean;
|
|
49
|
+
//# sourceMappingURL=model-mapper.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model-mapper.d.ts","sourceRoot":"","sources":["../../lib/model-mapper.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAoCpD,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,cAAc,EAAE,SAAS,MAAM,EAa3C,CAAC;AAEF,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG;IACtD,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;CAChB,CASA;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAehE;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAClC,OAAO,EAAE,MAAM,EACf,eAAe,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,IAAI,GAC1C,MAAM,GAAG,IAAI,CAuBf;AAUD,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAEhE"}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model Mapper — maps source provider model IDs to github-copilot model IDs
|
|
3
|
+
* and provides a ranked fallback chain when the exact model isn't available.
|
|
4
|
+
*
|
|
5
|
+
* GitHub Copilot uses DOTS in version numbers where Anthropic uses DASHES.
|
|
6
|
+
* e.g. anthropic: "claude-opus-4-6" -> copilot: "claude-opus-4.6"
|
|
7
|
+
*
|
|
8
|
+
* Verified against `opencode models github-copilot` output (2026-02-10).
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Maps source provider model IDs to their github-copilot equivalents.
|
|
12
|
+
*
|
|
13
|
+
* Key: model ID as used by the source provider (anthropic, openai, google, etc.)
|
|
14
|
+
* Value: model ID as accepted by the github-copilot API
|
|
15
|
+
*/
|
|
16
|
+
export const COPILOT_MODEL_MAP = {
|
|
17
|
+
// Anthropic
|
|
18
|
+
"claude-opus-4-6": "claude-opus-4.6",
|
|
19
|
+
"claude-opus-4.6": "claude-opus-4.6",
|
|
20
|
+
"claude-opus-4-5": "claude-opus-4.5",
|
|
21
|
+
"claude-opus-4.5": "claude-opus-4.5",
|
|
22
|
+
"claude-opus-4-1": "claude-opus-41",
|
|
23
|
+
"claude-opus-4.1": "claude-opus-41",
|
|
24
|
+
"claude-opus-41": "claude-opus-41",
|
|
25
|
+
"claude-sonnet-4-5": "claude-sonnet-4.5",
|
|
26
|
+
"claude-sonnet-4.5": "claude-sonnet-4.5",
|
|
27
|
+
"claude-sonnet-4": "claude-sonnet-4",
|
|
28
|
+
"claude-haiku-4-5": "claude-haiku-4.5",
|
|
29
|
+
"claude-haiku-4.5": "claude-haiku-4.5",
|
|
30
|
+
// OpenAI
|
|
31
|
+
"gpt-5.2-codex": "gpt-5.2-codex",
|
|
32
|
+
"gpt-5.2": "gpt-5.2",
|
|
33
|
+
"gpt-5.1-codex-max": "gpt-5.1-codex-max",
|
|
34
|
+
"gpt-5.1-codex": "gpt-5.1-codex",
|
|
35
|
+
"gpt-5.1-codex-mini": "gpt-5.1-codex-mini",
|
|
36
|
+
"gpt-5.1": "gpt-5.1",
|
|
37
|
+
"gpt-5": "gpt-5",
|
|
38
|
+
"gpt-5-mini": "gpt-5-mini",
|
|
39
|
+
"gpt-4.1": "gpt-4.1",
|
|
40
|
+
"gpt-4o": "gpt-4o",
|
|
41
|
+
// Google
|
|
42
|
+
"gemini-2.5-pro": "gemini-2.5-pro",
|
|
43
|
+
"gemini-3-flash": "gemini-3-flash-preview",
|
|
44
|
+
"gemini-3-flash-preview": "gemini-3-flash-preview",
|
|
45
|
+
"gemini-3-pro": "gemini-3-pro-preview",
|
|
46
|
+
"gemini-3-pro-preview": "gemini-3-pro-preview",
|
|
47
|
+
// xAI
|
|
48
|
+
"grok-code-fast-1": "grok-code-fast-1",
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* Fallback chain — ordered list of model IDs to try when the original
|
|
52
|
+
* model isn't available in copilot. Walked top-to-bottom, first match wins.
|
|
53
|
+
*
|
|
54
|
+
* Order per user spec:
|
|
55
|
+
* 1. claude-opus (latest)
|
|
56
|
+
* 2. openai codex (latest)
|
|
57
|
+
* 3. claude-sonnet (latest)
|
|
58
|
+
* 4. kimi-2.5
|
|
59
|
+
* 5. any remaining
|
|
60
|
+
*/
|
|
61
|
+
export const FALLBACK_CHAIN = [
|
|
62
|
+
"claude-opus-4.6",
|
|
63
|
+
"claude-opus-4.5",
|
|
64
|
+
"gpt-5.3-codex",
|
|
65
|
+
"gpt-5.2-codex",
|
|
66
|
+
"claude-sonnet-4.5",
|
|
67
|
+
"kimi-2.5",
|
|
68
|
+
"gpt-5.2",
|
|
69
|
+
"gpt-5-mini",
|
|
70
|
+
"gemini-3-pro-preview",
|
|
71
|
+
"gemini-3-flash-preview",
|
|
72
|
+
"grok-code-fast-1",
|
|
73
|
+
"claude-haiku-4.5",
|
|
74
|
+
];
|
|
75
|
+
export function parseModelString(fullModelID) {
|
|
76
|
+
const slashIndex = fullModelID.indexOf("/");
|
|
77
|
+
if (slashIndex === -1) {
|
|
78
|
+
return { providerID: "", modelID: fullModelID };
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
providerID: fullModelID.slice(0, slashIndex),
|
|
82
|
+
modelID: fullModelID.slice(slashIndex + 1),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Map a source-provider model ID to its github-copilot equivalent.
|
|
87
|
+
* Returns `null` if no direct mapping exists (use `resolveWithFallback` for chain).
|
|
88
|
+
*/
|
|
89
|
+
export function mapModelToCopilot(modelID) {
|
|
90
|
+
const { modelID: bare } = parseModelString(modelID);
|
|
91
|
+
if (COPILOT_MODEL_MAP[bare] !== undefined) {
|
|
92
|
+
return COPILOT_MODEL_MAP[bare];
|
|
93
|
+
}
|
|
94
|
+
const lower = bare.toLowerCase();
|
|
95
|
+
for (const key of Object.keys(COPILOT_MODEL_MAP)) {
|
|
96
|
+
if (key.toLowerCase() === lower) {
|
|
97
|
+
return COPILOT_MODEL_MAP[key];
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Resolve a model to a copilot equivalent, falling back through the
|
|
104
|
+
* ranked chain if no direct mapping exists.
|
|
105
|
+
*
|
|
106
|
+
* @param modelID - The original model ID (bare or fully-qualified)
|
|
107
|
+
* @param availableModels - Set of model IDs currently available in copilot.
|
|
108
|
+
* If provided, both direct mapping and fallback are validated against it.
|
|
109
|
+
* If null/undefined, the hardcoded map and chain are trusted as-is.
|
|
110
|
+
*/
|
|
111
|
+
export function resolveWithFallback(modelID, availableModels) {
|
|
112
|
+
const { modelID: bare } = parseModelString(modelID);
|
|
113
|
+
// 1. Try the original model ID as-is against copilot's available models
|
|
114
|
+
if (isAvailable(bare, availableModels)) {
|
|
115
|
+
return bare;
|
|
116
|
+
}
|
|
117
|
+
// 2. Try the mapped name (e.g. claude-opus-4-6 -> claude-opus-4.6)
|
|
118
|
+
const mappedMatch = mapModelToCopilot(modelID);
|
|
119
|
+
if (mappedMatch && mappedMatch !== bare && isAvailable(mappedMatch, availableModels)) {
|
|
120
|
+
return mappedMatch;
|
|
121
|
+
}
|
|
122
|
+
// 3. Walk the fallback chain
|
|
123
|
+
for (const candidate of FALLBACK_CHAIN) {
|
|
124
|
+
if (candidate === bare || candidate === mappedMatch)
|
|
125
|
+
continue;
|
|
126
|
+
if (isAvailable(candidate, availableModels)) {
|
|
127
|
+
return candidate;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
function isAvailable(modelID, availableModels) {
|
|
133
|
+
if (!availableModels)
|
|
134
|
+
return true;
|
|
135
|
+
return availableModels.has(modelID);
|
|
136
|
+
}
|
|
137
|
+
export function isModelCopilotAvailable(modelID) {
|
|
138
|
+
return mapModelToCopilot(modelID) !== null;
|
|
139
|
+
}
|
|
140
|
+
//# sourceMappingURL=model-mapper.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model-mapper.js","sourceRoot":"","sources":["../../lib/model-mapper.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "opencode-copilot-failover",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Auto-failover from anthropic/openai providers to github-copilot when primary providers return retryable errors",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"author": "pl4fun",
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/pl4fun/opencode-copilot-failover.git"
|
|
13
|
+
},
|
|
14
|
+
"homepage": "https://github.com/pl4fun/opencode-copilot-failover#readme",
|
|
15
|
+
"bugs": {
|
|
16
|
+
"url": "https://github.com/pl4fun/opencode-copilot-failover/issues"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"opencode",
|
|
20
|
+
"plugin",
|
|
21
|
+
"copilot",
|
|
22
|
+
"failover",
|
|
23
|
+
"github-copilot",
|
|
24
|
+
"provider",
|
|
25
|
+
"auto-switch"
|
|
26
|
+
],
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">=20.0.0"
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
"dist/",
|
|
32
|
+
"README.md",
|
|
33
|
+
"LICENSE"
|
|
34
|
+
],
|
|
35
|
+
"scripts": {
|
|
36
|
+
"build": "tsc",
|
|
37
|
+
"prepublishOnly": "npm run build && npm run test",
|
|
38
|
+
"test": "vitest run",
|
|
39
|
+
"typecheck": "tsc --noEmit"
|
|
40
|
+
},
|
|
41
|
+
"peerDependencies": {
|
|
42
|
+
"@opencode-ai/plugin": "^1.1.19"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@opencode-ai/plugin": "^1.1.19",
|
|
46
|
+
"@opencode-ai/sdk": "^1.1.19",
|
|
47
|
+
"@types/node": "^22",
|
|
48
|
+
"typescript": "^5.8",
|
|
49
|
+
"vitest": "latest"
|
|
50
|
+
}
|
|
51
|
+
}
|