pi-provider-qoder-cn 0.2.27 → 0.2.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -0
- package/dist/index.js +142 -17
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -14,6 +14,16 @@ load order (last wins), so keep this package listed last or uninstall upstream.
|
|
|
14
14
|
pi install npm:pi-provider-qoder-cn
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
+
If your npm registry is a mirror (for example `registry.npmmirror.com`), it may
|
|
18
|
+
lag behind the official registry by hours. A release that shows as green on
|
|
19
|
+
GitHub but is missing from `npm view` is usually this, not a failed publish.
|
|
20
|
+
Check the official source directly and install with an explicit registry:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm view pi-provider-qoder-cn version --registry=https://registry.npmjs.org/
|
|
24
|
+
npm install pi-provider-qoder-cn@latest --registry=https://registry.npmjs.org/
|
|
25
|
+
```
|
|
26
|
+
|
|
17
27
|
## What this fork adds
|
|
18
28
|
|
|
19
29
|
| Feature | Upstream | This fork |
|
package/dist/index.js
CHANGED
|
@@ -3008,6 +3008,21 @@ var THINKING_TAG_VARIANTS = [
|
|
|
3008
3008
|
{ open: "<thought>", close: "</thought>" }
|
|
3009
3009
|
];
|
|
3010
3010
|
var ALL_THINKING_TAGS = THINKING_TAG_VARIANTS.flatMap((variant) => [variant.open, variant.close]);
|
|
3011
|
+
function hasAnyCompleteThinkingTag(text) {
|
|
3012
|
+
return THINKING_TAG_VARIANTS.some((variant) => text.includes(variant.open) || text.includes(variant.close));
|
|
3013
|
+
}
|
|
3014
|
+
function hasOrphanThinkingCloser(text) {
|
|
3015
|
+
for (const variant of THINKING_TAG_VARIANTS) {
|
|
3016
|
+
const closePos = text.indexOf(variant.close);
|
|
3017
|
+
if (closePos === -1) continue;
|
|
3018
|
+
const openerBefore = THINKING_TAG_VARIANTS.some((other) => {
|
|
3019
|
+
const openPos = text.indexOf(other.open);
|
|
3020
|
+
return openPos !== -1 && openPos < closePos;
|
|
3021
|
+
});
|
|
3022
|
+
if (!openerBefore) return true;
|
|
3023
|
+
}
|
|
3024
|
+
return false;
|
|
3025
|
+
}
|
|
3011
3026
|
function getTrailingPossibleTagPrefixLength(text, tag) {
|
|
3012
3027
|
const maxPrefixLength = Math.min(text.length, tag.length - 1);
|
|
3013
3028
|
for (let len = maxPrefixLength; len > 0; len--) {
|
|
@@ -3055,6 +3070,23 @@ var ThinkingTagParser = class {
|
|
|
3055
3070
|
textBlockIndex = null;
|
|
3056
3071
|
lastTextBlockIndex = null;
|
|
3057
3072
|
activeEndTag = THINKING_TAG_VARIANTS[0].close;
|
|
3073
|
+
orphanCloserMeansThinking = false;
|
|
3074
|
+
/**
|
|
3075
|
+
* Must be called before the first `processChunk`.
|
|
3076
|
+
*
|
|
3077
|
+
* When the reasoning_content channel carried no thinking body this turn
|
|
3078
|
+
* (only a bare opener tag, or nothing at all), an orphan closer in the
|
|
3079
|
+
* content stream is preceded by THINKING text rather than answer text.
|
|
3080
|
+
* stream.ts decides this once, at the first content delta, because Qoder
|
|
3081
|
+
* sends the reasoning channel before the content channel.
|
|
3082
|
+
*
|
|
3083
|
+
* Default false preserves the historical behaviour: the opener and its
|
|
3084
|
+
* thinking text arrived via reasoning_content, so text before the orphan
|
|
3085
|
+
* closer in the content stream is visible answer text.
|
|
3086
|
+
*/
|
|
3087
|
+
setOrphanCloserMeansThinking(value) {
|
|
3088
|
+
this.orphanCloserMeansThinking = value;
|
|
3089
|
+
}
|
|
3058
3090
|
processChunk(chunk) {
|
|
3059
3091
|
this.textBuffer += chunk;
|
|
3060
3092
|
while (this.textBuffer.length > 0) {
|
|
@@ -3091,6 +3123,17 @@ var ThinkingTagParser = class {
|
|
|
3091
3123
|
content: block.thinking,
|
|
3092
3124
|
partial: this.output
|
|
3093
3125
|
});
|
|
3126
|
+
} else if (this.orphanCloserMeansThinking && !this.thinkingExtracted) {
|
|
3127
|
+
this.emitThinking(this.textBuffer);
|
|
3128
|
+
if (this.thinkingBlockIndex !== null) {
|
|
3129
|
+
const block = this.output.content[this.thinkingBlockIndex];
|
|
3130
|
+
this.stream.push({
|
|
3131
|
+
type: "thinking_end",
|
|
3132
|
+
contentIndex: this.thinkingBlockIndex,
|
|
3133
|
+
content: block.thinking,
|
|
3134
|
+
partial: this.output
|
|
3135
|
+
});
|
|
3136
|
+
}
|
|
3094
3137
|
} else {
|
|
3095
3138
|
this.emitText(this.textBuffer);
|
|
3096
3139
|
}
|
|
@@ -3124,6 +3167,27 @@ var ThinkingTagParser = class {
|
|
|
3124
3167
|
return;
|
|
3125
3168
|
}
|
|
3126
3169
|
if (bestCloseVariant !== null) {
|
|
3170
|
+
if (this.orphanCloserMeansThinking && bestClosePos > 0) {
|
|
3171
|
+
const thinkingText = this.textBuffer.slice(0, bestClosePos);
|
|
3172
|
+
this.emitThinking(thinkingText);
|
|
3173
|
+
if (this.thinkingBlockIndex !== null) {
|
|
3174
|
+
const block = this.output.content[this.thinkingBlockIndex];
|
|
3175
|
+
this.stream.push({
|
|
3176
|
+
type: "thinking_end",
|
|
3177
|
+
contentIndex: this.thinkingBlockIndex,
|
|
3178
|
+
content: block.thinking,
|
|
3179
|
+
partial: this.output
|
|
3180
|
+
});
|
|
3181
|
+
this.thinkingBlockIndex = null;
|
|
3182
|
+
}
|
|
3183
|
+
this.textBuffer = this.textBuffer.slice(bestClosePos + bestCloseVariant.close.length);
|
|
3184
|
+
if (this.textBuffer.startsWith("\n\n")) this.textBuffer = this.textBuffer.slice(2);
|
|
3185
|
+
else if (this.textBuffer.startsWith("\n")) this.textBuffer = this.textBuffer.slice(1);
|
|
3186
|
+
this.thinkingExtracted = true;
|
|
3187
|
+
this.lastTextBlockIndex = this.textBlockIndex;
|
|
3188
|
+
this.textBlockIndex = null;
|
|
3189
|
+
return;
|
|
3190
|
+
}
|
|
3127
3191
|
if (bestClosePos > 0) this.emitText(this.textBuffer.slice(0, bestClosePos));
|
|
3128
3192
|
this.textBuffer = this.textBuffer.slice(bestClosePos + bestCloseVariant.close.length);
|
|
3129
3193
|
if (this.textBuffer.startsWith("\n\n")) this.textBuffer = this.textBuffer.slice(2);
|
|
@@ -3133,7 +3197,9 @@ var ThinkingTagParser = class {
|
|
|
3133
3197
|
const trailingPrefixLength = getMaxTrailingPossibleTagPrefixLength(this.textBuffer, ALL_THINKING_TAGS);
|
|
3134
3198
|
const safeLen = this.textBuffer.length - trailingPrefixLength;
|
|
3135
3199
|
if (safeLen > 0) {
|
|
3136
|
-
this.
|
|
3200
|
+
const safeText = this.textBuffer.slice(0, safeLen);
|
|
3201
|
+
if (this.orphanCloserMeansThinking) this.emitThinking(safeText);
|
|
3202
|
+
else this.emitText(safeText);
|
|
3137
3203
|
this.textBuffer = this.textBuffer.slice(safeLen);
|
|
3138
3204
|
}
|
|
3139
3205
|
}
|
|
@@ -3233,7 +3299,14 @@ function transformTools(tools) {
|
|
|
3233
3299
|
function transformMessagesForQoder(messages) {
|
|
3234
3300
|
const normalizedMessages = [];
|
|
3235
3301
|
const droppedToolCallIds = /* @__PURE__ */ new Set();
|
|
3302
|
+
const pendingToolImages = [];
|
|
3303
|
+
const flushToolImages = () => {
|
|
3304
|
+
if (pendingToolImages.length === 0) return;
|
|
3305
|
+
normalizedMessages.push(...pendingToolImages);
|
|
3306
|
+
pendingToolImages.length = 0;
|
|
3307
|
+
};
|
|
3236
3308
|
for (const msg of messages) {
|
|
3309
|
+
if (msg.role !== "toolResult") flushToolImages();
|
|
3237
3310
|
if (msg.role === "assistant" && (msg.stopReason === "error" || msg.stopReason === "aborted")) {
|
|
3238
3311
|
const am = msg;
|
|
3239
3312
|
if (Array.isArray(am.content)) {
|
|
@@ -3325,7 +3398,7 @@ function transformMessagesForQoder(messages) {
|
|
|
3325
3398
|
});
|
|
3326
3399
|
const images = getContentImages(tr);
|
|
3327
3400
|
if (images.length > 0) {
|
|
3328
|
-
|
|
3401
|
+
pendingToolImages.push({
|
|
3329
3402
|
role: "user",
|
|
3330
3403
|
content: [
|
|
3331
3404
|
{
|
|
@@ -3343,12 +3416,14 @@ function transformMessagesForQoder(messages) {
|
|
|
3343
3416
|
}
|
|
3344
3417
|
}
|
|
3345
3418
|
}
|
|
3419
|
+
flushToolImages();
|
|
3346
3420
|
return normalizedMessages;
|
|
3347
3421
|
}
|
|
3348
3422
|
|
|
3349
3423
|
// src/protocol/stream.ts
|
|
3350
3424
|
var healCooldowns = /* @__PURE__ */ new Map();
|
|
3351
3425
|
var HEAL_COOLDOWN_MS = 6e4;
|
|
3426
|
+
var CONTENT_CLASSIFY_BUFFER_LIMIT = 8 * 1024;
|
|
3352
3427
|
function stableHash(prefix, ...inputs) {
|
|
3353
3428
|
const hash = crypto3.createHash("sha256");
|
|
3354
3429
|
hash.update(prefix);
|
|
@@ -3731,13 +3806,43 @@ function streamQoder(model, context, options) {
|
|
|
3731
3806
|
const toolCallsState = [];
|
|
3732
3807
|
const thinkingEnabled = options?.reasoning !== false && options?.reasoning !== "off";
|
|
3733
3808
|
const thinkingParser = thinkingEnabled ? new ThinkingTagParser(output, stream) : null;
|
|
3809
|
+
let sawDone = false;
|
|
3810
|
+
let sawFinishReason = false;
|
|
3811
|
+
let sawAnyDelta = false;
|
|
3812
|
+
let reasoningChannelHadBody = false;
|
|
3813
|
+
let contentClassificationDone = false;
|
|
3814
|
+
let contentBuffer = "";
|
|
3815
|
+
const flushContentBuffer = (final = false) => {
|
|
3816
|
+
if (!contentBuffer) return;
|
|
3817
|
+
if (!contentClassificationDone && (final || hasAnyCompleteThinkingTag(contentBuffer) || contentBuffer.length >= CONTENT_CLASSIFY_BUFFER_LIMIT)) {
|
|
3818
|
+
if (thinkingParser && !reasoningChannelHadBody && hasOrphanThinkingCloser(contentBuffer)) {
|
|
3819
|
+
thinkingParser.setOrphanCloserMeansThinking(true);
|
|
3820
|
+
}
|
|
3821
|
+
contentClassificationDone = true;
|
|
3822
|
+
}
|
|
3823
|
+
if (!contentClassificationDone) return;
|
|
3824
|
+
const pending = contentBuffer;
|
|
3825
|
+
contentBuffer = "";
|
|
3826
|
+
if (thinkingParser) thinkingParser.processChunk(pending);
|
|
3827
|
+
else emitPlainText(pending);
|
|
3828
|
+
};
|
|
3829
|
+
const emitPlainText = (text) => {
|
|
3830
|
+
if (!text) return;
|
|
3831
|
+
if (contentBlockIndex === -1) {
|
|
3832
|
+
contentBlockIndex = output.content.length;
|
|
3833
|
+
output.content.push({ type: "text", text: "" });
|
|
3834
|
+
stream.push({ type: "text_start", contentIndex: contentBlockIndex, partial: output });
|
|
3835
|
+
}
|
|
3836
|
+
const block = output.content[contentBlockIndex];
|
|
3837
|
+
block.text += text;
|
|
3838
|
+
stream.push({ type: "text_delta", contentIndex: contentBlockIndex, delta: text, partial: output });
|
|
3839
|
+
};
|
|
3734
3840
|
if (!started) {
|
|
3735
3841
|
started = true;
|
|
3736
3842
|
} else {
|
|
3737
3843
|
output.content.length = 0;
|
|
3738
3844
|
}
|
|
3739
3845
|
stream.push({ type: "start", partial: output });
|
|
3740
|
-
let sawDone = false;
|
|
3741
3846
|
while (!sawDone) {
|
|
3742
3847
|
const { done, value } = await reader.read();
|
|
3743
3848
|
if (done) break;
|
|
@@ -3788,9 +3893,11 @@ function streamQoder(model, context, options) {
|
|
|
3788
3893
|
const choice = inner.choices[0];
|
|
3789
3894
|
const delta = choice.delta;
|
|
3790
3895
|
if (delta) {
|
|
3896
|
+
sawAnyDelta = true;
|
|
3791
3897
|
if (delta.reasoning_content) {
|
|
3792
3898
|
rawReasoningTail = (rawReasoningTail + delta.reasoning_content).slice(-512);
|
|
3793
3899
|
const reasoningChunk = stripDsmlResidue(stripThinkingTags(delta.reasoning_content));
|
|
3900
|
+
if (reasoningChunk.trim().length > 0) reasoningChannelHadBody = true;
|
|
3794
3901
|
if (reasoningChunk) {
|
|
3795
3902
|
if (thinkingBlockIndex === -1) {
|
|
3796
3903
|
thinkingBlockIndex = output.content.length;
|
|
@@ -3819,24 +3926,14 @@ function streamQoder(model, context, options) {
|
|
|
3819
3926
|
thinkingBlockIndex = -1;
|
|
3820
3927
|
}
|
|
3821
3928
|
if (thinkingParser) {
|
|
3822
|
-
|
|
3929
|
+
contentBuffer += delta.content;
|
|
3930
|
+
flushContentBuffer();
|
|
3823
3931
|
} else {
|
|
3824
|
-
|
|
3825
|
-
contentBlockIndex = output.content.length;
|
|
3826
|
-
output.content.push({ type: "text", text: "" });
|
|
3827
|
-
stream.push({ type: "text_start", contentIndex: contentBlockIndex, partial: output });
|
|
3828
|
-
}
|
|
3829
|
-
const block = output.content[contentBlockIndex];
|
|
3830
|
-
block.text += delta.content;
|
|
3831
|
-
stream.push({
|
|
3832
|
-
type: "text_delta",
|
|
3833
|
-
contentIndex: contentBlockIndex,
|
|
3834
|
-
delta: delta.content,
|
|
3835
|
-
partial: output
|
|
3836
|
-
});
|
|
3932
|
+
emitPlainText(delta.content);
|
|
3837
3933
|
}
|
|
3838
3934
|
}
|
|
3839
3935
|
if (delta.tool_calls && Array.isArray(delta.tool_calls)) {
|
|
3936
|
+
flushContentBuffer(true);
|
|
3840
3937
|
for (const tc of delta.tool_calls) {
|
|
3841
3938
|
const idx = tc.index ?? 0;
|
|
3842
3939
|
if (!toolCallsState[idx]) {
|
|
@@ -3875,6 +3972,7 @@ function streamQoder(model, context, options) {
|
|
|
3875
3972
|
}
|
|
3876
3973
|
}
|
|
3877
3974
|
if (choice.finish_reason) {
|
|
3975
|
+
sawFinishReason = true;
|
|
3878
3976
|
output.stopReason = choice.finish_reason;
|
|
3879
3977
|
}
|
|
3880
3978
|
}
|
|
@@ -3891,6 +3989,7 @@ function streamQoder(model, context, options) {
|
|
|
3891
3989
|
}
|
|
3892
3990
|
await reader.cancel().catch(() => {
|
|
3893
3991
|
});
|
|
3992
|
+
flushContentBuffer(true);
|
|
3894
3993
|
if (thinkingParser) {
|
|
3895
3994
|
thinkingParser.finalize();
|
|
3896
3995
|
}
|
|
@@ -3936,6 +4035,32 @@ function streamQoder(model, context, options) {
|
|
|
3936
4035
|
stream.end();
|
|
3937
4036
|
return;
|
|
3938
4037
|
}
|
|
4038
|
+
if (!sawAnyDelta) {
|
|
4039
|
+
if (!options?.signal?.aborted) {
|
|
4040
|
+
output.stopReason = "error";
|
|
4041
|
+
output.errorMessage = "Qoder server error: incomplete stream (no content deltas received)";
|
|
4042
|
+
stream.push({ type: "error", reason: "error", error: output });
|
|
4043
|
+
stream.end();
|
|
4044
|
+
return;
|
|
4045
|
+
}
|
|
4046
|
+
output.stopReason = "aborted";
|
|
4047
|
+
stream.push({ type: "error", reason: "aborted", error: output });
|
|
4048
|
+
stream.end();
|
|
4049
|
+
return;
|
|
4050
|
+
}
|
|
4051
|
+
if (!sawDone && !sawFinishReason) {
|
|
4052
|
+
if (!options?.signal?.aborted) {
|
|
4053
|
+
output.stopReason = "error";
|
|
4054
|
+
output.errorMessage = "Qoder server error: incomplete stream (response ended without [DONE] or finish_reason)";
|
|
4055
|
+
stream.push({ type: "error", reason: "error", error: output });
|
|
4056
|
+
stream.end();
|
|
4057
|
+
return;
|
|
4058
|
+
}
|
|
4059
|
+
output.stopReason = "aborted";
|
|
4060
|
+
stream.push({ type: "error", reason: "aborted", error: output });
|
|
4061
|
+
stream.end();
|
|
4062
|
+
return;
|
|
4063
|
+
}
|
|
3939
4064
|
stream.push({
|
|
3940
4065
|
type: "done",
|
|
3941
4066
|
reason: output.stopReason,
|
package/package.json
CHANGED