pi-provider-qoder-cn 0.2.8 → 0.2.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +368 -346
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1475,7 +1475,8 @@ __export(oauth_exports, {
|
|
|
1475
1475
|
getQoderPatForMode: () => getQoderPatForMode,
|
|
1476
1476
|
loginQoderForMode: () => loginQoderForMode,
|
|
1477
1477
|
refreshQoderTokenForMode: () => refreshQoderTokenForMode,
|
|
1478
|
-
resolveQoderIdentity: () => resolveQoderIdentity
|
|
1478
|
+
resolveQoderIdentity: () => resolveQoderIdentity,
|
|
1479
|
+
saveCredentialsToAuthFile: () => saveCredentialsToAuthFile
|
|
1479
1480
|
});
|
|
1480
1481
|
import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "node:fs";
|
|
1481
1482
|
import { homedir as homedir4 } from "node:os";
|
|
@@ -1631,14 +1632,16 @@ async function refreshQoderTokenForMode(credentials, mode) {
|
|
|
1631
1632
|
updateQoderModelsCache(qCreds.access, qCreds.userID, qCreds.name, qCreds.email, mode).catch(() => {
|
|
1632
1633
|
});
|
|
1633
1634
|
return refreshed;
|
|
1634
|
-
} catch {
|
|
1635
|
+
} catch (error) {
|
|
1636
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
1637
|
+
throw new Error(
|
|
1638
|
+
`Qoder CN credential refresh failed (PAT re-exchange): ${detail}. If this persists, the PAT may be revoked \u2014 run /login qoder-cn.`
|
|
1639
|
+
);
|
|
1635
1640
|
}
|
|
1636
1641
|
}
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
// extend 1 hour to retry later
|
|
1641
|
-
};
|
|
1642
|
+
throw new Error(
|
|
1643
|
+
"Qoder CN credential refresh failed: the stored refresh chain carries no PAT. Run /login qoder-cn."
|
|
1644
|
+
);
|
|
1642
1645
|
}
|
|
1643
1646
|
const parts = credentials.refresh.split("|");
|
|
1644
1647
|
const refreshToken = parts[0] || "";
|
|
@@ -1648,6 +1651,7 @@ async function refreshQoderTokenForMode(credentials, mode) {
|
|
|
1648
1651
|
const prevName = prev.name || "";
|
|
1649
1652
|
const prevEmail = prev.email || "";
|
|
1650
1653
|
const refreshURL = getQoderRefreshURL(mode);
|
|
1654
|
+
let lastError = "unknown error";
|
|
1651
1655
|
try {
|
|
1652
1656
|
const response = await fetch(refreshURL, {
|
|
1653
1657
|
method: "POST",
|
|
@@ -1684,14 +1688,11 @@ async function refreshQoderTokenForMode(credentials, mode) {
|
|
|
1684
1688
|
});
|
|
1685
1689
|
return refreshed;
|
|
1686
1690
|
}
|
|
1687
|
-
|
|
1691
|
+
lastError = `HTTP ${response.status} ${response.statusText}`;
|
|
1692
|
+
} catch (error) {
|
|
1693
|
+
lastError = error instanceof Error ? error.message : String(error);
|
|
1688
1694
|
}
|
|
1689
|
-
|
|
1690
|
-
...credentials,
|
|
1691
|
-
expires: Date.now() + 60 * 60 * 1e3
|
|
1692
|
-
// extend for 1 hour
|
|
1693
|
-
};
|
|
1694
|
-
return refreshedFallback;
|
|
1695
|
+
throw new Error(`Qoder CN token refresh failed (${lastError}). Run /login qoder-cn.`);
|
|
1695
1696
|
}
|
|
1696
1697
|
var AuthStorage2, identityCache, authFileMem;
|
|
1697
1698
|
var init_oauth = __esm({
|
|
@@ -1880,6 +1881,10 @@ function formatQoderStreamError(statusCode, rawBody, now = Date.now()) {
|
|
|
1880
1881
|
const truncated = bodyStr.length > 500 ? `${bodyStr.slice(0, 500)}...` : bodyStr;
|
|
1881
1882
|
return `Upstream status ${statusCode}: ${truncated}`;
|
|
1882
1883
|
}
|
|
1884
|
+
function isCredentialExpiredError(error) {
|
|
1885
|
+
const message = error instanceof Error ? error.message : String(error ?? "");
|
|
1886
|
+
return message.includes("\u9519\u8BEF\u7801 105") || message.includes("\u51ED\u8BC1\u5931\u6548") || /token expired|login expired/i.test(message);
|
|
1887
|
+
}
|
|
1883
1888
|
|
|
1884
1889
|
// src/commands/usage.ts
|
|
1885
1890
|
init_region();
|
|
@@ -2575,6 +2580,8 @@ function transformMessagesForQoder(messages) {
|
|
|
2575
2580
|
}
|
|
2576
2581
|
|
|
2577
2582
|
// src/protocol/stream.ts
|
|
2583
|
+
var lastForcedHealAt = 0;
|
|
2584
|
+
var HEAL_COOLDOWN_MS = 6e4;
|
|
2578
2585
|
function stableHash(prefix, ...inputs) {
|
|
2579
2586
|
const hash = crypto3.createHash("sha256");
|
|
2580
2587
|
hash.update(prefix);
|
|
@@ -2711,373 +2718,388 @@ function streamQoder(model, context, options) {
|
|
|
2711
2718
|
try {
|
|
2712
2719
|
const providerMode = model.provider === "qoder-cn" ? "cn" : "global";
|
|
2713
2720
|
const region = getQoderRegionConfig(providerMode);
|
|
2714
|
-
const
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
const ident = await resolveQoderIdentity(accessToken, model.provider, providerMode);
|
|
2721
|
-
const userID = ident.userID || "qoder-user";
|
|
2722
|
-
const name = ident.name || region.userNameFallback;
|
|
2723
|
-
const email = ident.email || region.userEmailFallback;
|
|
2724
|
-
const machineID = ident.machineID || getMachineId();
|
|
2725
|
-
const modelConfig = getCachedModelConfig(model.id, providerMode);
|
|
2726
|
-
if (!modelConfig?.key) {
|
|
2727
|
-
throw new Error(`Unknown Qoder model id: ${model.id}`);
|
|
2728
|
-
}
|
|
2729
|
-
const qoderModel = modelConfig.key;
|
|
2730
|
-
const isReasoning = !!modelConfig.is_reasoning;
|
|
2731
|
-
const resolved = resolveRequestContext(PiAi, context);
|
|
2732
|
-
const normalizedMessages = transformMessagesForQoder(resolved.messages);
|
|
2733
|
-
const systemText = resolved.systemText;
|
|
2734
|
-
let lastUserText = "";
|
|
2735
|
-
for (let i = normalizedMessages.length - 1; i >= 0; i--) {
|
|
2736
|
-
if (normalizedMessages[i].role === "user") {
|
|
2737
|
-
const content = normalizedMessages[i].content;
|
|
2738
|
-
lastUserText = typeof content === "string" ? content : Array.isArray(content) ? content.map((c) => "text" in c ? c.text : "").join("") : "";
|
|
2739
|
-
break;
|
|
2721
|
+
const runAttempt = async (apiKey) => {
|
|
2722
|
+
const accessToken = apiKey ?? options?.apiKey;
|
|
2723
|
+
if (!accessToken) {
|
|
2724
|
+
throw new Error(
|
|
2725
|
+
providerMode === "cn" ? "Qoder CN credentials not set. Run /login qoder-cn or set QODERCN_PERSONAL_ACCESS_TOKEN." : "Qoder credentials not set. Run /login qoder or set QODER_PERSONAL_ACCESS_TOKEN."
|
|
2726
|
+
);
|
|
2740
2727
|
}
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
const recordID = stableChatRecordID(qoderModel, normalizedMessages, toolsRaw, maxTokens);
|
|
2750
|
-
const requestedLevel = options?.reasoning;
|
|
2751
|
-
const clamped = requestedLevel ? clampThinkingLevel(model, requestedLevel) : void 0;
|
|
2752
|
-
const reasoningLevel = clamped === "off" ? void 0 : clamped;
|
|
2753
|
-
const parameters = { max_tokens: maxTokens };
|
|
2754
|
-
if (reasoningLevel) {
|
|
2755
|
-
parameters.enable_thinking = true;
|
|
2756
|
-
const mapped = model.thinkingLevelMap?.[reasoningLevel];
|
|
2757
|
-
const effort = mapped && mapped !== "enabled" && mapped !== "disabled" ? mapped : reasoningLevel;
|
|
2758
|
-
if (modelConfig?.thinking_config?.enabled?.efforts && typeof effort === "string") {
|
|
2759
|
-
parameters.reasoning_effort = effort;
|
|
2728
|
+
const ident = await resolveQoderIdentity(accessToken, model.provider, providerMode);
|
|
2729
|
+
const userID = ident.userID || "qoder-user";
|
|
2730
|
+
const name = ident.name || region.userNameFallback;
|
|
2731
|
+
const email = ident.email || region.userEmailFallback;
|
|
2732
|
+
const machineID = ident.machineID || getMachineId();
|
|
2733
|
+
const modelConfig = getCachedModelConfig(model.id, providerMode);
|
|
2734
|
+
if (!modelConfig?.key) {
|
|
2735
|
+
throw new Error(`Unknown Qoder model id: ${model.id}`);
|
|
2760
2736
|
}
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
source: 1,
|
|
2774
|
-
version: "3",
|
|
2775
|
-
session_type: "qodercli",
|
|
2776
|
-
agent_id: "agent_common",
|
|
2777
|
-
task_id: "common",
|
|
2778
|
-
code_language: "",
|
|
2779
|
-
chat_prompt: "",
|
|
2780
|
-
image_urls: null,
|
|
2781
|
-
aliyun_user_type: "",
|
|
2782
|
-
// Qoder's server ignores the top-level `system` field (verified: the
|
|
2783
|
-
// model never sees it). Inject the system prompt as a leading
|
|
2784
|
-
// role:system message instead, which the server does honor.
|
|
2785
|
-
system: "",
|
|
2786
|
-
messages: systemText ? [{ role: "system", content: systemText }, ...normalizedMessages] : normalizedMessages,
|
|
2787
|
-
tools: toolsRaw || [],
|
|
2788
|
-
parameters,
|
|
2789
|
-
chat_context: {
|
|
2790
|
-
chatPrompt: "",
|
|
2791
|
-
imageUrls: null,
|
|
2792
|
-
extra: {
|
|
2793
|
-
context: [],
|
|
2794
|
-
modelConfig: {
|
|
2795
|
-
key: qoderModel,
|
|
2796
|
-
is_reasoning: isReasoning
|
|
2797
|
-
},
|
|
2798
|
-
originalContent: lastUserText
|
|
2799
|
-
},
|
|
2800
|
-
features: [],
|
|
2801
|
-
text: lastUserText
|
|
2802
|
-
},
|
|
2803
|
-
model_config: modelConfig,
|
|
2804
|
-
business: {
|
|
2805
|
-
product: "cli",
|
|
2806
|
-
version: "1.0.0",
|
|
2807
|
-
type: "agent",
|
|
2808
|
-
stage: "start",
|
|
2809
|
-
id: crypto3.randomUUID(),
|
|
2810
|
-
name: lastUserText.substring(0, 30),
|
|
2811
|
-
begin_at: Date.now()
|
|
2737
|
+
const qoderModel = modelConfig.key;
|
|
2738
|
+
const isReasoning = !!modelConfig.is_reasoning;
|
|
2739
|
+
const resolved = resolveRequestContext(PiAi, context);
|
|
2740
|
+
const normalizedMessages = transformMessagesForQoder(resolved.messages);
|
|
2741
|
+
const systemText = resolved.systemText;
|
|
2742
|
+
let lastUserText = "";
|
|
2743
|
+
for (let i = normalizedMessages.length - 1; i >= 0; i--) {
|
|
2744
|
+
if (normalizedMessages[i].role === "user") {
|
|
2745
|
+
const content = normalizedMessages[i].content;
|
|
2746
|
+
lastUserText = typeof content === "string" ? content : Array.isArray(content) ? content.map((c) => "text" in c ? c.text : "").join("") : "";
|
|
2747
|
+
break;
|
|
2748
|
+
}
|
|
2812
2749
|
}
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2750
|
+
const stablePart = stableHash("qoder-session", userID, qoderModel);
|
|
2751
|
+
const sessionID = options?.sessionId ? `${stablePart}-${options.sessionId}` : `${stablePart}-${crypto3.randomUUID()}`;
|
|
2752
|
+
let maxTokens = MAX_OUTPUT_TOKENS;
|
|
2753
|
+
if (options?.maxTokens && options.maxTokens < maxTokens) {
|
|
2754
|
+
maxTokens = options.maxTokens;
|
|
2755
|
+
}
|
|
2756
|
+
const toolsRaw = resolved.tools.length > 0 ? transformTools(resolved.tools) : void 0;
|
|
2757
|
+
const recordID = stableChatRecordID(qoderModel, normalizedMessages, toolsRaw, maxTokens);
|
|
2758
|
+
const requestedLevel = options?.reasoning;
|
|
2759
|
+
const clamped = requestedLevel ? clampThinkingLevel(model, requestedLevel) : void 0;
|
|
2760
|
+
const reasoningLevel = clamped === "off" ? void 0 : clamped;
|
|
2761
|
+
const parameters = { max_tokens: maxTokens };
|
|
2762
|
+
if (reasoningLevel) {
|
|
2763
|
+
parameters.enable_thinking = true;
|
|
2764
|
+
const mapped = model.thinkingLevelMap?.[reasoningLevel];
|
|
2765
|
+
const effort = mapped && mapped !== "enabled" && mapped !== "disabled" ? mapped : reasoningLevel;
|
|
2766
|
+
if (modelConfig?.thinking_config?.enabled?.efforts && typeof effort === "string") {
|
|
2767
|
+
parameters.reasoning_effort = effort;
|
|
2768
|
+
}
|
|
2769
|
+
} else {
|
|
2770
|
+
parameters.enable_thinking = false;
|
|
2771
|
+
}
|
|
2772
|
+
const reqBody = {
|
|
2773
|
+
request_id: crypto3.randomUUID(),
|
|
2774
|
+
request_set_id: recordID,
|
|
2775
|
+
chat_record_id: recordID,
|
|
2776
|
+
session_id: sessionID,
|
|
2777
|
+
stream: true,
|
|
2778
|
+
chat_task: "FREE_INPUT",
|
|
2779
|
+
is_reply: true,
|
|
2780
|
+
is_retry: false,
|
|
2781
|
+
source: 1,
|
|
2782
|
+
version: "3",
|
|
2783
|
+
session_type: "qodercli",
|
|
2784
|
+
agent_id: "agent_common",
|
|
2785
|
+
task_id: "common",
|
|
2786
|
+
code_language: "",
|
|
2787
|
+
chat_prompt: "",
|
|
2788
|
+
image_urls: null,
|
|
2789
|
+
aliyun_user_type: "",
|
|
2790
|
+
// Qoder's server ignores the top-level `system` field (verified: the
|
|
2791
|
+
// model never sees it). Inject the system prompt as a leading
|
|
2792
|
+
// role:system message instead, which the server does honor.
|
|
2793
|
+
system: "",
|
|
2794
|
+
messages: systemText ? [{ role: "system", content: systemText }, ...normalizedMessages] : normalizedMessages,
|
|
2795
|
+
tools: toolsRaw || [],
|
|
2796
|
+
parameters,
|
|
2797
|
+
chat_context: {
|
|
2798
|
+
chatPrompt: "",
|
|
2799
|
+
imageUrls: null,
|
|
2800
|
+
extra: {
|
|
2801
|
+
context: [],
|
|
2802
|
+
modelConfig: {
|
|
2803
|
+
key: qoderModel,
|
|
2804
|
+
is_reasoning: isReasoning
|
|
2805
|
+
},
|
|
2806
|
+
originalContent: lastUserText
|
|
2807
|
+
},
|
|
2808
|
+
features: [],
|
|
2809
|
+
text: lastUserText
|
|
2841
2810
|
},
|
|
2842
|
-
|
|
2843
|
-
|
|
2811
|
+
model_config: modelConfig,
|
|
2812
|
+
business: {
|
|
2813
|
+
product: "cli",
|
|
2814
|
+
version: "1.0.0",
|
|
2815
|
+
type: "agent",
|
|
2816
|
+
stage: "start",
|
|
2817
|
+
id: crypto3.randomUUID(),
|
|
2818
|
+
name: lastUserText.substring(0, 30),
|
|
2819
|
+
begin_at: Date.now()
|
|
2820
|
+
}
|
|
2821
|
+
};
|
|
2822
|
+
const bodyBytes = Buffer.from(JSON.stringify(reqBody));
|
|
2823
|
+
const encodedBytes = qoderEncodeBody(bodyBytes);
|
|
2824
|
+
const chatURL = getQoderChatURL(providerMode);
|
|
2825
|
+
const headers = buildAuthHeaders(encodedBytes, chatURL, {
|
|
2826
|
+
userID,
|
|
2827
|
+
authToken: accessToken,
|
|
2828
|
+
name,
|
|
2829
|
+
email,
|
|
2830
|
+
machineID
|
|
2844
2831
|
});
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2832
|
+
const modelSource = modelConfig.source || "system";
|
|
2833
|
+
let queuedAttempt = 0;
|
|
2834
|
+
let reader;
|
|
2835
|
+
let decoder;
|
|
2836
|
+
let buffer = "";
|
|
2837
|
+
let bufferStart = 0;
|
|
2838
|
+
for (; ; ) {
|
|
2839
|
+
const response = await fetch(chatURL, {
|
|
2840
|
+
method: "POST",
|
|
2841
|
+
headers: {
|
|
2842
|
+
"Content-Type": "application/json",
|
|
2843
|
+
Accept: "text/event-stream",
|
|
2844
|
+
"Cache-Control": "no-cache",
|
|
2845
|
+
"Accept-Encoding": "identity",
|
|
2846
|
+
"X-Model-Key": qoderModel,
|
|
2847
|
+
"X-Model-Source": modelSource,
|
|
2848
|
+
...headers
|
|
2849
|
+
},
|
|
2850
|
+
body: encodedBytes,
|
|
2851
|
+
signal: options?.signal
|
|
2858
2852
|
});
|
|
2859
|
-
if (
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
);
|
|
2853
|
+
if (!response.ok) {
|
|
2854
|
+
const errText = await response.text();
|
|
2855
|
+
throw new Error(formatQoderStreamError(response.status, errText));
|
|
2863
2856
|
}
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
}
|
|
2870
|
-
if (!reader || !decoder) throw new Error("No response body");
|
|
2871
|
-
let contentBlockIndex = -1;
|
|
2872
|
-
let thinkingBlockIndex = -1;
|
|
2873
|
-
let rawReasoningTail = "";
|
|
2874
|
-
const toolCallsState = [];
|
|
2875
|
-
const thinkingEnabled = options?.reasoning !== false && options?.reasoning !== "off";
|
|
2876
|
-
const thinkingParser = thinkingEnabled ? new ThinkingTagParser(output, stream) : null;
|
|
2877
|
-
stream.push({ type: "start", partial: output });
|
|
2878
|
-
let sawDone = false;
|
|
2879
|
-
while (!sawDone) {
|
|
2880
|
-
const { done, value } = await reader.read();
|
|
2881
|
-
if (done) break;
|
|
2882
|
-
if (bufferStart > 0) {
|
|
2883
|
-
buffer = buffer.substring(bufferStart);
|
|
2857
|
+
const body = response.body;
|
|
2858
|
+
if (!body) throw new Error("No response body");
|
|
2859
|
+
reader = new PeekableReader(body.getReader());
|
|
2860
|
+
decoder = new TextDecoder();
|
|
2861
|
+
buffer = "";
|
|
2884
2862
|
bufferStart = 0;
|
|
2863
|
+
const queue = await peekQueueNotice(reader, decoder);
|
|
2864
|
+
if (queue) {
|
|
2865
|
+
await reader.cancel().catch(() => {
|
|
2866
|
+
});
|
|
2867
|
+
if (queuedAttempt >= MAX_QUEUE_RETRIES) {
|
|
2868
|
+
throw new Error(
|
|
2869
|
+
`Qoder is at capacity: the request stayed queued after ${queuedAttempt + 1} attempts (~${queue.waitTimeSeconds}s wait reported). Try again shortly.`
|
|
2870
|
+
);
|
|
2871
|
+
}
|
|
2872
|
+
queuedAttempt++;
|
|
2873
|
+
await sleep(queue.retryAfterMs, options?.signal);
|
|
2874
|
+
continue;
|
|
2875
|
+
}
|
|
2876
|
+
break;
|
|
2885
2877
|
}
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2878
|
+
if (!reader || !decoder) throw new Error("No response body");
|
|
2879
|
+
let contentBlockIndex = -1;
|
|
2880
|
+
let thinkingBlockIndex = -1;
|
|
2881
|
+
let rawReasoningTail = "";
|
|
2882
|
+
const toolCallsState = [];
|
|
2883
|
+
const thinkingEnabled = options?.reasoning !== false && options?.reasoning !== "off";
|
|
2884
|
+
const thinkingParser = thinkingEnabled ? new ThinkingTagParser(output, stream) : null;
|
|
2885
|
+
stream.push({ type: "start", partial: output });
|
|
2886
|
+
let sawDone = false;
|
|
2887
|
+
while (!sawDone) {
|
|
2888
|
+
const { done, value } = await reader.read();
|
|
2889
|
+
if (done) break;
|
|
2890
|
+
if (bufferStart > 0) {
|
|
2891
|
+
buffer = buffer.substring(bufferStart);
|
|
2892
|
+
bufferStart = 0;
|
|
2897
2893
|
}
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
if (
|
|
2894
|
+
buffer += decoder.decode(value, { stream: true });
|
|
2895
|
+
while (true) {
|
|
2896
|
+
const lineEnd = buffer.indexOf("\n", bufferStart);
|
|
2897
|
+
if (lineEnd === -1) break;
|
|
2898
|
+
const line = buffer.substring(bufferStart, lineEnd).trim();
|
|
2899
|
+
bufferStart = lineEnd + 1;
|
|
2900
|
+
if (!line.startsWith("data:")) continue;
|
|
2901
|
+
const dataStr = line.substring(5).trim();
|
|
2902
|
+
if (dataStr === "[DONE]") {
|
|
2905
2903
|
sawDone = true;
|
|
2906
2904
|
break;
|
|
2907
2905
|
}
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
const
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
output.
|
|
2921
|
-
output.
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
});
|
|
2955
|
-
thinkingBlockIndex = -1;
|
|
2956
|
-
}
|
|
2957
|
-
if (thinkingParser) {
|
|
2958
|
-
thinkingParser.processChunk(delta.content);
|
|
2959
|
-
} else {
|
|
2960
|
-
if (contentBlockIndex === -1) {
|
|
2961
|
-
contentBlockIndex = output.content.length;
|
|
2962
|
-
output.content.push({ type: "text", text: "" });
|
|
2963
|
-
stream.push({ type: "text_start", contentIndex: contentBlockIndex, partial: output });
|
|
2906
|
+
try {
|
|
2907
|
+
const envelope = JSON.parse(dataStr);
|
|
2908
|
+
if (envelope.statusCodeValue && envelope.statusCodeValue !== 200) {
|
|
2909
|
+
throw new Error(formatQoderStreamError(envelope.statusCodeValue, envelope.body));
|
|
2910
|
+
}
|
|
2911
|
+
const innerStr = envelope.body;
|
|
2912
|
+
if (innerStr === "[DONE]") {
|
|
2913
|
+
sawDone = true;
|
|
2914
|
+
break;
|
|
2915
|
+
}
|
|
2916
|
+
if (!innerStr) continue;
|
|
2917
|
+
const inner = JSON.parse(innerStr);
|
|
2918
|
+
if (inner.id) output.responseId = inner.id;
|
|
2919
|
+
if (inner.model) output.responseModel = inner.model;
|
|
2920
|
+
if (inner.usage) {
|
|
2921
|
+
const u = inner.usage;
|
|
2922
|
+
const promptTokens = u.prompt_tokens ?? 0;
|
|
2923
|
+
const cacheReadTokens = u.prompt_tokens_details?.cached_tokens ?? 0;
|
|
2924
|
+
const cacheWriteTokens = u.prompt_tokens_details?.cache_write_tokens ?? 0;
|
|
2925
|
+
output.usage.input = Math.max(0, promptTokens - cacheReadTokens - cacheWriteTokens);
|
|
2926
|
+
output.usage.output = u.completion_tokens ?? 0;
|
|
2927
|
+
output.usage.totalTokens = u.total_tokens ?? 0;
|
|
2928
|
+
output.usage.cacheRead = cacheReadTokens;
|
|
2929
|
+
output.usage.cacheWrite = cacheWriteTokens;
|
|
2930
|
+
}
|
|
2931
|
+
if (inner.choices && inner.choices.length > 0) {
|
|
2932
|
+
const choice = inner.choices[0];
|
|
2933
|
+
const delta = choice.delta;
|
|
2934
|
+
if (delta) {
|
|
2935
|
+
if (delta.reasoning_content) {
|
|
2936
|
+
rawReasoningTail = (rawReasoningTail + delta.reasoning_content).slice(-512);
|
|
2937
|
+
const reasoningChunk = stripDsmlResidue(stripThinkingTags(delta.reasoning_content));
|
|
2938
|
+
if (reasoningChunk) {
|
|
2939
|
+
if (thinkingBlockIndex === -1) {
|
|
2940
|
+
thinkingBlockIndex = output.content.length;
|
|
2941
|
+
output.content.push({ type: "thinking", thinking: "" });
|
|
2942
|
+
stream.push({ type: "thinking_start", contentIndex: thinkingBlockIndex, partial: output });
|
|
2943
|
+
}
|
|
2944
|
+
const block = output.content[thinkingBlockIndex];
|
|
2945
|
+
block.thinking += reasoningChunk;
|
|
2946
|
+
stream.push({
|
|
2947
|
+
type: "thinking_delta",
|
|
2948
|
+
contentIndex: thinkingBlockIndex,
|
|
2949
|
+
delta: reasoningChunk,
|
|
2950
|
+
partial: output
|
|
2951
|
+
});
|
|
2964
2952
|
}
|
|
2965
|
-
const block = output.content[contentBlockIndex];
|
|
2966
|
-
block.text += delta.content;
|
|
2967
|
-
stream.push({
|
|
2968
|
-
type: "text_delta",
|
|
2969
|
-
contentIndex: contentBlockIndex,
|
|
2970
|
-
delta: delta.content,
|
|
2971
|
-
partial: output
|
|
2972
|
-
});
|
|
2973
2953
|
}
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
if (tc.id) state.id = tc.id;
|
|
2983
|
-
if (tc.function?.name) state.name = tc.function.name;
|
|
2984
|
-
if (state.emittedStart === void 0 && (state.id || state.name)) {
|
|
2985
|
-
state.emittedStart = true;
|
|
2986
|
-
state.contentIndex = output.content.length;
|
|
2987
|
-
output.content.push({
|
|
2988
|
-
type: "toolCall",
|
|
2989
|
-
id: state.id,
|
|
2990
|
-
name: state.name,
|
|
2991
|
-
arguments: {}
|
|
2954
|
+
if (delta.content) {
|
|
2955
|
+
if (thinkingBlockIndex !== -1) {
|
|
2956
|
+
const block = output.content[thinkingBlockIndex];
|
|
2957
|
+
stream.push({
|
|
2958
|
+
type: "thinking_end",
|
|
2959
|
+
contentIndex: thinkingBlockIndex,
|
|
2960
|
+
content: block.thinking,
|
|
2961
|
+
partial: output
|
|
2992
2962
|
});
|
|
2993
|
-
|
|
2994
|
-
}
|
|
2995
|
-
if (state.emittedStart) {
|
|
2996
|
-
const block = output.content[state.contentIndex];
|
|
2997
|
-
block.id = state.id;
|
|
2998
|
-
block.name = state.name;
|
|
2963
|
+
thinkingBlockIndex = -1;
|
|
2999
2964
|
}
|
|
3000
|
-
if (
|
|
3001
|
-
|
|
3002
|
-
|
|
2965
|
+
if (thinkingParser) {
|
|
2966
|
+
thinkingParser.processChunk(delta.content);
|
|
2967
|
+
} else {
|
|
2968
|
+
if (contentBlockIndex === -1) {
|
|
2969
|
+
contentBlockIndex = output.content.length;
|
|
2970
|
+
output.content.push({ type: "text", text: "" });
|
|
2971
|
+
stream.push({ type: "text_start", contentIndex: contentBlockIndex, partial: output });
|
|
2972
|
+
}
|
|
2973
|
+
const block = output.content[contentBlockIndex];
|
|
2974
|
+
block.text += delta.content;
|
|
3003
2975
|
stream.push({
|
|
3004
|
-
type: "
|
|
3005
|
-
contentIndex:
|
|
3006
|
-
delta:
|
|
2976
|
+
type: "text_delta",
|
|
2977
|
+
contentIndex: contentBlockIndex,
|
|
2978
|
+
delta: delta.content,
|
|
3007
2979
|
partial: output
|
|
3008
2980
|
});
|
|
3009
2981
|
}
|
|
3010
2982
|
}
|
|
2983
|
+
if (delta.tool_calls && Array.isArray(delta.tool_calls)) {
|
|
2984
|
+
for (const tc of delta.tool_calls) {
|
|
2985
|
+
const idx = tc.index ?? 0;
|
|
2986
|
+
if (!toolCallsState[idx]) {
|
|
2987
|
+
toolCallsState[idx] = { arguments: "", id: "", name: "", contentIndex: 0 };
|
|
2988
|
+
}
|
|
2989
|
+
const state = toolCallsState[idx];
|
|
2990
|
+
if (tc.id) state.id = tc.id;
|
|
2991
|
+
if (tc.function?.name) state.name = tc.function.name;
|
|
2992
|
+
if (state.emittedStart === void 0 && (state.id || state.name)) {
|
|
2993
|
+
state.emittedStart = true;
|
|
2994
|
+
state.contentIndex = output.content.length;
|
|
2995
|
+
output.content.push({
|
|
2996
|
+
type: "toolCall",
|
|
2997
|
+
id: state.id,
|
|
2998
|
+
name: state.name,
|
|
2999
|
+
arguments: {}
|
|
3000
|
+
});
|
|
3001
|
+
stream.push({ type: "toolcall_start", contentIndex: state.contentIndex, partial: output });
|
|
3002
|
+
}
|
|
3003
|
+
if (state.emittedStart) {
|
|
3004
|
+
const block = output.content[state.contentIndex];
|
|
3005
|
+
block.id = state.id;
|
|
3006
|
+
block.name = state.name;
|
|
3007
|
+
}
|
|
3008
|
+
if (tc.function?.arguments) {
|
|
3009
|
+
const argDelta = tc.function.arguments;
|
|
3010
|
+
state.arguments += argDelta;
|
|
3011
|
+
stream.push({
|
|
3012
|
+
type: "toolcall_delta",
|
|
3013
|
+
contentIndex: state.contentIndex,
|
|
3014
|
+
delta: argDelta,
|
|
3015
|
+
partial: output
|
|
3016
|
+
});
|
|
3017
|
+
}
|
|
3018
|
+
}
|
|
3019
|
+
}
|
|
3020
|
+
}
|
|
3021
|
+
if (choice.finish_reason) {
|
|
3022
|
+
output.stopReason = choice.finish_reason;
|
|
3011
3023
|
}
|
|
3012
3024
|
}
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
if (process.env.QODER_DEBUG) {
|
|
3020
|
-
console.error("[pi-provider-qoder] skipping malformed SSE line:", dataStr.slice(0, 200));
|
|
3025
|
+
} catch (e) {
|
|
3026
|
+
if (e instanceof SyntaxError) {
|
|
3027
|
+
if (process.env.QODER_DEBUG) {
|
|
3028
|
+
console.error("[pi-provider-qoder] skipping malformed SSE line:", dataStr.slice(0, 200));
|
|
3029
|
+
}
|
|
3030
|
+
continue;
|
|
3021
3031
|
}
|
|
3022
|
-
|
|
3032
|
+
throw e;
|
|
3023
3033
|
}
|
|
3024
|
-
throw e;
|
|
3025
3034
|
}
|
|
3026
3035
|
}
|
|
3027
|
-
|
|
3028
|
-
await reader.cancel().catch(() => {
|
|
3029
|
-
});
|
|
3030
|
-
if (thinkingParser) {
|
|
3031
|
-
thinkingParser.finalize();
|
|
3032
|
-
}
|
|
3033
|
-
if (thinkingBlockIndex !== -1) {
|
|
3034
|
-
const block = output.content[thinkingBlockIndex];
|
|
3035
|
-
stream.push({
|
|
3036
|
-
type: "thinking_end",
|
|
3037
|
-
contentIndex: thinkingBlockIndex,
|
|
3038
|
-
content: block.thinking,
|
|
3039
|
-
partial: output
|
|
3036
|
+
await reader.cancel().catch(() => {
|
|
3040
3037
|
});
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
try {
|
|
3047
|
-
args = JSON.parse(state.arguments || "{}");
|
|
3048
|
-
} catch {
|
|
3049
|
-
}
|
|
3050
|
-
const block = output.content[state.contentIndex];
|
|
3051
|
-
block.arguments = args;
|
|
3038
|
+
if (thinkingParser) {
|
|
3039
|
+
thinkingParser.finalize();
|
|
3040
|
+
}
|
|
3041
|
+
if (thinkingBlockIndex !== -1) {
|
|
3042
|
+
const block = output.content[thinkingBlockIndex];
|
|
3052
3043
|
stream.push({
|
|
3053
|
-
type: "
|
|
3054
|
-
contentIndex:
|
|
3055
|
-
|
|
3056
|
-
type: "toolCall",
|
|
3057
|
-
id: state.id,
|
|
3058
|
-
name: state.name,
|
|
3059
|
-
arguments: args
|
|
3060
|
-
},
|
|
3044
|
+
type: "thinking_end",
|
|
3045
|
+
contentIndex: thinkingBlockIndex,
|
|
3046
|
+
content: block.thinking,
|
|
3061
3047
|
partial: output
|
|
3062
3048
|
});
|
|
3063
3049
|
}
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3050
|
+
for (const state of toolCallsState) {
|
|
3051
|
+
if (state?.emittedStart && !state.emittedEnd) {
|
|
3052
|
+
state.emittedEnd = true;
|
|
3053
|
+
let args = {};
|
|
3054
|
+
try {
|
|
3055
|
+
args = JSON.parse(state.arguments || "{}");
|
|
3056
|
+
} catch {
|
|
3057
|
+
}
|
|
3058
|
+
const block = output.content[state.contentIndex];
|
|
3059
|
+
block.arguments = args;
|
|
3060
|
+
stream.push({
|
|
3061
|
+
type: "toolcall_end",
|
|
3062
|
+
contentIndex: state.contentIndex,
|
|
3063
|
+
toolCall: {
|
|
3064
|
+
type: "toolCall",
|
|
3065
|
+
id: state.id,
|
|
3066
|
+
name: state.name,
|
|
3067
|
+
arguments: args
|
|
3068
|
+
},
|
|
3069
|
+
partial: output
|
|
3070
|
+
});
|
|
3071
|
+
}
|
|
3072
|
+
}
|
|
3073
|
+
if (toolCallsState.some((state) => state?.emittedStart)) {
|
|
3074
|
+
output.stopReason = "toolUse";
|
|
3075
|
+
}
|
|
3076
|
+
if (isDegenerateDsmlTurn(output, rawReasoningTail)) {
|
|
3077
|
+
output.stopReason = "error";
|
|
3078
|
+
output.errorMessage = "Qoder server error: degenerate model output (unparseable DSML tool-call markup, no tool call executed)";
|
|
3079
|
+
stream.push({ type: "error", reason: "error", error: output });
|
|
3080
|
+
stream.end();
|
|
3081
|
+
return;
|
|
3082
|
+
}
|
|
3083
|
+
stream.push({
|
|
3084
|
+
type: "done",
|
|
3085
|
+
reason: output.stopReason,
|
|
3086
|
+
message: output
|
|
3087
|
+
});
|
|
3072
3088
|
stream.end();
|
|
3073
|
-
|
|
3089
|
+
};
|
|
3090
|
+
try {
|
|
3091
|
+
await runAttempt(void 0);
|
|
3092
|
+
} catch (error) {
|
|
3093
|
+
if (model.provider !== "qoder-cn" || !isCredentialExpiredError(error) || Date.now() - lastForcedHealAt < HEAL_COOLDOWN_MS) {
|
|
3094
|
+
throw error;
|
|
3095
|
+
}
|
|
3096
|
+
lastForcedHealAt = Date.now();
|
|
3097
|
+
const stored = getCachedCredentials("", model.provider);
|
|
3098
|
+
if (!stored) throw error;
|
|
3099
|
+
const healed = await refreshQoderTokenForMode(stored, providerMode);
|
|
3100
|
+
saveCredentialsToAuthFile(model.provider, healed);
|
|
3101
|
+
await runAttempt(healed.access);
|
|
3074
3102
|
}
|
|
3075
|
-
stream.push({
|
|
3076
|
-
type: "done",
|
|
3077
|
-
reason: output.stopReason,
|
|
3078
|
-
message: output
|
|
3079
|
-
});
|
|
3080
|
-
stream.end();
|
|
3081
3103
|
} catch (e) {
|
|
3082
3104
|
output.stopReason = options?.signal?.aborted ? "aborted" : "error";
|
|
3083
3105
|
output.errorMessage = e instanceof Error ? e.message : String(e);
|
package/package.json
CHANGED