@jacobbd/relay-ai 0.12.4 → 0.12.6
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/README.md +14 -1
- package/dist/{chunk-LPOAO33X.js → chunk-5MEDBKPX.js} +163 -18
- package/dist/chunk-5MEDBKPX.js.map +1 -0
- package/dist/{chunk-2OEMU2WC.js → chunk-6ULON7FU.js} +2 -2
- package/dist/{chunk-5INR7PKO.js → chunk-VPDX24ZR.js} +463 -11
- package/dist/chunk-VPDX24ZR.js.map +1 -0
- package/dist/cli.js +130 -25
- package/dist/cli.js.map +1 -1
- package/dist/core/index.js +623 -34
- package/dist/core/index.js.map +1 -1
- package/dist/{manual-models-MZUHC3UN.js → manual-models-EIKWUZEG.js} +3 -3
- package/dist/{ui-command-XCYCQQRA.js → ui-command-U3L4LI2K.js} +4 -4
- package/package.json +1 -1
- package/dist/chunk-5INR7PKO.js.map +0 -1
- package/dist/chunk-LPOAO33X.js.map +0 -1
- /package/dist/{chunk-2OEMU2WC.js.map → chunk-6ULON7FU.js.map} +0 -0
- /package/dist/{manual-models-MZUHC3UN.js.map → manual-models-EIKWUZEG.js.map} +0 -0
- /package/dist/{ui-command-XCYCQQRA.js.map → ui-command-U3L4LI2K.js.map} +0 -0
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
supportsManualModels,
|
|
11
11
|
translateRequest,
|
|
12
12
|
validateCustomEndpointUrl
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-VPDX24ZR.js";
|
|
14
14
|
|
|
15
15
|
// src/registry/validate-manual-model.ts
|
|
16
16
|
import { randomUUID } from "crypto";
|
|
@@ -149,4 +149,4 @@ export {
|
|
|
149
149
|
addManualModel,
|
|
150
150
|
removeManualModel
|
|
151
151
|
};
|
|
152
|
-
//# sourceMappingURL=chunk-
|
|
152
|
+
//# sourceMappingURL=chunk-6ULON7FU.js.map
|
|
@@ -7,7 +7,7 @@ import { join } from "path";
|
|
|
7
7
|
// package.json
|
|
8
8
|
var package_default = {
|
|
9
9
|
name: "@jacobbd/relay-ai",
|
|
10
|
-
version: "0.12.
|
|
10
|
+
version: "0.12.6",
|
|
11
11
|
publishConfig: {
|
|
12
12
|
access: "public"
|
|
13
13
|
},
|
|
@@ -694,6 +694,13 @@ function decodeAuthEntry(value) {
|
|
|
694
694
|
if (typeof value === "string" && value.trim()) return value.trim();
|
|
695
695
|
if (!value || typeof value !== "object") return null;
|
|
696
696
|
const record = value;
|
|
697
|
+
if (record["type"] === "api" && typeof record["key"] === "string" && record["key"].trim()) {
|
|
698
|
+
return {
|
|
699
|
+
type: "api",
|
|
700
|
+
key: record["key"],
|
|
701
|
+
metadata: record["metadata"] && typeof record["metadata"] === "object" && !Array.isArray(record["metadata"]) ? record["metadata"] : void 0
|
|
702
|
+
};
|
|
703
|
+
}
|
|
697
704
|
if (record["type"] === "oauth" && typeof record["access"] === "string" && typeof record["refresh"] === "string" && typeof record["expires"] === "number") {
|
|
698
705
|
return {
|
|
699
706
|
type: "oauth",
|
|
@@ -747,6 +754,9 @@ function readOpencodeAuthFile(env = process.env) {
|
|
|
747
754
|
function isOpencodeOAuth(entry) {
|
|
748
755
|
return !!entry && typeof entry === "object" && entry.type === "oauth";
|
|
749
756
|
}
|
|
757
|
+
function isOpencodeApi(entry) {
|
|
758
|
+
return !!entry && typeof entry === "object" && entry.type === "api";
|
|
759
|
+
}
|
|
750
760
|
function oauthCredentialToKeychainJson(cred) {
|
|
751
761
|
return JSON.stringify(cred);
|
|
752
762
|
}
|
|
@@ -2934,6 +2944,193 @@ function createResponsesWebSocketFetch(wsUrl, log) {
|
|
|
2934
2944
|
};
|
|
2935
2945
|
}
|
|
2936
2946
|
|
|
2947
|
+
// src/gateway-protocol.ts
|
|
2948
|
+
import { createHash as createHash2 } from "crypto";
|
|
2949
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync6, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
|
|
2950
|
+
import { dirname as dirname2, join as join4 } from "path";
|
|
2951
|
+
var DUAL_PROVIDER_IDS = /* @__PURE__ */ new Set([
|
|
2952
|
+
"zen",
|
|
2953
|
+
"go",
|
|
2954
|
+
"opencode",
|
|
2955
|
+
"opencode-zen",
|
|
2956
|
+
"opencode-go",
|
|
2957
|
+
"openrouter",
|
|
2958
|
+
"commandcode",
|
|
2959
|
+
"command-code"
|
|
2960
|
+
]);
|
|
2961
|
+
function normalizedRoot(baseURL) {
|
|
2962
|
+
return baseURL.replace(/\/+$/, "").replace(/\/v1$/i, "");
|
|
2963
|
+
}
|
|
2964
|
+
function isKnownDualHost(providerId, baseURL) {
|
|
2965
|
+
if (!baseURL) return false;
|
|
2966
|
+
let parsed;
|
|
2967
|
+
try {
|
|
2968
|
+
parsed = new URL(baseURL);
|
|
2969
|
+
} catch {
|
|
2970
|
+
return false;
|
|
2971
|
+
}
|
|
2972
|
+
const host = parsed.hostname.toLowerCase();
|
|
2973
|
+
const path = parsed.pathname.toLowerCase();
|
|
2974
|
+
const id = providerId?.trim().toLowerCase();
|
|
2975
|
+
if (host === "opencode.ai" && path.includes("/zen")) return true;
|
|
2976
|
+
if (host === "openrouter.ai" && path.includes("/api")) return true;
|
|
2977
|
+
if (host === "commandcode.ai" || host.endsWith(".commandcode.ai")) {
|
|
2978
|
+
if (path.includes("/provider")) return true;
|
|
2979
|
+
}
|
|
2980
|
+
return id !== void 0 && DUAL_PROVIDER_IDS.has(id) && (host === "opencode.ai" || host === "openrouter.ai" || host === "commandcode.ai");
|
|
2981
|
+
}
|
|
2982
|
+
function isDualProtocolGateway(providerId, baseURL) {
|
|
2983
|
+
return isKnownDualHost(providerId, baseURL);
|
|
2984
|
+
}
|
|
2985
|
+
function resolveProtocolAlternative(input) {
|
|
2986
|
+
if (!isDualProtocolGateway(input.providerId, input.baseURL)) return null;
|
|
2987
|
+
if (!input.baseURL) return null;
|
|
2988
|
+
const root = normalizedRoot(input.baseURL);
|
|
2989
|
+
if (input.modelFormat === "openai") {
|
|
2990
|
+
return {
|
|
2991
|
+
modelFormat: "anthropic",
|
|
2992
|
+
npm: "@ai-sdk/anthropic",
|
|
2993
|
+
baseURL: root,
|
|
2994
|
+
upstreamUrl: `${root}/v1/messages`
|
|
2995
|
+
};
|
|
2996
|
+
}
|
|
2997
|
+
const openaiRoot = `${root}/v1`;
|
|
2998
|
+
const openrouter = input.providerId?.trim().toLowerCase() === "openrouter" || input.npm === "@openrouter/ai-sdk-provider" || root.includes("openrouter.ai");
|
|
2999
|
+
return {
|
|
3000
|
+
modelFormat: "openai",
|
|
3001
|
+
npm: openrouter ? "@openrouter/ai-sdk-provider" : "@ai-sdk/openai-compatible",
|
|
3002
|
+
baseURL: openaiRoot,
|
|
3003
|
+
upstreamUrl: `${openaiRoot}/chat/completions`
|
|
3004
|
+
};
|
|
3005
|
+
}
|
|
3006
|
+
function errorText(value) {
|
|
3007
|
+
if (typeof value === "string") return value;
|
|
3008
|
+
if (!value || typeof value !== "object") return "";
|
|
3009
|
+
const record = value;
|
|
3010
|
+
const pieces = [record.message, record.responseBody, record.code].filter((item) => typeof item === "string");
|
|
3011
|
+
return pieces.join(" ");
|
|
3012
|
+
}
|
|
3013
|
+
function findErrorField(error, field, depth = 0) {
|
|
3014
|
+
if (!error || typeof error !== "object" || depth > 3) return void 0;
|
|
3015
|
+
const record = error;
|
|
3016
|
+
if (record[field] !== void 0) return record[field];
|
|
3017
|
+
return findErrorField(record.cause, field, depth + 1) ?? findErrorField(record.lastError, field, depth + 1);
|
|
3018
|
+
}
|
|
3019
|
+
function classifyProtocolFailure(error) {
|
|
3020
|
+
const statusValue = findErrorField(error, "statusCode") ?? findErrorField(error, "status");
|
|
3021
|
+
const status = typeof statusValue === "number" ? statusValue : void 0;
|
|
3022
|
+
const text = errorText(error) + " " + errorText(findErrorField(error, "cause")) + " " + errorText(findErrorField(error, "responseBody"));
|
|
3023
|
+
const lower = text.toLowerCase();
|
|
3024
|
+
if (lower.includes("abort") || lower.includes("cancel")) {
|
|
3025
|
+
return { retryable: false, status, reason: "request cancelled" };
|
|
3026
|
+
}
|
|
3027
|
+
if (lower.includes("not supported for format")) {
|
|
3028
|
+
return { retryable: true, status, reason: "provider rejected the selected API format" };
|
|
3029
|
+
}
|
|
3030
|
+
if (status === 401 || status === 403 || status === 429 || /invalid (api )?key|authentication|quota|billing|not in plan|model_not_in_plan/.test(lower)) {
|
|
3031
|
+
return { retryable: false, status, reason: "authentication or quota failure" };
|
|
3032
|
+
}
|
|
3033
|
+
if (/context (length|window)|too many tokens|maximum context|invalid tool|tool schema|moderation/.test(lower)) {
|
|
3034
|
+
return { retryable: false, status, reason: "request content was rejected" };
|
|
3035
|
+
}
|
|
3036
|
+
const explicitProtocol = status === 405 || status === 415 || status !== void 0 && [400, 404, 422].includes(status) && /endpoint|method|protocol|messages|chat\/completions|chat completions|anthropic/.test(lower);
|
|
3037
|
+
if (explicitProtocol) {
|
|
3038
|
+
return { retryable: true, status, reason: "provider rejected the selected API format" };
|
|
3039
|
+
}
|
|
3040
|
+
if (status === 500) {
|
|
3041
|
+
return { retryable: true, status, reason: "gateway returned an early internal error" };
|
|
3042
|
+
}
|
|
3043
|
+
return { retryable: false, status, reason: "failure is not attributable to API format" };
|
|
3044
|
+
}
|
|
3045
|
+
var remembered = /* @__PURE__ */ new Map();
|
|
3046
|
+
var PREFERENCE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
3047
|
+
var FAILURE_COOLDOWN_MS = 5 * 60 * 1e3;
|
|
3048
|
+
var loadedPersistentPath = null;
|
|
3049
|
+
function persistentPath() {
|
|
3050
|
+
return join4(getAppHome(), "protocol-cache.json");
|
|
3051
|
+
}
|
|
3052
|
+
function loadPersistent(now = Date.now()) {
|
|
3053
|
+
const path = persistentPath();
|
|
3054
|
+
if (loadedPersistentPath === path) return;
|
|
3055
|
+
remembered.clear();
|
|
3056
|
+
loadedPersistentPath = path;
|
|
3057
|
+
try {
|
|
3058
|
+
if (!existsSync4(path)) return;
|
|
3059
|
+
const raw = JSON.parse(readFileSync6(path, "utf8"));
|
|
3060
|
+
for (const [key, entry] of Object.entries(raw)) {
|
|
3061
|
+
if (!entry || entry.protocol !== void 0 && entry.protocol !== "openai" && entry.protocol !== "anthropic") continue;
|
|
3062
|
+
if (typeof entry.expiresAt !== "number" || entry.expiresAt <= now) continue;
|
|
3063
|
+
if (entry.cooldownUntil !== void 0 && typeof entry.cooldownUntil !== "number") continue;
|
|
3064
|
+
remembered.set(key, entry);
|
|
3065
|
+
}
|
|
3066
|
+
} catch {
|
|
3067
|
+
}
|
|
3068
|
+
}
|
|
3069
|
+
function savePersistent() {
|
|
3070
|
+
const path = persistentPath();
|
|
3071
|
+
try {
|
|
3072
|
+
mkdirSync3(dirname2(path), { recursive: true, mode: 448 });
|
|
3073
|
+
const live = {};
|
|
3074
|
+
const now = Date.now();
|
|
3075
|
+
for (const [key, entry] of remembered) {
|
|
3076
|
+
if (entry.expiresAt > now) live[key] = entry;
|
|
3077
|
+
}
|
|
3078
|
+
const tempPath = `${path}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
3079
|
+
writeFileSync2(tempPath, `${JSON.stringify(live)}
|
|
3080
|
+
`, { mode: 384 });
|
|
3081
|
+
renameSync2(tempPath, path);
|
|
3082
|
+
} catch {
|
|
3083
|
+
}
|
|
3084
|
+
}
|
|
3085
|
+
function protocolCacheKey(parts) {
|
|
3086
|
+
const credentialFingerprint = createHash2("sha256").update(parts.apiKey ?? "").update("\0").update(JSON.stringify(Object.entries(parts.headers ?? {}).sort(([a], [b]) => a.localeCompare(b)))).digest("hex");
|
|
3087
|
+
return createHash2("sha256").update(JSON.stringify({
|
|
3088
|
+
providerId: parts.providerId ?? "",
|
|
3089
|
+
modelId: parts.modelId,
|
|
3090
|
+
protocol: parts.protocol ?? "",
|
|
3091
|
+
baseURL: parts.baseURL ?? "",
|
|
3092
|
+
alternativeURL: parts.alternativeURL ?? "",
|
|
3093
|
+
credentialFingerprint
|
|
3094
|
+
})).digest("hex");
|
|
3095
|
+
}
|
|
3096
|
+
function rememberedProtocol(key, now = Date.now()) {
|
|
3097
|
+
loadPersistent(now);
|
|
3098
|
+
const entry = remembered.get(key);
|
|
3099
|
+
if (!entry) return void 0;
|
|
3100
|
+
if (entry.expiresAt <= now) {
|
|
3101
|
+
remembered.delete(key);
|
|
3102
|
+
savePersistent();
|
|
3103
|
+
return void 0;
|
|
3104
|
+
}
|
|
3105
|
+
if (entry.cooldownUntil !== void 0 && entry.cooldownUntil > now) return void 0;
|
|
3106
|
+
if (!entry.protocol) return void 0;
|
|
3107
|
+
return entry.protocol;
|
|
3108
|
+
}
|
|
3109
|
+
function rememberProtocol(key, protocol, now = Date.now()) {
|
|
3110
|
+
loadPersistent(now);
|
|
3111
|
+
remembered.set(key, { protocol, expiresAt: now + PREFERENCE_TTL_MS });
|
|
3112
|
+
savePersistent();
|
|
3113
|
+
}
|
|
3114
|
+
function protocolCooldownActive(key, now = Date.now()) {
|
|
3115
|
+
loadPersistent(now);
|
|
3116
|
+
const entry = remembered.get(key);
|
|
3117
|
+
if (!entry) return false;
|
|
3118
|
+
if (entry.expiresAt <= now) {
|
|
3119
|
+
remembered.delete(key);
|
|
3120
|
+
savePersistent();
|
|
3121
|
+
return false;
|
|
3122
|
+
}
|
|
3123
|
+
return entry.cooldownUntil !== void 0 && entry.cooldownUntil > now;
|
|
3124
|
+
}
|
|
3125
|
+
function rememberProtocolFailure(key, now = Date.now()) {
|
|
3126
|
+
loadPersistent(now);
|
|
3127
|
+
remembered.set(key, {
|
|
3128
|
+
expiresAt: now + PREFERENCE_TTL_MS,
|
|
3129
|
+
cooldownUntil: now + FAILURE_COOLDOWN_MS
|
|
3130
|
+
});
|
|
3131
|
+
savePersistent();
|
|
3132
|
+
}
|
|
3133
|
+
|
|
2937
3134
|
// src/provider-factory.ts
|
|
2938
3135
|
var RESPONSES_ONLY_PREFIXES = [
|
|
2939
3136
|
"gpt-5-codex",
|
|
@@ -3025,7 +3222,7 @@ async function loadSdkProviderFactory(npm) {
|
|
|
3025
3222
|
}
|
|
3026
3223
|
return cached;
|
|
3027
3224
|
}
|
|
3028
|
-
async function
|
|
3225
|
+
async function createLanguageModelSingle(spec) {
|
|
3029
3226
|
const npm = resolveProviderNpm(spec.npm);
|
|
3030
3227
|
const { modelId, apiKey, baseURL } = spec;
|
|
3031
3228
|
if (npm === VERTEX_ANTHROPIC_NPM) {
|
|
@@ -3072,7 +3269,8 @@ async function createLanguageModel(spec) {
|
|
|
3072
3269
|
if (npm === "@ai-sdk/anthropic") {
|
|
3073
3270
|
const { createAnthropic } = await import("@ai-sdk/anthropic");
|
|
3074
3271
|
const root = baseURL?.replace(/\/v1\/?$/, "").replace(/\/$/, "");
|
|
3075
|
-
const
|
|
3272
|
+
const openRouterBearer = spec.providerId?.trim().toLowerCase() === "openrouter" || root?.includes("openrouter.ai") === true;
|
|
3273
|
+
const anthropicOptions = spec.authType === "oauth" || openRouterBearer ? {
|
|
3076
3274
|
authToken: apiKey,
|
|
3077
3275
|
...spec.providerId === "claude-code" ? {
|
|
3078
3276
|
headers: {
|
|
@@ -3141,6 +3339,259 @@ async function createLanguageModel(spec) {
|
|
|
3141
3339
|
}
|
|
3142
3340
|
return model;
|
|
3143
3341
|
}
|
|
3342
|
+
function protocolForNpm(npm) {
|
|
3343
|
+
return npm === "@ai-sdk/anthropic" ? "anthropic" : "openai";
|
|
3344
|
+
}
|
|
3345
|
+
function isSemanticStreamPart(part) {
|
|
3346
|
+
if (!part || typeof part !== "object") return false;
|
|
3347
|
+
const type = part.type;
|
|
3348
|
+
return type === "text-start" || type === "text-delta" || type === "reasoning-start" || type === "reasoning-delta" || type === "tool-input-start" || type === "tool-input-delta" || type === "tool-input-end" || type === "tool-call" || type === "tool-result" || type === "tool-approval-request";
|
|
3349
|
+
}
|
|
3350
|
+
var MAX_PROTOCOL_PRELUDE_BYTES = 64 * 1024;
|
|
3351
|
+
var MAX_PROTOCOL_PRELUDE_EVENTS = 256;
|
|
3352
|
+
function streamPartBytes(part) {
|
|
3353
|
+
try {
|
|
3354
|
+
return JSON.stringify(part)?.length ?? 0;
|
|
3355
|
+
} catch {
|
|
3356
|
+
return 0;
|
|
3357
|
+
}
|
|
3358
|
+
}
|
|
3359
|
+
function observeAlternateStream(result, protocol, cacheKey) {
|
|
3360
|
+
const reader = result.stream.getReader();
|
|
3361
|
+
const stream = new ReadableStream({
|
|
3362
|
+
start: (controller) => {
|
|
3363
|
+
void (async () => {
|
|
3364
|
+
let semantic = false;
|
|
3365
|
+
let finish = false;
|
|
3366
|
+
try {
|
|
3367
|
+
while (true) {
|
|
3368
|
+
const next = await reader.read();
|
|
3369
|
+
if (next.done) break;
|
|
3370
|
+
semantic ||= isSemanticStreamPart(next.value);
|
|
3371
|
+
finish ||= Boolean(next.value && typeof next.value === "object" && next.value.type === "finish");
|
|
3372
|
+
controller.enqueue(next.value);
|
|
3373
|
+
}
|
|
3374
|
+
if (semantic && finish) rememberProtocol(cacheKey, protocol);
|
|
3375
|
+
else rememberProtocolFailure(cacheKey);
|
|
3376
|
+
controller.close();
|
|
3377
|
+
} catch (error) {
|
|
3378
|
+
rememberProtocolFailure(cacheKey);
|
|
3379
|
+
controller.error(error);
|
|
3380
|
+
} finally {
|
|
3381
|
+
reader.releaseLock();
|
|
3382
|
+
}
|
|
3383
|
+
})();
|
|
3384
|
+
},
|
|
3385
|
+
cancel: (reason) => reader.cancel(reason)
|
|
3386
|
+
});
|
|
3387
|
+
return { ...result, stream };
|
|
3388
|
+
}
|
|
3389
|
+
function createProtocolFallbackMiddleware(primary, alternate, primaryProtocol, alternateProtocol, cacheKey, onDebug) {
|
|
3390
|
+
const alternateModel = alternate;
|
|
3391
|
+
const note = (message) => {
|
|
3392
|
+
onDebug?.(`[protocol-fallback] ${message}`);
|
|
3393
|
+
};
|
|
3394
|
+
const attemptOrder = (runPrimary, runAlternate) => rememberedProtocol(cacheKey) === alternateProtocol ? { first: runAlternate, second: runPrimary, firstProtocol: alternateProtocol, secondProtocol: primaryProtocol } : { first: runPrimary, second: runAlternate, firstProtocol: primaryProtocol, secondProtocol: alternateProtocol };
|
|
3395
|
+
const surfacedError = (firstError, secondError) => classifyProtocolFailure(secondError).retryable ? firstError : secondError;
|
|
3396
|
+
return {
|
|
3397
|
+
specificationVersion: "v4",
|
|
3398
|
+
wrapGenerate: async ({ doGenerate, params }) => {
|
|
3399
|
+
if (protocolCooldownActive(cacheKey)) return doGenerate();
|
|
3400
|
+
const { first, second, firstProtocol, secondProtocol } = attemptOrder(
|
|
3401
|
+
doGenerate,
|
|
3402
|
+
() => alternateModel.doGenerate(params)
|
|
3403
|
+
);
|
|
3404
|
+
try {
|
|
3405
|
+
const result = await first();
|
|
3406
|
+
rememberProtocol(cacheKey, firstProtocol);
|
|
3407
|
+
return result;
|
|
3408
|
+
} catch (error) {
|
|
3409
|
+
const failure = classifyProtocolFailure(error);
|
|
3410
|
+
if (!failure.retryable) throw error;
|
|
3411
|
+
note(`${firstProtocol} failed (${failure.reason}${failure.status ? `, HTTP ${failure.status}` : ""}); retrying ${secondProtocol}`);
|
|
3412
|
+
let result;
|
|
3413
|
+
try {
|
|
3414
|
+
result = await second();
|
|
3415
|
+
} catch (alternateError) {
|
|
3416
|
+
const alternateFailure = classifyProtocolFailure(alternateError);
|
|
3417
|
+
note(`${secondProtocol} failed after fallback (${alternateFailure.reason}${alternateFailure.status ? `, HTTP ${alternateFailure.status}` : ""})`);
|
|
3418
|
+
rememberProtocolFailure(cacheKey);
|
|
3419
|
+
throw surfacedError(error, alternateError);
|
|
3420
|
+
}
|
|
3421
|
+
rememberProtocol(cacheKey, secondProtocol);
|
|
3422
|
+
return result;
|
|
3423
|
+
}
|
|
3424
|
+
},
|
|
3425
|
+
wrapStream: async ({ doStream, params }) => {
|
|
3426
|
+
if (protocolCooldownActive(cacheKey)) return doStream();
|
|
3427
|
+
const { first, second, firstProtocol, secondProtocol } = attemptOrder(
|
|
3428
|
+
doStream,
|
|
3429
|
+
() => alternateModel.doStream(params)
|
|
3430
|
+
);
|
|
3431
|
+
let primaryResult;
|
|
3432
|
+
try {
|
|
3433
|
+
primaryResult = await first();
|
|
3434
|
+
} catch (error) {
|
|
3435
|
+
const failure = classifyProtocolFailure(error);
|
|
3436
|
+
if (!failure.retryable) throw error;
|
|
3437
|
+
note(`${firstProtocol} stream failed (${failure.reason}${failure.status ? `, HTTP ${failure.status}` : ""}); retrying ${secondProtocol}`);
|
|
3438
|
+
let alternateResult;
|
|
3439
|
+
try {
|
|
3440
|
+
alternateResult = await second();
|
|
3441
|
+
} catch (alternateError) {
|
|
3442
|
+
const alternateFailure = classifyProtocolFailure(alternateError);
|
|
3443
|
+
note(`${secondProtocol} stream failed after fallback (${alternateFailure.reason}${alternateFailure.status ? `, HTTP ${alternateFailure.status}` : ""})`);
|
|
3444
|
+
rememberProtocolFailure(cacheKey);
|
|
3445
|
+
throw surfacedError(error, alternateError);
|
|
3446
|
+
}
|
|
3447
|
+
return observeAlternateStream(alternateResult, secondProtocol, cacheKey);
|
|
3448
|
+
}
|
|
3449
|
+
const primaryReader = primaryResult.stream.getReader();
|
|
3450
|
+
let switched = false;
|
|
3451
|
+
const stream = new ReadableStream({
|
|
3452
|
+
start: (controller) => {
|
|
3453
|
+
void (async () => {
|
|
3454
|
+
const buffered = [];
|
|
3455
|
+
let bufferedBytes = 0;
|
|
3456
|
+
let committed = false;
|
|
3457
|
+
let terminal = false;
|
|
3458
|
+
const pipeAlternate = async (cause, force = false) => {
|
|
3459
|
+
if (switched) {
|
|
3460
|
+
controller.error(cause);
|
|
3461
|
+
return;
|
|
3462
|
+
}
|
|
3463
|
+
const failure = classifyProtocolFailure(cause);
|
|
3464
|
+
if (!force && !failure.retryable) {
|
|
3465
|
+
controller.error(cause);
|
|
3466
|
+
return;
|
|
3467
|
+
}
|
|
3468
|
+
switched = true;
|
|
3469
|
+
note(`${firstProtocol} stream failed (${failure.reason}${failure.status ? `, HTTP ${failure.status}` : ""}); retrying ${secondProtocol}`);
|
|
3470
|
+
try {
|
|
3471
|
+
await primaryReader.cancel();
|
|
3472
|
+
} catch {
|
|
3473
|
+
}
|
|
3474
|
+
let alternateResult;
|
|
3475
|
+
try {
|
|
3476
|
+
alternateResult = await second();
|
|
3477
|
+
} catch (alternateError) {
|
|
3478
|
+
const alternateFailure = classifyProtocolFailure(alternateError);
|
|
3479
|
+
note(`${secondProtocol} stream failed after fallback (${alternateFailure.reason}${alternateFailure.status ? `, HTTP ${alternateFailure.status}` : ""})`);
|
|
3480
|
+
rememberProtocolFailure(cacheKey);
|
|
3481
|
+
throw surfacedError(cause, alternateError);
|
|
3482
|
+
}
|
|
3483
|
+
const reader = alternateResult.stream.getReader();
|
|
3484
|
+
let alternateSemantic = false;
|
|
3485
|
+
let alternateFinish = false;
|
|
3486
|
+
try {
|
|
3487
|
+
while (true) {
|
|
3488
|
+
const next = await reader.read();
|
|
3489
|
+
if (next.done) break;
|
|
3490
|
+
if (isSemanticStreamPart(next.value)) alternateSemantic = true;
|
|
3491
|
+
if (next.value && typeof next.value === "object" && next.value.type === "finish") {
|
|
3492
|
+
alternateFinish = true;
|
|
3493
|
+
}
|
|
3494
|
+
controller.enqueue(next.value);
|
|
3495
|
+
}
|
|
3496
|
+
if (alternateSemantic && alternateFinish) rememberProtocol(cacheKey, secondProtocol);
|
|
3497
|
+
else rememberProtocolFailure(cacheKey);
|
|
3498
|
+
controller.close();
|
|
3499
|
+
} finally {
|
|
3500
|
+
reader.releaseLock();
|
|
3501
|
+
}
|
|
3502
|
+
};
|
|
3503
|
+
try {
|
|
3504
|
+
while (true) {
|
|
3505
|
+
const next = await primaryReader.read();
|
|
3506
|
+
if (next.done) {
|
|
3507
|
+
if (!committed && terminal) {
|
|
3508
|
+
await pipeAlternate(new Error("provider returned an empty response"), true);
|
|
3509
|
+
} else if (committed && terminal) {
|
|
3510
|
+
rememberProtocol(cacheKey, firstProtocol);
|
|
3511
|
+
controller.close();
|
|
3512
|
+
} else {
|
|
3513
|
+
controller.close();
|
|
3514
|
+
}
|
|
3515
|
+
return;
|
|
3516
|
+
}
|
|
3517
|
+
const part = next.value;
|
|
3518
|
+
if (!committed && part && typeof part === "object" && part.type === "error") {
|
|
3519
|
+
await pipeAlternate(part.error ?? part);
|
|
3520
|
+
return;
|
|
3521
|
+
}
|
|
3522
|
+
if (!committed) {
|
|
3523
|
+
buffered.push(part);
|
|
3524
|
+
bufferedBytes += streamPartBytes(part);
|
|
3525
|
+
if (isSemanticStreamPart(part) || buffered.length >= MAX_PROTOCOL_PRELUDE_EVENTS || bufferedBytes >= MAX_PROTOCOL_PRELUDE_BYTES) {
|
|
3526
|
+
committed = true;
|
|
3527
|
+
for (const pending of buffered) controller.enqueue(pending);
|
|
3528
|
+
buffered.length = 0;
|
|
3529
|
+
bufferedBytes = 0;
|
|
3530
|
+
} else if (part && typeof part === "object" && part.type === "finish") {
|
|
3531
|
+
terminal = true;
|
|
3532
|
+
}
|
|
3533
|
+
} else {
|
|
3534
|
+
controller.enqueue(part);
|
|
3535
|
+
}
|
|
3536
|
+
}
|
|
3537
|
+
} catch (error) {
|
|
3538
|
+
if (!committed) await pipeAlternate(error);
|
|
3539
|
+
else controller.error(error);
|
|
3540
|
+
} finally {
|
|
3541
|
+
primaryReader.releaseLock();
|
|
3542
|
+
}
|
|
3543
|
+
})().catch((error) => controller.error(error));
|
|
3544
|
+
},
|
|
3545
|
+
cancel: (reason) => primaryReader.cancel(reason)
|
|
3546
|
+
});
|
|
3547
|
+
return { ...primaryResult, stream };
|
|
3548
|
+
}
|
|
3549
|
+
};
|
|
3550
|
+
}
|
|
3551
|
+
async function createLanguageModel(spec) {
|
|
3552
|
+
const npm = resolveProviderNpm(spec.npm);
|
|
3553
|
+
const primary = await createLanguageModelSingle(spec);
|
|
3554
|
+
if (spec.authType === "oauth" || !spec.baseURL) return primary;
|
|
3555
|
+
const primaryProtocol = protocolForNpm(npm);
|
|
3556
|
+
const alternative = resolveProtocolAlternative({
|
|
3557
|
+
providerId: spec.providerId,
|
|
3558
|
+
modelFormat: primaryProtocol,
|
|
3559
|
+
npm,
|
|
3560
|
+
baseURL: spec.baseURL
|
|
3561
|
+
});
|
|
3562
|
+
if (!alternative || !isDualProtocolGateway(spec.providerId, spec.baseURL)) return primary;
|
|
3563
|
+
const cacheKey = protocolCacheKey({
|
|
3564
|
+
providerId: spec.providerId,
|
|
3565
|
+
modelId: spec.modelId,
|
|
3566
|
+
protocol: primaryProtocol,
|
|
3567
|
+
baseURL: spec.baseURL,
|
|
3568
|
+
alternativeURL: alternative.upstreamUrl,
|
|
3569
|
+
apiKey: spec.apiKey,
|
|
3570
|
+
headers: spec.headers
|
|
3571
|
+
});
|
|
3572
|
+
let alternate;
|
|
3573
|
+
try {
|
|
3574
|
+
alternate = await createLanguageModelSingle({
|
|
3575
|
+
...spec,
|
|
3576
|
+
npm: alternative.npm,
|
|
3577
|
+
baseURL: alternative.baseURL
|
|
3578
|
+
});
|
|
3579
|
+
} catch (error) {
|
|
3580
|
+
spec.onDebug?.(`[protocol-fallback] alternate SDK unavailable: ${error instanceof Error ? error.message : String(error)}`);
|
|
3581
|
+
return primary;
|
|
3582
|
+
}
|
|
3583
|
+
return wrapLanguageModel({
|
|
3584
|
+
model: primary,
|
|
3585
|
+
middleware: createProtocolFallbackMiddleware(
|
|
3586
|
+
primary,
|
|
3587
|
+
alternate,
|
|
3588
|
+
primaryProtocol,
|
|
3589
|
+
alternative.modelFormat,
|
|
3590
|
+
cacheKey,
|
|
3591
|
+
spec.onDebug
|
|
3592
|
+
)
|
|
3593
|
+
});
|
|
3594
|
+
}
|
|
3144
3595
|
var ANTHROPIC_EFFORT_LEVELS = ["low", "medium", "high"];
|
|
3145
3596
|
var OPENAI_EFFORT_LEVELS = ["low", "medium", "high"];
|
|
3146
3597
|
var GEMINI_EFFORT_LEVELS = ["low", "medium", "high"];
|
|
@@ -4726,6 +5177,13 @@ export {
|
|
|
4726
5177
|
CLINE_PASS_CATALOG_URL,
|
|
4727
5178
|
CLINE_PASS_VALIDATION_URL,
|
|
4728
5179
|
CLINE_PASS_LEGACY_DEFAULT_CONTEXT_WINDOW,
|
|
5180
|
+
getAppHome,
|
|
5181
|
+
getLegacyAppHome,
|
|
5182
|
+
getConfigPath,
|
|
5183
|
+
getProvidersPath,
|
|
5184
|
+
getLogsPath,
|
|
5185
|
+
getVertexModelsPath,
|
|
5186
|
+
getLegacyConfPath,
|
|
4729
5187
|
modelPrefersResponsesApi,
|
|
4730
5188
|
isSdkMigratedNpm,
|
|
4731
5189
|
maxToolsForNpm,
|
|
@@ -4735,13 +5193,6 @@ export {
|
|
|
4735
5193
|
effortProviderOptions,
|
|
4736
5194
|
deepMergeProviderOptions,
|
|
4737
5195
|
thinkingProviderOptions,
|
|
4738
|
-
getAppHome,
|
|
4739
|
-
getLegacyAppHome,
|
|
4740
|
-
getConfigPath,
|
|
4741
|
-
getProvidersPath,
|
|
4742
|
-
getLogsPath,
|
|
4743
|
-
getVertexModelsPath,
|
|
4744
|
-
getLegacyConfPath,
|
|
4745
5196
|
contextWindowFromHeuristics,
|
|
4746
5197
|
resolveContextWindow,
|
|
4747
5198
|
stripOneMContextSuffix,
|
|
@@ -4749,6 +5200,7 @@ export {
|
|
|
4749
5200
|
routeLookupIds,
|
|
4750
5201
|
readOpencodeAuthFile,
|
|
4751
5202
|
isOpencodeOAuth,
|
|
5203
|
+
isOpencodeApi,
|
|
4752
5204
|
oauthCredentialToKeychainJson,
|
|
4753
5205
|
tokensToStoredCredential,
|
|
4754
5206
|
supportsNativeOAuth,
|
|
@@ -4822,4 +5274,4 @@ export {
|
|
|
4822
5274
|
streamAnthropicResponse,
|
|
4823
5275
|
generateAnthropicResponse
|
|
4824
5276
|
};
|
|
4825
|
-
//# sourceMappingURL=chunk-
|
|
5277
|
+
//# sourceMappingURL=chunk-VPDX24ZR.js.map
|