@oh-my-pi/pi-ai 16.1.8 → 16.1.10
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 +43 -0
- package/README.md +0 -1
- package/dist/types/registry/llama-cpp.d.ts +8 -0
- package/dist/types/registry/oauth/wafer.d.ts +0 -1
- package/dist/types/registry/registry.d.ts +5 -4
- package/dist/types/utils/json-parse.d.ts +16 -5
- package/dist/types/utils/request-debug.d.ts +0 -3
- package/package.json +4 -5
- package/src/auth-gateway/server.ts +6 -4
- package/src/providers/cursor.ts +5 -8
- package/src/providers/openai-codex-responses.ts +66 -1
- package/src/providers/openai-shared.ts +10 -0
- package/src/providers/transform-messages.ts +247 -186
- package/src/rate-limit-utils.ts +8 -2
- package/src/registry/llama-cpp.ts +34 -0
- package/src/registry/oauth/wafer.ts +4 -30
- package/src/registry/registry.ts +2 -2
- package/src/stream.ts +0 -1
- package/src/utils/json-parse.ts +397 -21
- package/src/utils/request-debug.ts +1 -34
- package/src/utils/schema/wire.ts +68 -0
- package/src/utils/validation.ts +129 -2
- package/dist/types/registry/wafer-pass.d.ts +0 -6
- package/src/registry/wafer-pass.ts +0 -12
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,49 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [16.1.10] - 2026-06-21
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- Improved JSON robustness by replacing external dependency with a custom, high-performance parser
|
|
10
|
+
- Strengthened streaming JSON parsing to prevent non-finite numbers from surfacing as `undefined/NaN`
|
|
11
|
+
- Configured JSON parser to reject JS-specific `NaN` and `Infinity` values for tool arguments
|
|
12
|
+
- Replaced the JSON repair/parse helpers (`parseJsonWithRepair`, `parseStreamingJson`) with a single from-scratch tolerant parser (`RelaxedJson`) that accepts single-quoted strings, unquoted object keys, trailing/stray commas, `//` and `/* */` comments, Python `True`/`False`/`None`, raw control characters, invalid escapes, and unescaped apostrophes (`'it's'`). Final parsing still throws on truncated/garbage input (so a malformed tool call is skipped rather than executed with half-formed args) and rejects JS-only `NaN`/`Infinity`; streaming parsing stays non-throwing and rolls back incomplete trailing tokens instead of surfacing `undefined`/`NaN`. The Cursor provider's ad-hoc regex + JSON5 tool-argument parser now routes through the shared parser.
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- Fixed tool call ID normalization for Anthropic-compatible models
|
|
17
|
+
- Fixed Anthropic Messages replay sanitizing malformed tool-call IDs, including aborted native tool calls with empty IDs, so retries no longer send invalid `tool_use.id` / `tool_result.tool_use_id` pairs.
|
|
18
|
+
- Fixed the Codex Responses WebSocket transport attributing a prior turn's output to the current one on a reused connection: a trailing/duplicate frame from a cleanly-completed previous response that slipped past the queue drain could be consumed as this request's terminal (ending the turn with empty output) or as a stale tool call. Frames are now keyed by `response.id` — a frame carrying the previous response's id is dropped, and one carrying a third id (or a regressed `sequence_number`) fails closed so the turn retries instead of mixing two responses' streams. Idless frames (deltas, the rate-limit/metadata preamble, `response.created`-less streams) still pass through, matching upstream codex-rs.
|
|
19
|
+
- Fixed `transformMessages` pulling an earlier, orphaned tool result onto a later tool call that reused the same id (left behind when compaction folded the originating `tool_use` into a summary). The pending-call flush now pairs each call with a result positioned *after* its assistant turn, so a reused id surfaces its own output rather than a prior turn's.
|
|
20
|
+
- Fixed DashScope 429 rate-limit messages that mention authorization being classified as credential failures, preventing valid API keys from being invalidated after throttling. ([#3172](https://github.com/can1357/oh-my-pi/issues/3172))
|
|
21
|
+
- Fixed OpenCode Go `401 Insufficient balance` quota errors being treated as unknown failures instead of usage-limit errors, restoring credential rotation and fallback chains. ([#3169](https://github.com/can1357/oh-my-pi/issues/3169))
|
|
22
|
+
|
|
23
|
+
### Removed
|
|
24
|
+
|
|
25
|
+
- Removed the `partial-json` dependency; streaming JSON parsing now uses the in-house `RelaxedJson` parser.
|
|
26
|
+
|
|
27
|
+
## [16.1.9] - 2026-06-21
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
|
|
31
|
+
- Added `llama.cpp` to the interactive `/login` provider list, accepting an optional API key while defaulting to local no-auth mode.
|
|
32
|
+
|
|
33
|
+
### Changed
|
|
34
|
+
|
|
35
|
+
- Optimized generated AI tool schemas by collapsing verbose `anyOf` unions into standard `enum` types
|
|
36
|
+
|
|
37
|
+
### Fixed
|
|
38
|
+
|
|
39
|
+
- Fixed tool-call argument validation dropping nested keys that were accidentally double-encoded
|
|
40
|
+
- Fixed the `moonshot` provider being locked to the international Kimi host (`api.moonshot.ai`): OpenAI-completions requests now honor a `MOONSHOT_BASE_URL` override so users can reach the Kimi China platform (`api.moonshot.cn`), which rejects keys issued for the international endpoint. ([#2883](https://github.com/can1357/oh-my-pi/issues/2883))
|
|
41
|
+
- Fixed tool-call argument validation dropping fields whose object keys were accidentally JSON-encoded a second time (e.g. `{ "\"op\"": "done" }`), which surfaced as spurious missing-required errors. A schema-agnostic pre-validation pass now recursively unwraps such double-encoded keys — through arrays and nested objects, and again after a JSON-string container is parsed — before the unrecognized-key repair can delete them.
|
|
42
|
+
|
|
43
|
+
### Removed
|
|
44
|
+
|
|
45
|
+
- Removed the `setNextRequestDebugPath`, `clearNextRequestDebugPath`, and `getNextRequestDebugPath` utility functions for request debugging, as request/response recording now relies exclusively on the `PI_REQ_DEBUG` environment variable.
|
|
46
|
+
- Removed Wafer Pass (`wafer-pass`) login support; Wafer Serverless remains available as `wafer-serverless`.
|
|
47
|
+
|
|
5
48
|
## [16.1.8] - 2026-06-20
|
|
6
49
|
|
|
7
50
|
### Changed
|
package/README.md
CHANGED
|
@@ -62,7 +62,6 @@ Unified LLM API with automatic model discovery, provider configuration, token an
|
|
|
62
62
|
- **Hugging Face Inference**
|
|
63
63
|
- **xAI**
|
|
64
64
|
- **Venice** (requires `VENICE_API_KEY`)
|
|
65
|
-
- **Wafer Pass** (requires `WAFER_PASS_API_KEY`; flat-rate subscription, includes GLM-5.1 and Qwen3.5-397B-A17B)
|
|
66
65
|
- **Wafer Serverless** (requires `WAFER_SERVERLESS_API_KEY`; pay-as-you-go)
|
|
67
66
|
- **OpenRouter**
|
|
68
67
|
- **Kilo Gateway** (supports OAuth `/login kilo` or `KILO_API_KEY`)
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { OAuthController, OAuthLoginCallbacks } from "./oauth/types";
|
|
2
|
+
export declare function loginLlamaCpp(options: OAuthController): Promise<string>;
|
|
3
|
+
export declare const llamaCppProvider: {
|
|
4
|
+
readonly id: "llama.cpp";
|
|
5
|
+
readonly name: "llama.cpp (Local OpenAI-compatible)";
|
|
6
|
+
readonly envKeys: "LLAMA_CPP_API_KEY";
|
|
7
|
+
readonly login: (cb: OAuthLoginCallbacks) => Promise<string>;
|
|
8
|
+
};
|
|
@@ -112,6 +112,11 @@ declare const ALL: ({
|
|
|
112
112
|
readonly id: "litellm";
|
|
113
113
|
readonly name: "LiteLLM";
|
|
114
114
|
readonly login: (cb: import("./oauth").OAuthLoginCallbacks) => Promise<string>;
|
|
115
|
+
} | {
|
|
116
|
+
readonly id: "llama.cpp";
|
|
117
|
+
readonly name: "llama.cpp (Local OpenAI-compatible)";
|
|
118
|
+
readonly envKeys: "LLAMA_CPP_API_KEY";
|
|
119
|
+
readonly login: (cb: import("./oauth").OAuthLoginCallbacks) => Promise<string>;
|
|
115
120
|
} | {
|
|
116
121
|
readonly id: "lm-studio";
|
|
117
122
|
readonly name: "LM Studio (Local OpenAI-compatible)";
|
|
@@ -226,10 +231,6 @@ declare const ALL: ({
|
|
|
226
231
|
readonly id: "vllm";
|
|
227
232
|
readonly name: "vLLM (Local OpenAI-compatible)";
|
|
228
233
|
readonly login: (cb: import("./oauth").OAuthLoginCallbacks) => Promise<string>;
|
|
229
|
-
} | {
|
|
230
|
-
readonly id: "wafer-pass";
|
|
231
|
-
readonly name: "Wafer Pass (flat-rate subscription)";
|
|
232
|
-
readonly login: (cb: import("./oauth").OAuthLoginCallbacks) => Promise<string>;
|
|
233
234
|
} | {
|
|
234
235
|
readonly id: "wafer-serverless";
|
|
235
236
|
readonly name: "Wafer Serverless (pay-as-you-go)";
|
|
@@ -1,11 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight string-level repair of the escape/control-char hazards that make
|
|
3
|
+
* otherwise-valid JSON fail `JSON.parse`: raw control characters inside strings
|
|
4
|
+
* are escaped, and invalid `\x` escapes have their backslash escaped. Returns the
|
|
5
|
+
* input unchanged when no repair is needed. Pure string→string; does not parse.
|
|
6
|
+
*/
|
|
1
7
|
export declare function repairJson(json: string): string;
|
|
8
|
+
/**
|
|
9
|
+
* Final-parse a JSON value, repairing the common LLM malformations
|
|
10
|
+
* ({@link RelaxedJson}). Tries strict `JSON.parse` first (fast path, exact JSON
|
|
11
|
+
* semantics), then the relaxed parser. Throws when the input is unrepairable,
|
|
12
|
+
* truncated, or carries trailing garbage — so callers can skip a bad tool call
|
|
13
|
+
* rather than execute a half-formed one.
|
|
14
|
+
*/
|
|
2
15
|
export declare function parseJsonWithRepair<T>(json: string): T;
|
|
3
16
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* @param partialJson The partial JSON string from streaming
|
|
8
|
-
* @returns Parsed object or empty object if parsing fails
|
|
17
|
+
* Parse possibly-incomplete JSON during streaming. Always returns a value, never
|
|
18
|
+
* throws: `{}` for empty/whitespace/unrecoverable buffers, and an auto-closed
|
|
19
|
+
* best-effort object for truncated ones.
|
|
9
20
|
*/
|
|
10
21
|
export declare function parseStreamingJson<T = Record<string, unknown>>(partialJson: string | undefined): T;
|
|
11
22
|
/**
|
|
@@ -22,9 +22,6 @@ export interface RequestDebugSession {
|
|
|
22
22
|
wrapResponse(response: Response): Promise<Response>;
|
|
23
23
|
}
|
|
24
24
|
export declare function isRequestDebugEnabled(): boolean;
|
|
25
|
-
export declare function setNextRequestDebugPath(requestPath: string): void;
|
|
26
|
-
export declare function clearNextRequestDebugPath(): void;
|
|
27
|
-
export declare function getNextRequestDebugPath(): string | undefined;
|
|
28
25
|
export declare function wrapFetchForRequestDebug(fetchImpl: FetchImpl): FetchImpl;
|
|
29
26
|
export declare function withRequestDebugFetch<T extends {
|
|
30
27
|
fetch?: FetchImpl;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-ai",
|
|
4
|
-
"version": "16.1.
|
|
4
|
+
"version": "16.1.10",
|
|
5
5
|
"description": "Unified LLM API with automatic model discovery and provider configuration",
|
|
6
6
|
"homepage": "https://omp.sh",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -38,11 +38,10 @@
|
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"@bufbuild/protobuf": "^2.12.0",
|
|
41
|
-
"@oh-my-pi/pi-catalog": "16.1.
|
|
42
|
-
"@oh-my-pi/pi-utils": "16.1.
|
|
43
|
-
"@oh-my-pi/pi-wire": "16.1.
|
|
41
|
+
"@oh-my-pi/pi-catalog": "16.1.10",
|
|
42
|
+
"@oh-my-pi/pi-utils": "16.1.10",
|
|
43
|
+
"@oh-my-pi/pi-wire": "16.1.10",
|
|
44
44
|
"arktype": "^2.2.0",
|
|
45
|
-
"partial-json": "^0.1.7",
|
|
46
45
|
"zod": "^4"
|
|
47
46
|
},
|
|
48
47
|
"devDependencies": {
|
|
@@ -229,11 +229,10 @@ export function classifyGatewayError(err: unknown): { status: number; type: stri
|
|
|
229
229
|
if (/\baborted\b|\babort signal\b/i.test(message)) {
|
|
230
230
|
return { status: 499, type: "request_aborted", message };
|
|
231
231
|
}
|
|
232
|
-
if (/\b(?:unauthorized|forbidden)\b/i.test(message)) {
|
|
233
|
-
return { status: 401, type: "authentication_error", message };
|
|
234
|
-
}
|
|
235
232
|
if (
|
|
236
|
-
// Match rate-limit phrasings
|
|
233
|
+
// Match rate-limit phrasings before auth wording: some providers
|
|
234
|
+
// describe throttling as "unauthorized due to rate limit".
|
|
235
|
+
// Keep boundaries so this does not collide with
|
|
237
236
|
// `GenerateContentRequest`, `accelerate`, `iterate`, `deprecated`, etc.
|
|
238
237
|
/\brate[- _]?limit(?:s|ed|ing)?\b|\bquota(?:_exceeded| exceeded)?\b|\btoo[- _]many[- _]requests\b/i.test(
|
|
239
238
|
message,
|
|
@@ -249,6 +248,9 @@ export function classifyGatewayError(err: unknown): { status: number; type: stri
|
|
|
249
248
|
) {
|
|
250
249
|
return { status: 429, type: "rate_limit_error", message };
|
|
251
250
|
}
|
|
251
|
+
if (/\b(?:unauthorized|forbidden)\b/i.test(message)) {
|
|
252
|
+
return { status: 401, type: "authentication_error", message };
|
|
253
|
+
}
|
|
252
254
|
if (/\b(?:unsupported|invalid_request|invalid request|bad request|malformed)\b/i.test(message)) {
|
|
253
255
|
return { status: 400, type: "invalid_request_error", message };
|
|
254
256
|
}
|
package/src/providers/cursor.ts
CHANGED
|
@@ -125,7 +125,7 @@ import type {
|
|
|
125
125
|
} from "../types";
|
|
126
126
|
import { normalizeSystemPrompts } from "../utils";
|
|
127
127
|
import { AssistantMessageEventStream } from "../utils/event-stream";
|
|
128
|
-
import { parseStreamingJson } from "../utils/json-parse";
|
|
128
|
+
import { parseJsonWithRepair, parseStreamingJson } from "../utils/json-parse";
|
|
129
129
|
import { createRequestDebugSession, isRequestDebugEnabled, type RequestDebugResponseLog } from "../utils/request-debug";
|
|
130
130
|
import { formatErrorMessageWithRetryAfter } from "../utils/retry-after";
|
|
131
131
|
import { toolWireSchema } from "../utils/schema/wire";
|
|
@@ -1781,13 +1781,10 @@ function parseToolArgsJson(text: string): unknown {
|
|
|
1781
1781
|
return text;
|
|
1782
1782
|
}
|
|
1783
1783
|
try {
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
return Bun.JSON5.parse(normalized);
|
|
1789
|
-
} catch {}
|
|
1790
|
-
return text;
|
|
1784
|
+
return parseJsonWithRepair<unknown>(trimmed);
|
|
1785
|
+
} catch {
|
|
1786
|
+
return text;
|
|
1787
|
+
}
|
|
1791
1788
|
}
|
|
1792
1789
|
|
|
1793
1790
|
function decodeMcpArgValue(value: Uint8Array): unknown {
|
|
@@ -205,6 +205,17 @@ function isCodexStreamProgressEvent(event: unknown): boolean {
|
|
|
205
205
|
return typeof type === "string" && CODEX_ADDITIONAL_PROGRESS_EVENT_TYPES.has(type);
|
|
206
206
|
}
|
|
207
207
|
|
|
208
|
+
function extractCodexFrameResponseId(frame: Record<string, unknown>): string | undefined {
|
|
209
|
+
const response = (frame as { response?: { id?: unknown } }).response;
|
|
210
|
+
const id = response?.id;
|
|
211
|
+
return typeof id === "string" && id.length > 0 ? id : undefined;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function extractCodexFrameSequenceNumber(frame: Record<string, unknown>): number | undefined {
|
|
215
|
+
const raw = (frame as { sequence_number?: unknown }).sequence_number;
|
|
216
|
+
return typeof raw === "number" && Number.isFinite(raw) ? Math.trunc(raw) : undefined;
|
|
217
|
+
}
|
|
218
|
+
|
|
208
219
|
type CodexWebSocketTimeoutDetails = {
|
|
209
220
|
lastEventAt: number;
|
|
210
221
|
lastEventType?: string;
|
|
@@ -2403,6 +2414,12 @@ class CodexWebSocketConnection {
|
|
|
2403
2414
|
#lastInboundAt = 0;
|
|
2404
2415
|
/** Wall-clock of the last heartbeat ping we issued; 0 if none yet. */
|
|
2405
2416
|
#lastPingAt = 0;
|
|
2417
|
+
/**
|
|
2418
|
+
* Most recent `response.id` accepted on this socket, retained across
|
|
2419
|
+
* requests. Lets the next request drop a trailing/duplicate frame from the
|
|
2420
|
+
* previous (cleanly-completed) response that outlived the queue drain.
|
|
2421
|
+
*/
|
|
2422
|
+
#lastSeenResponseId?: string;
|
|
2406
2423
|
|
|
2407
2424
|
constructor(url: string, headers: Record<string, string>, options: CodexWebSocketConnectionOptions) {
|
|
2408
2425
|
this.#url = url;
|
|
@@ -2650,6 +2667,11 @@ class CodexWebSocketConnection {
|
|
|
2650
2667
|
let lastProgressEventType: string | undefined;
|
|
2651
2668
|
let lastEventAt = lastProgressAt;
|
|
2652
2669
|
let lastEventType: string | undefined;
|
|
2670
|
+
// Cross-request frame guard: lock onto this response's id and reject
|
|
2671
|
+
// frames belonging to another response interleaved on the reused socket.
|
|
2672
|
+
let activeResponseId: string | undefined;
|
|
2673
|
+
let lastSequence: number | undefined;
|
|
2674
|
+
const priorResponseId = this.#lastSeenResponseId;
|
|
2653
2675
|
while (true) {
|
|
2654
2676
|
let timeoutMs: number | undefined;
|
|
2655
2677
|
let timeoutReason: string;
|
|
@@ -2691,8 +2713,51 @@ class CodexWebSocketConnection {
|
|
|
2691
2713
|
if (next === null) {
|
|
2692
2714
|
throw new CodexWebSocketTransportError(`websocket closed before response completion`);
|
|
2693
2715
|
}
|
|
2694
|
-
sawFirstEvent = true;
|
|
2695
2716
|
const eventType = typeof next.type === "string" ? next.type : "";
|
|
2717
|
+
// Cross-request frame guard. The socket is reused across turns. Upstream
|
|
2718
|
+
// codex-rs leans on the protocol guarantee that nothing follows a
|
|
2719
|
+
// response's terminal event, but our queue can still surface a trailing
|
|
2720
|
+
// or duplicate frame from a cleanly-completed prior response after
|
|
2721
|
+
// #dropStaleFrames() drained the queue at send time. Attaching such a
|
|
2722
|
+
// frame to THIS turn misattributes an earlier turn's output (a stale
|
|
2723
|
+
// `response.completed` ends the turn early; a stale item makes the model
|
|
2724
|
+
// see an unrelated call). Only lifecycle events (created/completed/
|
|
2725
|
+
// failed/incomplete) carry a `response.id` — exactly the harmful ones —
|
|
2726
|
+
// so key the guard on it and let idless frames (deltas, the rate-limit/
|
|
2727
|
+
// metadata preamble, created-less streams) pass through, matching
|
|
2728
|
+
// upstream rather than gating on `response.created`.
|
|
2729
|
+
const frameResponseId = extractCodexFrameResponseId(next);
|
|
2730
|
+
const frameSequence = extractCodexFrameSequenceNumber(next);
|
|
2731
|
+
if (frameResponseId !== undefined) {
|
|
2732
|
+
if (activeResponseId === undefined) {
|
|
2733
|
+
if (priorResponseId !== undefined && frameResponseId === priorResponseId) {
|
|
2734
|
+
// Trailing/duplicate frame of the previous response that
|
|
2735
|
+
// outlived the drain. Drop without locking or advancing the
|
|
2736
|
+
// first-event clocks so our own response can still start.
|
|
2737
|
+
continue;
|
|
2738
|
+
}
|
|
2739
|
+
activeResponseId = frameResponseId;
|
|
2740
|
+
} else if (frameResponseId !== activeResponseId) {
|
|
2741
|
+
// A different response is interleaving on the socket; the idless
|
|
2742
|
+
// deltas that follow are indistinguishable, so fail closed
|
|
2743
|
+
// (retryable) instead of risking misattribution.
|
|
2744
|
+
this.close("stale-frame");
|
|
2745
|
+
throw new CodexWebSocketTransportError(
|
|
2746
|
+
`websocket frame for response ${frameResponseId} interleaved into active response ${activeResponseId}`,
|
|
2747
|
+
);
|
|
2748
|
+
}
|
|
2749
|
+
this.#lastSeenResponseId = frameResponseId;
|
|
2750
|
+
}
|
|
2751
|
+
if (frameSequence !== undefined) {
|
|
2752
|
+
if (activeResponseId !== undefined && lastSequence !== undefined && frameSequence < lastSequence) {
|
|
2753
|
+
this.close("stale-frame");
|
|
2754
|
+
throw new CodexWebSocketTransportError(
|
|
2755
|
+
`websocket sequence_number ${frameSequence} regressed below ${lastSequence} within response ${activeResponseId}`,
|
|
2756
|
+
);
|
|
2757
|
+
}
|
|
2758
|
+
lastSequence = frameSequence;
|
|
2759
|
+
}
|
|
2760
|
+
sawFirstEvent = true;
|
|
2696
2761
|
lastEventAt = Date.now();
|
|
2697
2762
|
lastEventType = eventType || undefined;
|
|
2698
2763
|
if (isCodexStreamProgressEvent(next)) {
|
|
@@ -155,6 +155,16 @@ export function resolveOpenAIRequestSetup(
|
|
|
155
155
|
|
|
156
156
|
let copilotPremiumRequests: number | undefined;
|
|
157
157
|
let baseUrl = model.baseUrl;
|
|
158
|
+
if (model.provider === "moonshot") {
|
|
159
|
+
// Bundled `moonshot` catalog models hardcode the international endpoint
|
|
160
|
+
// (`api.moonshot.ai`). MOONSHOT_BASE_URL lets users redirect the provider
|
|
161
|
+
// at the China platform (`api.moonshot.cn`), which only accepts China keys
|
|
162
|
+
// and rejects the international host. (#2883)
|
|
163
|
+
const moonshotBaseUrl = $env.MOONSHOT_BASE_URL?.trim();
|
|
164
|
+
if (moonshotBaseUrl) {
|
|
165
|
+
baseUrl = moonshotBaseUrl;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
158
168
|
if (model.provider === "github-copilot") {
|
|
159
169
|
apiKey = parseGitHubCopilotApiKey(rawApiKey).accessToken;
|
|
160
170
|
const copilot = buildCopilotDynamicHeaders({
|