dsh-connect-trae 2.4.0 → 2.7.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/CHANGELOG.md +131 -0
- package/README.en.md +12 -2
- package/README.md +12 -2
- package/lib/client.js +240 -16
- package/lib/index.d.ts +139 -7
- package/lib/index.js +601 -26
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -185,12 +185,22 @@ function displayKey(name) {
|
|
|
185
185
|
* multiplier, reasoning and multimodal flags. `wire` (from `get_detail_param`)
|
|
186
186
|
* supplies the real `llm_utils_chat` `config_name` — the only id the chat
|
|
187
187
|
* endpoint actually accepts — plus the authoritative post-discount credit
|
|
188
|
-
* multiplier when `display_contact_config` carries one.
|
|
189
|
-
*
|
|
190
|
-
*
|
|
191
|
-
*
|
|
192
|
-
*
|
|
193
|
-
*
|
|
188
|
+
* multiplier when `display_contact_config` carries one.
|
|
189
|
+
*
|
|
190
|
+
* A remote row with no wire match is STILL KEPT (decision 2026-10-02, issue
|
|
191
|
+
* #19). It previously was dropped, on the reasoning that a row with no
|
|
192
|
+
* `config_name` is sent as an invalid one and rejected with 4001. That
|
|
193
|
+
* reasoning holds only for the accounts and functions we can measure, and
|
|
194
|
+
* measuring is the problem: whether a roster contains a model is ACCOUNT-
|
|
195
|
+
* SCOPED, so "no wire entry on a free account" cannot prove a paying account
|
|
196
|
+
* cannot call it. Dropping on that evidence silently denied those users models
|
|
197
|
+
* their own Trae IDE offers. The row therefore ships, and its call fails
|
|
198
|
+
* honestly: the bridge now translates the in-stream refusal into a readable
|
|
199
|
+
* error (`4001` → "Trae does not serve this model under the SOLO function the
|
|
200
|
+
* request used", `4011` → "Trae refused this model under the SOLO function the
|
|
201
|
+
* request used"), so a user who picks an unusable model is told why instead of
|
|
202
|
+
* seeing nothing. This reverses the 2026-08-30 note about `Doubao-Seed-Code`,
|
|
203
|
+
* which later turned out to be callable under `chat_v3`.
|
|
194
204
|
*
|
|
195
205
|
* Credit multiplier precedence: the wire's `display_contact_config` rate wins
|
|
196
206
|
* whenever present (it is the post-discount figure the Trae IDE renders — the
|
|
@@ -217,8 +227,7 @@ function mergeTraeModelSources(remote, wire) {
|
|
|
217
227
|
const result = [];
|
|
218
228
|
for (const model of remote) {
|
|
219
229
|
const wireModel = wireById.get(displayKey(model.id)) ?? wireByName.get(displayKey(model.name));
|
|
220
|
-
|
|
221
|
-
const creditMultiplier = wireModel.creditMultiplier ?? model.creditMultiplier;
|
|
230
|
+
const creditMultiplier = wireModel?.creditMultiplier ?? model.creditMultiplier;
|
|
222
231
|
result.push({
|
|
223
232
|
id: model.id,
|
|
224
233
|
name: model.name,
|
|
@@ -232,8 +241,8 @@ function mergeTraeModelSources(remote, wire) {
|
|
|
232
241
|
reasoning: model.reasoning,
|
|
233
242
|
reasoningEfforts: Object.fromEntries(model.reasoning.supported.map((effort) => [effort, effort === "low" ? "light" : effort === "xhigh" ? "extra_high" : "high"]))
|
|
234
243
|
},
|
|
235
|
-
...wireModel.id !== "" && wireModel.id !== model.id ? { wireConfigName: wireModel.id } : {},
|
|
236
|
-
...wireModel
|
|
244
|
+
...wireModel !== void 0 && wireModel.id !== "" && wireModel.id !== model.id ? { wireConfigName: wireModel.id } : {},
|
|
245
|
+
...wireModel?.function === void 0 ? {} : { wireFunction: wireModel.function }
|
|
237
246
|
});
|
|
238
247
|
}
|
|
239
248
|
return result;
|
|
@@ -1942,7 +1951,7 @@ function applyReasoningEffort(body, effort, capability) {
|
|
|
1942
1951
|
//#region src/solo.ts
|
|
1943
1952
|
const TRAE_SOLO_FUNCTION = "solo_work_lite";
|
|
1944
1953
|
/**
|
|
1945
|
-
*
|
|
1954
|
+
* `llm_utils_chat` functions to union per region, in priority order.
|
|
1946
1955
|
*
|
|
1947
1956
|
* Trae spreads its callable roster across several SOLO-mode functions, and a
|
|
1948
1957
|
* model is only usable through the one that lists it: `glm-5.3` is absent from
|
|
@@ -1950,14 +1959,69 @@ const TRAE_SOLO_FUNCTION = "solo_work_lite";
|
|
|
1950
1959
|
* calling it through the former answers `4001 param is invalid`, through the
|
|
1951
1960
|
* latter streams normally). Rather than betting on a single function, the
|
|
1952
1961
|
* directory unions them; the first function to provide a config wins, so the
|
|
1953
|
-
* order below decides which wire name a model is called with.
|
|
1962
|
+
* order below decides which wire name AND which function a model is called with.
|
|
1963
|
+
*
|
|
1964
|
+
* THIS LIST IS NOT "directory functions" — it is the set of functions
|
|
1965
|
+
* `llm_utils_chat` actually accepts, because ownership here is what the chat
|
|
1966
|
+
* call replays. `solo_agent` is deliberately ABSENT (measured 2026-10-01 on a
|
|
1967
|
+
* live SG credential, issue #19): it is a Remote-agent roster function, not a
|
|
1968
|
+
* chat function. Every model sent with `function: "solo_agent"` — `gpt-5.4`,
|
|
1969
|
+
* `gpt-5.6-sol`, `gpt-6-astra`, `glm-5.2`, `kimi-k2.7-code` — answered
|
|
1970
|
+
* `event:error {"code":4011}` (message says "rate limit", it is really "not
|
|
1971
|
+
* served here"), while the same model with the same body answered normally
|
|
1972
|
+
* under `solo_work_remote` / `solo_work_lite` / `solo_agent_remote`.
|
|
1973
|
+
*
|
|
1974
|
+
* Note `get_detail_param` DOES answer `solo_agent` with a 43-entry list, so
|
|
1975
|
+
* asking it looks helpful while making 8 of the 10 exposed ai-region models
|
|
1976
|
+
* uncallable: it won precedence and stamped its own name onto configs it
|
|
1977
|
+
* cannot serve. `solo_agent_remote` is the chat-callable half of that roster
|
|
1978
|
+
* (it answers `minimax-m3`, `gemini-3.1-pro`, … that no `solo_work_*` lists)
|
|
1979
|
+
* and therefore takes the last slot — after the two IDE functions that serve
|
|
1980
|
+
* their shared configs without the Remote plan gate.
|
|
1981
|
+
*
|
|
1982
|
+
* `chat_v3` is the widest chat roster of all (measured 2026-10-01 on live
|
|
1983
|
+
* credentials, issue #19 follow-up): its `get_detail_param` answers 40
|
|
1984
|
+
* config_names on `ai` — more than any other function except `solo_agent` (43,
|
|
1985
|
+
* which cannot serve chat). It lists models NO other chat function names, and
|
|
1986
|
+
* it is joined LAST so it only fills gaps: a config two functions list keeps
|
|
1987
|
+
* the earlier function, so no precedence is disturbed. The ai models it unlocks
|
|
1988
|
+
* were each verified callable end-to-end through the plugin's own
|
|
1989
|
+
* `prepareSoloBody` envelope:
|
|
1990
|
+
*
|
|
1991
|
+
* ai (+6): kimi-k2.7-code, deepseek-v4-flash-0731, deepseek-v3.2,
|
|
1992
|
+
* gemini-3-flash-premium, gemini_2.5_flash_premium, Dola-Seed-2.0-Code
|
|
1993
|
+
*
|
|
1994
|
+
* CN joins `chat_v3` and `solo_coder` too (2026-10-02). Both are callable there
|
|
1995
|
+
* — verified per model through `prepareSoloBody`, calling each model under a
|
|
1996
|
+
* function whose roster names it: `glm-5` / `glm-5.1` / `qwen-3.5` and
|
|
1997
|
+
* `deepseek-v4-pro` / `deepseek-v4-flash` under `solo_coder`, and
|
|
1998
|
+
* `doubao-seed-2.0-code` under `solo_coder` / `solo_work_remote` /
|
|
1999
|
+
* `solo_work_lite`. An earlier note here claimed those three answered `4001`
|
|
2000
|
+
* "under every function" — that was a measurement error, not an upstream
|
|
2001
|
+
* refusal: the probe had called them by display id while the chat endpoint needs
|
|
2002
|
+
* the config_name its owning function returned.
|
|
2003
|
+
*
|
|
2004
|
+
* This also corrects an older note that `Doubao-Seed-Code` "has no config_name
|
|
2005
|
+
* and must stay hidden" (docs/MODEL_MANAGEMENT_DESIGN.md): it does have one,
|
|
2006
|
+
* under `chat_v3`.
|
|
2007
|
+
*
|
|
2008
|
+
* A model in NO chat function's roster is still left to the merge: it ships
|
|
2009
|
+
* with no wire target and its call fails with a readable upstream message
|
|
2010
|
+
* rather than being hidden (see mergeTraeModelSources).
|
|
1954
2011
|
*/
|
|
1955
2012
|
const TRAE_DIRECTORY_FUNCTIONS = {
|
|
1956
|
-
cn: [
|
|
2013
|
+
cn: [
|
|
2014
|
+
"solo_work_remote",
|
|
2015
|
+
TRAE_SOLO_FUNCTION,
|
|
2016
|
+
"solo_agent_remote",
|
|
2017
|
+
"chat_v3",
|
|
2018
|
+
"solo_coder"
|
|
2019
|
+
],
|
|
1957
2020
|
ai: [
|
|
1958
|
-
"solo_agent",
|
|
1959
2021
|
"solo_work_remote",
|
|
1960
|
-
TRAE_SOLO_FUNCTION
|
|
2022
|
+
TRAE_SOLO_FUNCTION,
|
|
2023
|
+
"solo_agent_remote",
|
|
2024
|
+
"chat_v3"
|
|
1961
2025
|
]
|
|
1962
2026
|
};
|
|
1963
2027
|
const TRAE_SOLO_CHAT_PATH = "/api/agent/v3/llm_utils_chat";
|
|
@@ -2304,6 +2368,75 @@ function decodeTraeEvent(event) {
|
|
|
2304
2368
|
}
|
|
2305
2369
|
//#endregion
|
|
2306
2370
|
//#region src/solo-bridge.ts
|
|
2371
|
+
/**
|
|
2372
|
+
* What Trae's in-stream error codes mean. Measured 2026-10-01 on a live SG
|
|
2373
|
+
* credential (issue #19), by sending the same body to several functions:
|
|
2374
|
+
*
|
|
2375
|
+
* - `1005` — the model needs a paid plan. Trae answers HTTP 200 with
|
|
2376
|
+
* `event:error {"code":1005,"message":"","extra":"{\"plan\":1}"}`, so the
|
|
2377
|
+
* gate is only visible in `extra.plan`; `gpt-6-sol` returned it under both
|
|
2378
|
+
* `solo_work_remote` and `solo_agent_remote` on a free account.
|
|
2379
|
+
* - `4011` — the function the request named does not serve that model. Its
|
|
2380
|
+
* message claims "exceeded the rate limit", which is misleading: the same
|
|
2381
|
+
* model answers normally when sent under `solo_work_remote`.
|
|
2382
|
+
* - `4001` — `config_name` is not one this function serves.
|
|
2383
|
+
*/
|
|
2384
|
+
const TRAE_ERROR_HINTS = {
|
|
2385
|
+
1005: "Trae requires a paid plan for this model; the current account's plan does not cover it",
|
|
2386
|
+
4001: "Trae does not serve this model under the SOLO function the request used (config_name rejected)",
|
|
2387
|
+
4011: "Trae refused this model under the SOLO function the request used"
|
|
2388
|
+
};
|
|
2389
|
+
/** `extra` is a JSON *string* on the wire; unwrap it to read `plan`. */
|
|
2390
|
+
function traeErrorPlan(extra) {
|
|
2391
|
+
let value = extra;
|
|
2392
|
+
if (typeof value === "string") try {
|
|
2393
|
+
value = JSON.parse(value);
|
|
2394
|
+
} catch {
|
|
2395
|
+
return;
|
|
2396
|
+
}
|
|
2397
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
2398
|
+
const plan = value["plan"];
|
|
2399
|
+
return typeof plan === "number" ? plan : void 0;
|
|
2400
|
+
}
|
|
2401
|
+
/** A readable message for an in-stream Trae failure, with its code preserved. */
|
|
2402
|
+
function describeTraeFailure(failure) {
|
|
2403
|
+
const plan = traeErrorPlan(failure.extra);
|
|
2404
|
+
const parts = [
|
|
2405
|
+
failure.code === void 0 ? "Trae refused the request" : TRAE_ERROR_HINTS[failure.code] ?? "Trae refused the request",
|
|
2406
|
+
...plan === void 0 ? [] : [`plan ${plan}`],
|
|
2407
|
+
...failure.code === void 0 ? [] : [`Trae code ${failure.code}`]
|
|
2408
|
+
];
|
|
2409
|
+
const detail = failure.message.trim();
|
|
2410
|
+
return detail === "" ? parts.join(" · ") : `${parts.join(" · ")} · upstream: ${detail}`;
|
|
2411
|
+
}
|
|
2412
|
+
/**
|
|
2413
|
+
* HTTP status + error kind for an in-stream failure, so the loopback shim can
|
|
2414
|
+
* answer with a real error instead of a truncated 200 stream.
|
|
2415
|
+
*
|
|
2416
|
+
* `1005` is a subscription gate: 402 is what makes clients stop rather than
|
|
2417
|
+
* retry, and it is truthful. Everything else is a client-side rejection.
|
|
2418
|
+
*/
|
|
2419
|
+
function classifyTraeFailure(failure) {
|
|
2420
|
+
return failure.code === 1005 ? {
|
|
2421
|
+
status: 402,
|
|
2422
|
+
kind: "hard_credit"
|
|
2423
|
+
} : {
|
|
2424
|
+
status: 400,
|
|
2425
|
+
kind: "client"
|
|
2426
|
+
};
|
|
2427
|
+
}
|
|
2428
|
+
/** Recognise an in-stream failure event, or return undefined for anything else. */
|
|
2429
|
+
function traeFailureOf(decoded) {
|
|
2430
|
+
if (decoded.type !== "unknown") return void 0;
|
|
2431
|
+
const payload = decoded.data;
|
|
2432
|
+
const code = typeof payload?.["code"] === "number" ? payload["code"] : void 0;
|
|
2433
|
+
if (decoded.event !== "error" && !(code !== void 0 && code >= 4e3)) return void 0;
|
|
2434
|
+
return {
|
|
2435
|
+
...code === void 0 ? {} : { code },
|
|
2436
|
+
message: typeof payload?.["message"] === "string" ? payload["message"] : "",
|
|
2437
|
+
...payload?.["extra"] === void 0 ? {} : { extra: payload["extra"] }
|
|
2438
|
+
};
|
|
2439
|
+
}
|
|
2307
2440
|
function normalizeToolCalls(value) {
|
|
2308
2441
|
if (!Array.isArray(value)) return [];
|
|
2309
2442
|
const calls = [];
|
|
@@ -2328,6 +2461,10 @@ function normalizeToolCalls(value) {
|
|
|
2328
2461
|
function bridgeTraeSoloStream(response, model) {
|
|
2329
2462
|
const source = response.body;
|
|
2330
2463
|
if (source === null) return new Response(null, { status: 502 });
|
|
2464
|
+
return bridgeTraeSource(source, model);
|
|
2465
|
+
}
|
|
2466
|
+
/** {@link bridgeTraeSoloStream} over an already-opened byte stream. */
|
|
2467
|
+
function bridgeTraeSource(source, model) {
|
|
2331
2468
|
const id = `chatcmpl-${randomUUID().replaceAll("-", "").slice(0, 24)}`;
|
|
2332
2469
|
const created = Math.floor(Date.now() / 1e3);
|
|
2333
2470
|
const decoder = new TextDecoder();
|
|
@@ -2356,9 +2493,8 @@ function bridgeTraeSoloStream(response, model) {
|
|
|
2356
2493
|
const consume = (event) => {
|
|
2357
2494
|
const decoded = decodeTraeEvent(event);
|
|
2358
2495
|
if (decoded.type === "unknown") {
|
|
2359
|
-
const
|
|
2360
|
-
|
|
2361
|
-
if (decoded.event === "error" || code !== void 0 && code >= 4e3) upstreamError = new Error(typeof payload?.["message"] === "string" && payload["message"] !== "" ? payload["message"] : `Trae upstream error (code ${code ?? "?"})`);
|
|
2496
|
+
const failure = traeFailureOf(decoded);
|
|
2497
|
+
if (failure !== void 0) upstreamError = new Error(describeTraeFailure(failure));
|
|
2362
2498
|
return;
|
|
2363
2499
|
}
|
|
2364
2500
|
if (decoded.type === "delta") {
|
|
@@ -2428,6 +2564,123 @@ function bridgeTraeSoloStream(response, model) {
|
|
|
2428
2564
|
headers: { "Content-Type": "text/event-stream" }
|
|
2429
2565
|
});
|
|
2430
2566
|
}
|
|
2567
|
+
/**
|
|
2568
|
+
* How far {@link openTraeSource} reads before giving up on finding a failure.
|
|
2569
|
+
* Trae opens a real answer with small `metadata` / `timing_cost` events, so a
|
|
2570
|
+
* healthy stream is recognised well inside these bounds; they exist only so a
|
|
2571
|
+
* pathological stream cannot stall the response headers.
|
|
2572
|
+
*/
|
|
2573
|
+
const PEEK_MAX_CHUNKS = 64;
|
|
2574
|
+
const PEEK_MAX_BYTES = 262144;
|
|
2575
|
+
/** Replay the bytes the peek consumed, then drain the rest of the upstream. */
|
|
2576
|
+
function replayTraeSource(buffered, reader, ended) {
|
|
2577
|
+
let index = 0;
|
|
2578
|
+
return new ReadableStream({
|
|
2579
|
+
async pull(controller) {
|
|
2580
|
+
const chunk = buffered[index];
|
|
2581
|
+
if (chunk !== void 0) {
|
|
2582
|
+
index += 1;
|
|
2583
|
+
controller.enqueue(chunk);
|
|
2584
|
+
return;
|
|
2585
|
+
}
|
|
2586
|
+
if (ended) {
|
|
2587
|
+
controller.close();
|
|
2588
|
+
return;
|
|
2589
|
+
}
|
|
2590
|
+
const next = await reader.read();
|
|
2591
|
+
if (next.done) {
|
|
2592
|
+
controller.close();
|
|
2593
|
+
return;
|
|
2594
|
+
}
|
|
2595
|
+
controller.enqueue(next.value);
|
|
2596
|
+
},
|
|
2597
|
+
async cancel(reason) {
|
|
2598
|
+
await reader.cancel(reason).catch(() => {});
|
|
2599
|
+
}
|
|
2600
|
+
});
|
|
2601
|
+
}
|
|
2602
|
+
/**
|
|
2603
|
+
* Open the upstream answer, failing fast when Trae refuses BEFORE producing any
|
|
2604
|
+
* output.
|
|
2605
|
+
*
|
|
2606
|
+
* This exists because Trae reports authorisation and subscription failures
|
|
2607
|
+
* inside a **200** SSE body. Relaying that as a 200 and aborting mid-stream
|
|
2608
|
+
* (what this bridge used to do) loses the code at the HTTP boundary: pi-ai only
|
|
2609
|
+
* sees a truncated stream and reports `Stream ended without finish_reason`,
|
|
2610
|
+
* which reads as a network fault and is retried five times — the exact symptom
|
|
2611
|
+
* in issue #19. Peeking until the first real output lets a refusal become a
|
|
2612
|
+
* normal error response with its code and plan intact.
|
|
2613
|
+
*
|
|
2614
|
+
* The peek is limited to the head of the stream and replayed verbatim, so a
|
|
2615
|
+
* healthy answer keeps streaming with no buffering beyond the first event.
|
|
2616
|
+
*/
|
|
2617
|
+
async function openTraeSource(response) {
|
|
2618
|
+
const body = response.body;
|
|
2619
|
+
if (body === null) return {
|
|
2620
|
+
ok: false,
|
|
2621
|
+
status: 502,
|
|
2622
|
+
kind: "server",
|
|
2623
|
+
message: "Trae upstream returned no response body"
|
|
2624
|
+
};
|
|
2625
|
+
const reader = body.getReader();
|
|
2626
|
+
const decoder = new TextDecoder();
|
|
2627
|
+
const sse = new SseDecoder();
|
|
2628
|
+
const buffered = [];
|
|
2629
|
+
let bytes = 0;
|
|
2630
|
+
let ended = false;
|
|
2631
|
+
try {
|
|
2632
|
+
while (buffered.length < PEEK_MAX_CHUNKS && bytes < PEEK_MAX_BYTES) {
|
|
2633
|
+
const next = await reader.read();
|
|
2634
|
+
if (next.done) {
|
|
2635
|
+
ended = true;
|
|
2636
|
+
break;
|
|
2637
|
+
}
|
|
2638
|
+
buffered.push(next.value);
|
|
2639
|
+
bytes += next.value.byteLength;
|
|
2640
|
+
let answered = false;
|
|
2641
|
+
for (const event of sse.push(decoder.decode(next.value, { stream: true }))) {
|
|
2642
|
+
const decoded = decodeTraeEvent(event);
|
|
2643
|
+
const failure = traeFailureOf(decoded);
|
|
2644
|
+
if (failure !== void 0) {
|
|
2645
|
+
await reader.cancel().catch(() => {});
|
|
2646
|
+
return {
|
|
2647
|
+
ok: false,
|
|
2648
|
+
...classifyTraeFailure(failure),
|
|
2649
|
+
message: describeTraeFailure(failure)
|
|
2650
|
+
};
|
|
2651
|
+
}
|
|
2652
|
+
if (decoded.type !== "unknown") answered = true;
|
|
2653
|
+
}
|
|
2654
|
+
if (answered) break;
|
|
2655
|
+
}
|
|
2656
|
+
} catch (error) {
|
|
2657
|
+
reader.releaseLock();
|
|
2658
|
+
return {
|
|
2659
|
+
ok: false,
|
|
2660
|
+
status: 0,
|
|
2661
|
+
kind: "server",
|
|
2662
|
+
message: `transport error: ${String(error)}`
|
|
2663
|
+
};
|
|
2664
|
+
}
|
|
2665
|
+
return {
|
|
2666
|
+
ok: true,
|
|
2667
|
+
source: replayTraeSource(buffered, reader, ended)
|
|
2668
|
+
};
|
|
2669
|
+
}
|
|
2670
|
+
/**
|
|
2671
|
+
* Bridge an upstream answer, turning an early refusal into a real error result.
|
|
2672
|
+
*
|
|
2673
|
+
* The sibling {@link bridgeTraeSoloStream} stays synchronous and always answers
|
|
2674
|
+
* 200; it is the primitive, and this is the policy the bridge class uses.
|
|
2675
|
+
*/
|
|
2676
|
+
async function bridgeTraeSoloStreamOrFail(response, model) {
|
|
2677
|
+
const opened = await openTraeSource(response);
|
|
2678
|
+
if (!opened.ok) return opened;
|
|
2679
|
+
return {
|
|
2680
|
+
ok: true,
|
|
2681
|
+
response: bridgeTraeSource(opened.source, model)
|
|
2682
|
+
};
|
|
2683
|
+
}
|
|
2431
2684
|
/** Native SOLO client wrapper used by the loopback OpenAI adapter. */
|
|
2432
2685
|
var TraeSoloBridge = class {
|
|
2433
2686
|
upstream;
|
|
@@ -2475,10 +2728,7 @@ var TraeSoloBridge = class {
|
|
|
2475
2728
|
}
|
|
2476
2729
|
const result = await this.upstream.chatStream(prepared, signal);
|
|
2477
2730
|
if (!result.ok) return result;
|
|
2478
|
-
return
|
|
2479
|
-
ok: true,
|
|
2480
|
-
response: bridgeTraeSoloStream(result.response, model)
|
|
2481
|
-
};
|
|
2731
|
+
return await bridgeTraeSoloStreamOrFail(result.response, model);
|
|
2482
2732
|
}
|
|
2483
2733
|
};
|
|
2484
2734
|
//#endregion
|
|
@@ -2541,6 +2791,66 @@ function parseTraeRemoteModel(value) {
|
|
|
2541
2791
|
//#region src/solo-remote.ts
|
|
2542
2792
|
const TRAE_SOLO_REMOTE_BASE = "https://solo.trae.cn/api/remote/v1";
|
|
2543
2793
|
/**
|
|
2794
|
+
* Remote-directory functions to ask for, per region.
|
|
2795
|
+
*
|
|
2796
|
+
* The remote `/models` answer is **grouped by function** and the groups are not
|
|
2797
|
+
* interchangeable (measured 2026-10-01, issue #19):
|
|
2798
|
+
*
|
|
2799
|
+
* ```
|
|
2800
|
+
* solo_agent : 19 models ← superset; also the ONLY source of
|
|
2801
|
+
* gpt-6-astra / gpt-5.6-sol|terra|luna /
|
|
2802
|
+
* glm-5.2 / gpt-5.5 / Seed-2.1-Turbo
|
|
2803
|
+
* solo_agent_remote : 10 models ← what this client used to read, and all it
|
|
2804
|
+
* read: the preferred group only
|
|
2805
|
+
* ```
|
|
2806
|
+
*
|
|
2807
|
+
* `solo_agent` ⊇ `solo_agent_remote`, so reading just the latter hid nine
|
|
2808
|
+
* models that the Trae IDE does show — exactly what the reporter saw. Both
|
|
2809
|
+
* groups are therefore requested, and every returned group is unioned.
|
|
2810
|
+
*
|
|
2811
|
+
* Asking for a function the gateway does not serve is harmless: unknown names
|
|
2812
|
+
* are ignored rather than rejected (`solo_work_remote` returns no group on
|
|
2813
|
+
* `ai`, and the request still answers HTTP 200).
|
|
2814
|
+
*
|
|
2815
|
+
* `chat_v3` was added on 2026-10-01 (issue #19 follow-up) because it carries
|
|
2816
|
+
* three ai-region models that appear in NO other group — `deepseek-v3.2`,
|
|
2817
|
+
* `gemini-3-flash-premium`, `gemini_2.5_flash_premium` — and those three are
|
|
2818
|
+
* callable through it (see TRAE_DIRECTORY_FUNCTIONS in solo.ts).
|
|
2819
|
+
*
|
|
2820
|
+
* Both regions now ask the SAME group list (2026-10-02). CN previously asked
|
|
2821
|
+
* only `solo_agent_remote` + `solo_work_remote`, which left its `solo_coder`
|
|
2822
|
+
* group (12 models) unread — the same shape of miss as the ai one. Every group
|
|
2823
|
+
* below was measured to exist on both gateways; a region that does not serve a
|
|
2824
|
+
* name simply returns no group for it, which the union tolerates. Reading costs
|
|
2825
|
+
* one query and the merge no longer discards rows for lacking a wire match (see
|
|
2826
|
+
* mergeTraeModelSources), so an unread group is a model silently missing from
|
|
2827
|
+
* the user's own list — the failure this list exists to prevent.
|
|
2828
|
+
*
|
|
2829
|
+
* `builder_v3` is included for the same reason even though neither region's
|
|
2830
|
+
* current wire lists name it: its rows are real directory entries, and whether
|
|
2831
|
+
* a paying account can call one is not something this client can determine.
|
|
2832
|
+
*/
|
|
2833
|
+
const TRAE_REMOTE_DIRECTORY_FUNCTIONS = {
|
|
2834
|
+
cn: [
|
|
2835
|
+
"solo_agent",
|
|
2836
|
+
"solo_agent_remote",
|
|
2837
|
+
"solo_work_remote",
|
|
2838
|
+
"solo_work_lite",
|
|
2839
|
+
"chat_v3",
|
|
2840
|
+
"solo_coder",
|
|
2841
|
+
"builder_v3"
|
|
2842
|
+
],
|
|
2843
|
+
ai: [
|
|
2844
|
+
"solo_agent",
|
|
2845
|
+
"solo_agent_remote",
|
|
2846
|
+
"solo_work_remote",
|
|
2847
|
+
"solo_work_lite",
|
|
2848
|
+
"chat_v3",
|
|
2849
|
+
"solo_coder",
|
|
2850
|
+
"builder_v3"
|
|
2851
|
+
]
|
|
2852
|
+
};
|
|
2853
|
+
/**
|
|
2544
2854
|
* Region-scoped request dressing. The CN portal is `solo.trae.cn` with the
|
|
2545
2855
|
* CN locale headers; the international directory lives on the shared
|
|
2546
2856
|
* `coresg-normal.trae.ai` gateway and was verified (2026-09-15) with the
|
|
@@ -2590,16 +2900,16 @@ var TraeSoloRemoteCatalogClient = class {
|
|
|
2590
2900
|
const region = regionOfCredential(await this.options.credential());
|
|
2591
2901
|
const base = this.baseUrl ?? REGION_GATEWAYS[region].remote;
|
|
2592
2902
|
const headers = await this.headers(region);
|
|
2593
|
-
const
|
|
2903
|
+
const functions = TRAE_REMOTE_DIRECTORY_FUNCTIONS[region].join(",");
|
|
2904
|
+
const response = await this.fetchImpl(`${base}/models?functions=${functions}`, {
|
|
2594
2905
|
headers,
|
|
2595
2906
|
signal: signal ?? AbortSignal.timeout(3e4)
|
|
2596
2907
|
});
|
|
2597
2908
|
if (!response.ok) throw new Error(`SOLO remote models returned HTTP ${response.status}`);
|
|
2598
2909
|
const groups = (await response.json()).data?.list ?? [];
|
|
2599
|
-
const preferred = groups.find((group) => group.function === "solo_agent_remote") ?? groups[0];
|
|
2600
2910
|
const seen = /* @__PURE__ */ new Set();
|
|
2601
2911
|
const models = [];
|
|
2602
|
-
for (const raw of
|
|
2912
|
+
for (const group of groups) for (const raw of group.models ?? []) {
|
|
2603
2913
|
const model = parseTraeRemoteModel(raw);
|
|
2604
2914
|
if (model === void 0 || seen.has(model.id)) continue;
|
|
2605
2915
|
seen.add(model.id);
|
|
@@ -3145,6 +3455,165 @@ var TraeDelegatingUpstreamClient = class {
|
|
|
3145
3455
|
}
|
|
3146
3456
|
};
|
|
3147
3457
|
//#endregion
|
|
3458
|
+
//#region src/model-probe.ts
|
|
3459
|
+
/**
|
|
3460
|
+
* How long one probe may take. Trae answers a one-word prompt in a few seconds;
|
|
3461
|
+
* beyond this the answer is not worth waiting for and is reported as unknown.
|
|
3462
|
+
*/
|
|
3463
|
+
const TRAE_PROBE_TIMEOUT_MS = 3e4;
|
|
3464
|
+
/** Upstream error codes the probe recognises, and what they mean. */
|
|
3465
|
+
const GATE_CODES = /* @__PURE__ */ new Set([1005]);
|
|
3466
|
+
const UNSUPPORTED_CODES = /* @__PURE__ */ new Set([4001, 4011]);
|
|
3467
|
+
/** Pull `Trae code N` out of an already-translated failure message. */
|
|
3468
|
+
function codeOf(message) {
|
|
3469
|
+
const match = /Trae code (\d+)/u.exec(message);
|
|
3470
|
+
return match === null ? void 0 : Number(match[1]);
|
|
3471
|
+
}
|
|
3472
|
+
/**
|
|
3473
|
+
* Classify a failure that arrived as a thrown stream error rather than a
|
|
3474
|
+
* returned result.
|
|
3475
|
+
*
|
|
3476
|
+
* The bridge errors the stream (instead of returning a result) when a refusal
|
|
3477
|
+
* interrupts an answer that had already started; its message is the same
|
|
3478
|
+
* translated text, so a named code is still decisive. Anything without a
|
|
3479
|
+
* recognised code — an abort, a socket reset, a proxy — stays `unknown`.
|
|
3480
|
+
*/
|
|
3481
|
+
function classifyProbeError(message) {
|
|
3482
|
+
if (codeOf(message) !== void 0) {
|
|
3483
|
+
const classified = classifyProbeResult({
|
|
3484
|
+
ok: false,
|
|
3485
|
+
status: 0,
|
|
3486
|
+
kind: "client",
|
|
3487
|
+
message
|
|
3488
|
+
}, false);
|
|
3489
|
+
if (classified.verdict !== "unknown") return classified;
|
|
3490
|
+
}
|
|
3491
|
+
return {
|
|
3492
|
+
verdict: "unknown",
|
|
3493
|
+
detail: message === "" ? "the stream failed without a message" : `stream failed: ${message}`
|
|
3494
|
+
};
|
|
3495
|
+
}
|
|
3496
|
+
/**
|
|
3497
|
+
* Classify a completed upstream call.
|
|
3498
|
+
*
|
|
3499
|
+
* `result` is what `TraeSoloBridge.chatStream` returned, so a refusal that
|
|
3500
|
+
* arrived inside a 200 body has already been turned into a real error result
|
|
3501
|
+
* (and its code translated into `message`) by the bridge — this function only
|
|
3502
|
+
* has to read it back.
|
|
3503
|
+
*/
|
|
3504
|
+
function classifyProbeResult(result, outputSeen) {
|
|
3505
|
+
if (result.ok) return outputSeen ? {
|
|
3506
|
+
verdict: "available",
|
|
3507
|
+
detail: "upstream answered"
|
|
3508
|
+
} : {
|
|
3509
|
+
verdict: "unknown",
|
|
3510
|
+
detail: "the stream ended without any output"
|
|
3511
|
+
};
|
|
3512
|
+
const message = result.message.trim() === "" ? `HTTP ${result.status}` : result.message;
|
|
3513
|
+
if (result.kind === "authentication" || result.status === 401 || result.status === 403) return {
|
|
3514
|
+
verdict: "auth",
|
|
3515
|
+
detail: message
|
|
3516
|
+
};
|
|
3517
|
+
if (result.kind === "soft_rate" || result.status === 429) return {
|
|
3518
|
+
verdict: "rate_limited",
|
|
3519
|
+
detail: message
|
|
3520
|
+
};
|
|
3521
|
+
const code = codeOf(message);
|
|
3522
|
+
if (code !== void 0 && GATE_CODES.has(code)) return {
|
|
3523
|
+
verdict: "gated",
|
|
3524
|
+
detail: message
|
|
3525
|
+
};
|
|
3526
|
+
if (code !== void 0 && UNSUPPORTED_CODES.has(code)) return {
|
|
3527
|
+
verdict: "unsupported",
|
|
3528
|
+
detail: message
|
|
3529
|
+
};
|
|
3530
|
+
if (result.kind === "client" && result.status >= 400 && result.status < 500) return {
|
|
3531
|
+
verdict: "unsupported",
|
|
3532
|
+
detail: message
|
|
3533
|
+
};
|
|
3534
|
+
return {
|
|
3535
|
+
verdict: "unknown",
|
|
3536
|
+
detail: message
|
|
3537
|
+
};
|
|
3538
|
+
}
|
|
3539
|
+
/** Read a probe response without letting a large or endless body stall. */
|
|
3540
|
+
async function readProbeOutput(response) {
|
|
3541
|
+
try {
|
|
3542
|
+
const text = await response.text();
|
|
3543
|
+
return { output: /"content"\s*:\s*"[^"]/u.test(text) || /"tool_calls"/u.test(text) };
|
|
3544
|
+
} catch (error) {
|
|
3545
|
+
return {
|
|
3546
|
+
output: false,
|
|
3547
|
+
error: error instanceof Error ? error : new Error(String(error))
|
|
3548
|
+
};
|
|
3549
|
+
}
|
|
3550
|
+
}
|
|
3551
|
+
/** The minimal chat call one probe makes. */
|
|
3552
|
+
function probeBody(model) {
|
|
3553
|
+
return JSON.stringify({
|
|
3554
|
+
model,
|
|
3555
|
+
messages: [{
|
|
3556
|
+
role: "user",
|
|
3557
|
+
content: "ping"
|
|
3558
|
+
}]
|
|
3559
|
+
});
|
|
3560
|
+
}
|
|
3561
|
+
/**
|
|
3562
|
+
* Probe one model through the bridge the plugin actually chats with, so the
|
|
3563
|
+
* recorded verdict describes the user's real path (same wire resolution, same
|
|
3564
|
+
* function stamping, same error translation).
|
|
3565
|
+
*/
|
|
3566
|
+
async function probeOneModel(chat, id, timeoutMs = TRAE_PROBE_TIMEOUT_MS) {
|
|
3567
|
+
const controller = new AbortController();
|
|
3568
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
3569
|
+
try {
|
|
3570
|
+
const result = await chat(probeBody(id), controller.signal);
|
|
3571
|
+
if (!result.ok) return {
|
|
3572
|
+
id,
|
|
3573
|
+
...classifyProbeResult(result, false)
|
|
3574
|
+
};
|
|
3575
|
+
const { output, error } = await readProbeOutput(result.response);
|
|
3576
|
+
if (error !== void 0) return {
|
|
3577
|
+
id,
|
|
3578
|
+
...classifyProbeError(error.message)
|
|
3579
|
+
};
|
|
3580
|
+
return {
|
|
3581
|
+
id,
|
|
3582
|
+
...classifyProbeResult(result, output)
|
|
3583
|
+
};
|
|
3584
|
+
} catch (error) {
|
|
3585
|
+
if (controller.signal.aborted) return {
|
|
3586
|
+
id,
|
|
3587
|
+
verdict: "unknown",
|
|
3588
|
+
detail: `no answer within ${Math.round(timeoutMs / 1e3)}s`
|
|
3589
|
+
};
|
|
3590
|
+
return {
|
|
3591
|
+
id,
|
|
3592
|
+
...classifyProbeError(error instanceof Error ? error.message : String(error))
|
|
3593
|
+
};
|
|
3594
|
+
} finally {
|
|
3595
|
+
clearTimeout(timer);
|
|
3596
|
+
}
|
|
3597
|
+
}
|
|
3598
|
+
/**
|
|
3599
|
+
* Probe a list of models ONE AT A TIME.
|
|
3600
|
+
*
|
|
3601
|
+
* Sequential on purpose: Trae throttles bursts, and a burst of parallel probes
|
|
3602
|
+
* would manufacture the very `rate_limited`/`unknown` verdicts this feature
|
|
3603
|
+
* must avoid. An authentication failure stops the run — every later probe would
|
|
3604
|
+
* fail the same way, and spending calls to learn that again is waste.
|
|
3605
|
+
*/
|
|
3606
|
+
async function probeModelsSequentially(chat, ids, onResult) {
|
|
3607
|
+
const results = [];
|
|
3608
|
+
for (const [index, id] of ids.entries()) {
|
|
3609
|
+
const result = await probeOneModel(chat, id);
|
|
3610
|
+
results.push(result);
|
|
3611
|
+
onResult?.(result, index);
|
|
3612
|
+
if (result.verdict === "auth") break;
|
|
3613
|
+
}
|
|
3614
|
+
return results;
|
|
3615
|
+
}
|
|
3616
|
+
//#endregion
|
|
3148
3617
|
//#region src/usage.ts
|
|
3149
3618
|
/**
|
|
3150
3619
|
* Read-only Trae usage/credits client.
|
|
@@ -3285,6 +3754,8 @@ var TraeUsageClient = class {
|
|
|
3285
3754
|
const fissionStart = asNumber(payload["solo_fission_start_time"]);
|
|
3286
3755
|
const fissionExpire = asNumber(payload["solo_fission_expire_time"]);
|
|
3287
3756
|
const fissionMax = asNumber(payload["solo_fission_max_usage"]);
|
|
3757
|
+
const payIdentity = asNumber(payload["user_pay_identity"]);
|
|
3758
|
+
const payIdentityStr = typeof payload["user_pay_identity_str"] === "string" && payload["user_pay_identity_str"].trim() !== "" ? payload["user_pay_identity_str"].trim() : void 0;
|
|
3288
3759
|
return {
|
|
3289
3760
|
isDollarUsageBilling: flag("is_dollar_usage_billing"),
|
|
3290
3761
|
hasPackage: flag("has_package"),
|
|
@@ -3295,6 +3766,8 @@ var TraeUsageClient = class {
|
|
|
3295
3766
|
enableSoloBuilder: flag("enable_solo_builder"),
|
|
3296
3767
|
enableSoloCoder: flag("enable_solo_coder"),
|
|
3297
3768
|
enableSoloWeb: flag("enable_solo_web"),
|
|
3769
|
+
...payIdentity === void 0 ? {} : { payIdentity },
|
|
3770
|
+
...payIdentityStr === void 0 ? {} : { payIdentityStr },
|
|
3298
3771
|
...fissionStart === void 0 || fissionExpire === void 0 || fissionMax === void 0 ? {} : { fission: {
|
|
3299
3772
|
startTimeMs: fissionStart,
|
|
3300
3773
|
expireTimeMs: fissionExpire,
|
|
@@ -3404,6 +3877,13 @@ var TraeUsageClient = class {
|
|
|
3404
3877
|
const TRAE_USAGE_PATH = "/plugins/dsh-connect-trae/usage";
|
|
3405
3878
|
/** Plugin-owned live model refresh endpoint. */
|
|
3406
3879
|
const TRAE_MODELS_REFRESH_PATH = "/plugins/dsh-connect-trae/models/refresh";
|
|
3880
|
+
/**
|
|
3881
|
+
* Plugin-owned availability probe: POST the model ids to test, get one verdict
|
|
3882
|
+
* per model. Unlike every read-only sibling this route SPENDS quota — a real
|
|
3883
|
+
* (minimal) chat call per model — so it is user-initiated only, and the card
|
|
3884
|
+
* labels the button as consuming a small amount.
|
|
3885
|
+
*/
|
|
3886
|
+
const TRAE_MODELS_TEST_PATH = "/plugins/dsh-connect-trae/models/test";
|
|
3407
3887
|
/** Plugin-owned local account rescan endpoint. */
|
|
3408
3888
|
const TRAE_ACCOUNTS_REFRESH_PATH = "/plugins/dsh-connect-trae/accounts/refresh";
|
|
3409
3889
|
/**
|
|
@@ -3568,6 +4048,39 @@ function json(res, status, body) {
|
|
|
3568
4048
|
});
|
|
3569
4049
|
res.end(payload);
|
|
3570
4050
|
}
|
|
4051
|
+
/**
|
|
4052
|
+
* Read the ids to test from a POST body.
|
|
4053
|
+
*
|
|
4054
|
+
* Only ids present in the region's live catalog are accepted downstream (see
|
|
4055
|
+
* index.ts), so this reader only has to produce a bounded, string-only list.
|
|
4056
|
+
*/
|
|
4057
|
+
async function readModelTestBody(req) {
|
|
4058
|
+
const chunks = [];
|
|
4059
|
+
let size = 0;
|
|
4060
|
+
for await (const chunk of req) {
|
|
4061
|
+
const buffer = chunk;
|
|
4062
|
+
size += buffer.byteLength;
|
|
4063
|
+
if (size > 65536) throw new Error("request body too large");
|
|
4064
|
+
chunks.push(buffer);
|
|
4065
|
+
}
|
|
4066
|
+
const text = Buffer.concat(chunks).toString("utf8").trim();
|
|
4067
|
+
if (text === "") return [];
|
|
4068
|
+
let parsed;
|
|
4069
|
+
try {
|
|
4070
|
+
parsed = JSON.parse(text);
|
|
4071
|
+
} catch {
|
|
4072
|
+
throw new Error("invalid JSON body");
|
|
4073
|
+
}
|
|
4074
|
+
const raw = typeof parsed === "object" && parsed !== null ? parsed["ids"] : void 0;
|
|
4075
|
+
if (!Array.isArray(raw)) throw new Error("body must carry an ids array");
|
|
4076
|
+
const ids = [];
|
|
4077
|
+
for (const value of raw) {
|
|
4078
|
+
if (typeof value !== "string") continue;
|
|
4079
|
+
const id = value.trim();
|
|
4080
|
+
if (id !== "" && !ids.includes(id)) ids.push(id);
|
|
4081
|
+
}
|
|
4082
|
+
return ids;
|
|
4083
|
+
}
|
|
3571
4084
|
/** Loopback browser origins only; other devices are refused until trusted origins exist. */
|
|
3572
4085
|
function loopbackOrigin(req) {
|
|
3573
4086
|
const origin = req.headers.origin;
|
|
@@ -3750,6 +4263,40 @@ function registerTraeUsageRoute(ctx, deps) {
|
|
|
3750
4263
|
}
|
|
3751
4264
|
});
|
|
3752
4265
|
/**
|
|
4266
|
+
* One-click availability test for the enabled models.
|
|
4267
|
+
*
|
|
4268
|
+
* This ROUTE COSTS QUOTA: each tested model gets a real, minimal chat call.
|
|
4269
|
+
* Every other plugin surface is read-only, so the guards here are tighter
|
|
4270
|
+
* than a GET's — POST only, loopback origin only, and the id list is taken
|
|
4271
|
+
* from the request body and validated against the region's live catalog, so
|
|
4272
|
+
* a caller cannot spend calls on arbitrary upstream config names. The count
|
|
4273
|
+
* is capped for the same reason.
|
|
4274
|
+
*/
|
|
4275
|
+
const disposeModelsTest = ctx.webServer.register({
|
|
4276
|
+
kind: "exact",
|
|
4277
|
+
path: TRAE_MODELS_TEST_PATH,
|
|
4278
|
+
handler: async (req, res) => {
|
|
4279
|
+
if (req.method !== "POST") return json(res, 405, { error: "method not allowed" });
|
|
4280
|
+
if (!loopbackOrigin(req)) return json(res, 403, { error: "origin-not-trusted" });
|
|
4281
|
+
if (deps.probeModels === void 0) return json(res, 503, { error: "model test unavailable" });
|
|
4282
|
+
const region = requestRegion(req, res);
|
|
4283
|
+
if (region === void 0) return;
|
|
4284
|
+
let ids;
|
|
4285
|
+
try {
|
|
4286
|
+
ids = await readModelTestBody(req);
|
|
4287
|
+
} catch (error) {
|
|
4288
|
+
return json(res, 400, { error: safeMessage(error) });
|
|
4289
|
+
}
|
|
4290
|
+
if (ids.length === 0) return json(res, 400, { error: "no models to test" });
|
|
4291
|
+
if (ids.length > 64) return json(res, 400, { error: `too many models (limit ${String(64)})` });
|
|
4292
|
+
try {
|
|
4293
|
+
json(res, 200, await deps.probeModels(region, ids));
|
|
4294
|
+
} catch (error) {
|
|
4295
|
+
json(res, 500, { error: safeMessage(error) });
|
|
4296
|
+
}
|
|
4297
|
+
}
|
|
4298
|
+
});
|
|
4299
|
+
/**
|
|
3753
4300
|
* Daily check-in claim — the ONLY route in this plugin that mutates
|
|
3754
4301
|
* upstream account state, so it is guarded more tightly than its siblings:
|
|
3755
4302
|
* POST only, loopback origin only, and the status read runs FIRST so a day
|
|
@@ -3815,6 +4362,7 @@ function registerTraeUsageRoute(ctx, deps) {
|
|
|
3815
4362
|
return () => {
|
|
3816
4363
|
disposeCheckin();
|
|
3817
4364
|
disposeRefresh();
|
|
4365
|
+
disposeModelsTest();
|
|
3818
4366
|
disposeAccounts();
|
|
3819
4367
|
disposeUsage();
|
|
3820
4368
|
};
|
|
@@ -4332,6 +4880,32 @@ function apply(ctx, config) {
|
|
|
4332
4880
|
}
|
|
4333
4881
|
return merged;
|
|
4334
4882
|
},
|
|
4883
|
+
/**
|
|
4884
|
+
* One-click availability test. The ids come from the card, so each one is
|
|
4885
|
+
* checked against THIS region's live catalog first: a caller must not be
|
|
4886
|
+
* able to spend quota probing arbitrary upstream config names, and a row the
|
|
4887
|
+
* region does not know is reported as skipped rather than silently dropped.
|
|
4888
|
+
*
|
|
4889
|
+
* The probe goes through `upstream` — the same bridge real chats use — so
|
|
4890
|
+
* wire resolution, function stamping and error translation all match what
|
|
4891
|
+
* the user gets when they actually send a message.
|
|
4892
|
+
*/
|
|
4893
|
+
probeModels: async (ids) => {
|
|
4894
|
+
const known = /* @__PURE__ */ new Set();
|
|
4895
|
+
for (const model of [...catalog.current(), ...displayModels(current(), region)]) {
|
|
4896
|
+
known.add(model.id.trim().toLowerCase());
|
|
4897
|
+
known.add(model.name.trim().toLowerCase());
|
|
4898
|
+
}
|
|
4899
|
+
const testable = [];
|
|
4900
|
+
const skipped = [];
|
|
4901
|
+
for (const id of ids) if (known.has(id.trim().toLowerCase())) testable.push(id);
|
|
4902
|
+
else skipped.push(id);
|
|
4903
|
+
const results = await probeModelsSequentially((body, signal) => upstream.chatStream(body, signal), testable);
|
|
4904
|
+
return skipped.length === 0 ? { results } : {
|
|
4905
|
+
results,
|
|
4906
|
+
skipped
|
|
4907
|
+
};
|
|
4908
|
+
},
|
|
4335
4909
|
invalidateAdapter: () => {},
|
|
4336
4910
|
rawDiagnostic: () => rawDiagnostic()
|
|
4337
4911
|
};
|
|
@@ -4343,6 +4917,7 @@ function apply(ctx, config) {
|
|
|
4343
4917
|
enabledModelIds: (region) => regionStateOf(current(), region).enabledModelIds ?? [],
|
|
4344
4918
|
regionEnabled: (region) => regionEnabled(current(), region),
|
|
4345
4919
|
discoverModels: (region, signal) => stacks[region].discoverModels(signal),
|
|
4920
|
+
probeModels: (region, ids) => stacks[region].probeModels(ids),
|
|
4346
4921
|
rawDiagnostic: (region) => stacks[region].rawDiagnostic()
|
|
4347
4922
|
}));
|
|
4348
4923
|
/**
|
|
@@ -4487,4 +5062,4 @@ function apply(ctx, config) {
|
|
|
4487
5062
|
});
|
|
4488
5063
|
}
|
|
4489
5064
|
//#endregion
|
|
4490
|
-
export { Config, FALLBACK_TRAE_MODELS, FALLBACK_TRAE_MODELS_AI, REGION_GATEWAYS, SseDecoder, TRAE_AI_PROVIDER, TRAE_CHECKIN_PATH, TRAE_CN_AGENT_TASK_PATH, TRAE_CN_TITLE_PATH, TRAE_MODEL_DETAIL_FUNCTIONS, TRAE_MODEL_DETAIL_PATH, TRAE_PAY_BASE, TRAE_PROVIDER, TRAE_PROVIDERS, TRAE_PROVIDER_DISPLAY_NAMES, TRAE_RAW_CHAT_V1_PATH, TRAE_RAW_CHAT_V2_PATH, TRAE_REASONING_EFFORTS, TRAE_REGIONS, TRAE_REGION_PARAM, TRAE_SETTINGS_NS, TRAE_SOLO_CHAT_PATH, TRAE_SOLO_FUNCTION, TRAE_SOLO_MODELS_PATH, TRAE_SOLO_REMOTE_BASE, TRAE_STATE_DB_FILENAME, TRAE_STREAM_IDLE_TIMEOUT_MS, TRAE_USAGE_PATH, TRAE_VERSION_CODE_FALLBACK, TraeCatalog, TraeCredentialStore, TraeDelegatingUpstreamClient, TraeFallbackUpstreamClient, TraeGatedUpstreamClient, TraeRawCapabilityController, TraeRawCapabilityState, TraeRawChatUpstreamClient, TraeSoloBridge, TraeSoloRemoteCatalogClient, TraeSoloUpstreamClient, TraeUsageClient, UnconfiguredTraeUpstreamClient, apply, applyContextBudgets, applyImageSelection, applyReasoningEffort, asVolatile, bridgeTraeSoloStream, buildTraeAgentTaskBody, buildTraeCnHeaders, buildTraeFusionRawChatEnvelope, buildTraeModelDetailRequest, buildTraeRawChatDraft, buildTraeRawChatRuntimeConfig, classifyTraeRawChatFailure, createTraeAdapter, createTraeRawGateway, createTraeShim, decodeRawChatChunk, decodeTraeEvent, decryptTraeStorageValue, deriveCatalog, describeIdShape, describeNameShape, discoveredCatalog, fallbackModelsFor, hashTraeRawChatArg, identityHeaders, inject, legacyTraeOwnAuthPath, maskUserPath, mergeTraeModelSources, name, nextRegionEnabled, nextRegionSlots, normalizeTraeCredential, normalizeTraeVersionCode, parseObservedModelConfig, parseReasoningCapability, parseTraeAuthValue, parseTraeCachedModel, parseTraeModelExtraConfigLogLine, parseTraeRawChatBehaviorConfig, parseTraeRemoteModel, parseTraeStorageDocument, pickTraeStorageIdentity, prepareSoloBody, probeTraeRawChatCapability, rawCapabilityDiagnostic, rawCapabilityFingerprint, readTraeCachedModel, readTraeCliIdentity, readTraeIdentity, refreshTraeCredential, regionEnabled, regionEnabledOf, regionOfCredential, regionOfEdition, regionOfHost, regionOfTraeProvider, regionOfTraeStatusUrl, regionOfUserRegion, regionStateOf, registerTraeUsageRoute, resolveTraeIdentity, resolveTraeRawRuntime, sanitizeCatalog, settingsNamespaceOf, traeEndpoint, traeInputModalities, traeModelDisplayName, traeOwnAuthPath, traeRawChatExtraInfo, traeStateDatabaseCandidates, traeStorageCandidates, traeWebUsage, traeWindowsAppNames, unwrapVolatile, unwrapVolatileDeep, withTraeRegion };
|
|
5065
|
+
export { Config, FALLBACK_TRAE_MODELS, FALLBACK_TRAE_MODELS_AI, REGION_GATEWAYS, SseDecoder, TRAE_AI_PROVIDER, TRAE_CHECKIN_PATH, TRAE_CN_AGENT_TASK_PATH, TRAE_CN_TITLE_PATH, TRAE_MODELS_TEST_PATH, TRAE_MODEL_DETAIL_FUNCTIONS, TRAE_MODEL_DETAIL_PATH, TRAE_PAY_BASE, TRAE_PROBE_TIMEOUT_MS, TRAE_PROVIDER, TRAE_PROVIDERS, TRAE_PROVIDER_DISPLAY_NAMES, TRAE_RAW_CHAT_V1_PATH, TRAE_RAW_CHAT_V2_PATH, TRAE_REASONING_EFFORTS, TRAE_REGIONS, TRAE_REGION_PARAM, TRAE_SETTINGS_NS, TRAE_SOLO_CHAT_PATH, TRAE_SOLO_FUNCTION, TRAE_SOLO_MODELS_PATH, TRAE_SOLO_REMOTE_BASE, TRAE_STATE_DB_FILENAME, TRAE_STREAM_IDLE_TIMEOUT_MS, TRAE_USAGE_PATH, TRAE_VERSION_CODE_FALLBACK, TraeCatalog, TraeCredentialStore, TraeDelegatingUpstreamClient, TraeFallbackUpstreamClient, TraeGatedUpstreamClient, TraeRawCapabilityController, TraeRawCapabilityState, TraeRawChatUpstreamClient, TraeSoloBridge, TraeSoloRemoteCatalogClient, TraeSoloUpstreamClient, TraeUsageClient, UnconfiguredTraeUpstreamClient, apply, applyContextBudgets, applyImageSelection, applyReasoningEffort, asVolatile, bridgeTraeSoloStream, buildTraeAgentTaskBody, buildTraeCnHeaders, buildTraeFusionRawChatEnvelope, buildTraeModelDetailRequest, buildTraeRawChatDraft, buildTraeRawChatRuntimeConfig, classifyProbeError, classifyProbeResult, classifyTraeRawChatFailure, createTraeAdapter, createTraeRawGateway, createTraeShim, decodeRawChatChunk, decodeTraeEvent, decryptTraeStorageValue, deriveCatalog, describeIdShape, describeNameShape, discoveredCatalog, fallbackModelsFor, hashTraeRawChatArg, identityHeaders, inject, legacyTraeOwnAuthPath, maskUserPath, mergeTraeModelSources, name, nextRegionEnabled, nextRegionSlots, normalizeTraeCredential, normalizeTraeVersionCode, parseObservedModelConfig, parseReasoningCapability, parseTraeAuthValue, parseTraeCachedModel, parseTraeModelExtraConfigLogLine, parseTraeRawChatBehaviorConfig, parseTraeRemoteModel, parseTraeStorageDocument, pickTraeStorageIdentity, prepareSoloBody, probeBody, probeModelsSequentially, probeOneModel, probeTraeRawChatCapability, rawCapabilityDiagnostic, rawCapabilityFingerprint, readTraeCachedModel, readTraeCliIdentity, readTraeIdentity, refreshTraeCredential, regionEnabled, regionEnabledOf, regionOfCredential, regionOfEdition, regionOfHost, regionOfTraeProvider, regionOfTraeStatusUrl, regionOfUserRegion, regionStateOf, registerTraeUsageRoute, resolveTraeIdentity, resolveTraeRawRuntime, sanitizeCatalog, settingsNamespaceOf, traeEndpoint, traeInputModalities, traeModelDisplayName, traeOwnAuthPath, traeRawChatExtraInfo, traeStateDatabaseCandidates, traeStorageCandidates, traeWebUsage, traeWindowsAppNames, unwrapVolatile, unwrapVolatileDeep, withTraeRegion };
|