pi-provider-qoder-cn 0.2.28 → 0.2.30
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 +41 -17
- package/dist/index.js +193 -78
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -193,9 +193,9 @@ The provider list is re-registered in the running session, so the change is usab
|
|
|
193
193
|
in `/model` immediately — no restart. Append `list` to print the models pi currently
|
|
194
194
|
has cached, without touching the network.
|
|
195
195
|
|
|
196
|
-
Models offered only in the `app` group are included too
|
|
197
|
-
|
|
198
|
-
|
|
196
|
+
Models offered only in the `app` group are included too �� QoDuck (`lmodel`) was
|
|
197
|
+
such an entry before its upstream removal in the 2026-11 catalog. Entries
|
|
198
|
+
already present in `chat` are not duplicated, and UI-only tiers from other
|
|
199
199
|
groups (`Performance`, `Premium`, ...) stay out of the picker.
|
|
200
200
|
|
|
201
201
|
The catalog key is a slot, not a model: upstream has served Kimi-K2.6 through
|
|
@@ -284,22 +284,46 @@ Choose the Qoder CN endpoint (default: personal account)
|
|
|
284
284
|
|
|
285
285
|
## Models
|
|
286
286
|
|
|
287
|
+
### Qoder CN (`qoder-cn`) �� catalog as of 2026-11-11
|
|
288
|
+
|
|
289
|
+
| Model | id | Context | Thinking effort |
|
|
290
|
+
|---|---|---|---|
|
|
291
|
+
| Auto | `auto` | 1M | �� |
|
|
292
|
+
| Qwen 3.8-Max | `qwen3.8-max` | 1M | ? |
|
|
293
|
+
| Qwen 3.8-Flash | `qwen3.8-flash` | 1M | ? |
|
|
294
|
+
| DeepSeek V4 Pro | `deepseek-v4-pro` | 1M | ? |
|
|
295
|
+
| DeepSeek Flash | `deepseek-flash` | 1M | ? |
|
|
296
|
+
| GLM-5.3 | `glm-5.3` | 1M | ? |
|
|
297
|
+
| GLM-5.3-Flash | `glm-5.3-flash` | 1M | ? |
|
|
298
|
+
| Kimi-K3.1-Pro | `kimi-k3.1-pro` | 1M | ? |
|
|
299
|
+
| Kimi-K2.8-Preview | `kimi-k2.8-preview` | 1M | ? |
|
|
300
|
+
| Step-5-Preview | `step-5-preview` | 1M | ? |
|
|
301
|
+
| MiniMax-M3.1-Flash | `minimax-m3.1-flash` | 1M | ? |
|
|
302
|
+
|
|
303
|
+
### Qoder Global (`qoder`) �� catalog as of 2026-11-11
|
|
304
|
+
|
|
287
305
|
| Model | id | Context | Thinking effort |
|
|
288
306
|
|---|---|---|---|
|
|
289
|
-
| Auto | `
|
|
290
|
-
|
|
|
291
|
-
|
|
|
292
|
-
|
|
|
293
|
-
|
|
|
294
|
-
|
|
|
295
|
-
|
|
|
296
|
-
|
|
|
297
|
-
|
|
|
298
|
-
|
|
|
299
|
-
| GLM
|
|
300
|
-
|
|
|
301
|
-
|
|
|
302
|
-
|
|
|
307
|
+
| Auto | `Auto` | 1M | �� |
|
|
308
|
+
| Ultimate | `Ultimate` | 1M | ? |
|
|
309
|
+
| Efficient | `Efficient` | 200K | �� |
|
|
310
|
+
| Sonus | `Sonus` | 1M | ? |
|
|
311
|
+
| Cantus | `Cantus` | 1M | ? |
|
|
312
|
+
| Qwen4-Max-Preview | `Qwen4-Max-Preview` | 1M | ? |
|
|
313
|
+
| Qwen 3.8-Max | `Qwen3.8-Max` | 1M | ? |
|
|
314
|
+
| Qwen 3.8-Flash | `Qwen3.8-Flash` | 1M | ? |
|
|
315
|
+
| Kimi-K3.1-Pro | `Kimi-K3.1-Pro` | 1M | ? |
|
|
316
|
+
| Kimi-K2.8-Preview | `Kimi-K2.8-Preview` | 1M | ? |
|
|
317
|
+
| GLM-5.3 | `GLM-5.3` | 1M | ? |
|
|
318
|
+
| GLM-5.3-Flash | `GLM-5.3-Flash` | 1M | ? |
|
|
319
|
+
| DeepSeek-V4-Pro | `DeepSeek-V4-Pro` | 1M | ? |
|
|
320
|
+
| DeepSeek-Flash | `DeepSeek-Flash` | 1M | ? |
|
|
321
|
+
| MiniMax-M3.1-Flash | `MiniMax-M3.1-Flash` | 1M | ? |
|
|
322
|
+
|
|
323
|
+
Retired in the 2026-11 catalog refresh: QoDuck (`qoduck`/`QoDuck`, both
|
|
324
|
+
regions) and the Qwen3.7 / GLM-5.2 / Kimi-K3 / Kimi-K2.7-Code / MiniMax-M3 /
|
|
325
|
+
DeepSeek-V4-Flash families. Run `/qoder-cn.models` or `/qoder.models` for the
|
|
326
|
+
live list �� the tables above are the offline snapshot, not the source of truth.
|
|
303
327
|
|
|
304
328
|
```bash
|
|
305
329
|
pi --provider qoder-cn --model qwen3.8-flash
|
package/dist/index.js
CHANGED
|
@@ -696,7 +696,7 @@ var init_catalog = __esm({
|
|
|
696
696
|
api: "qoder-api",
|
|
697
697
|
provider: "qoder",
|
|
698
698
|
baseUrl: getQoderBaseUrl("global"),
|
|
699
|
-
reasoning:
|
|
699
|
+
reasoning: false,
|
|
700
700
|
supportsEffort: false,
|
|
701
701
|
input: ["text", "image"],
|
|
702
702
|
cost: ZERO_COST,
|
|
@@ -718,65 +718,65 @@ var init_catalog = __esm({
|
|
|
718
718
|
maxTokens: MAX_OUTPUT_TOKENS
|
|
719
719
|
},
|
|
720
720
|
{
|
|
721
|
-
id: "
|
|
722
|
-
upstreamKey: "
|
|
723
|
-
name: "
|
|
721
|
+
id: "Efficient",
|
|
722
|
+
upstreamKey: "efficient",
|
|
723
|
+
name: "Efficient",
|
|
724
724
|
api: "qoder-api",
|
|
725
725
|
provider: "qoder",
|
|
726
726
|
baseUrl: getQoderBaseUrl("global"),
|
|
727
|
-
reasoning:
|
|
728
|
-
supportsEffort:
|
|
727
|
+
reasoning: false,
|
|
728
|
+
supportsEffort: false,
|
|
729
729
|
input: ["text", "image"],
|
|
730
730
|
cost: ZERO_COST,
|
|
731
|
-
contextWindow:
|
|
731
|
+
contextWindow: 2e5,
|
|
732
732
|
maxTokens: MAX_OUTPUT_TOKENS
|
|
733
733
|
},
|
|
734
734
|
{
|
|
735
|
-
id: "
|
|
736
|
-
upstreamKey: "
|
|
737
|
-
name: "
|
|
735
|
+
id: "Sonus",
|
|
736
|
+
upstreamKey: "smodel",
|
|
737
|
+
name: "Sonus",
|
|
738
738
|
api: "qoder-api",
|
|
739
739
|
provider: "qoder",
|
|
740
740
|
baseUrl: getQoderBaseUrl("global"),
|
|
741
|
-
reasoning:
|
|
742
|
-
supportsEffort:
|
|
741
|
+
reasoning: true,
|
|
742
|
+
supportsEffort: true,
|
|
743
743
|
input: ["text", "image"],
|
|
744
744
|
cost: ZERO_COST,
|
|
745
745
|
contextWindow: DEFAULT_CONTEXT_WINDOW,
|
|
746
746
|
maxTokens: MAX_OUTPUT_TOKENS
|
|
747
747
|
},
|
|
748
748
|
{
|
|
749
|
-
id: "
|
|
750
|
-
upstreamKey: "
|
|
751
|
-
name: "
|
|
749
|
+
id: "Cantus",
|
|
750
|
+
upstreamKey: "cmodel",
|
|
751
|
+
name: "Cantus",
|
|
752
752
|
api: "qoder-api",
|
|
753
753
|
provider: "qoder",
|
|
754
754
|
baseUrl: getQoderBaseUrl("global"),
|
|
755
|
-
reasoning:
|
|
756
|
-
supportsEffort:
|
|
757
|
-
input: ["text"],
|
|
755
|
+
reasoning: true,
|
|
756
|
+
supportsEffort: true,
|
|
757
|
+
input: ["text", "image"],
|
|
758
758
|
cost: ZERO_COST,
|
|
759
759
|
contextWindow: DEFAULT_CONTEXT_WINDOW,
|
|
760
760
|
maxTokens: MAX_OUTPUT_TOKENS
|
|
761
761
|
},
|
|
762
762
|
{
|
|
763
|
-
id: "
|
|
764
|
-
upstreamKey: "
|
|
765
|
-
name: "
|
|
763
|
+
id: "Qwen4-Max-Preview",
|
|
764
|
+
upstreamKey: "qmodel_latest",
|
|
765
|
+
name: "Qwen4-Max-Preview",
|
|
766
766
|
api: "qoder-api",
|
|
767
767
|
provider: "qoder",
|
|
768
768
|
baseUrl: getQoderBaseUrl("global"),
|
|
769
|
-
reasoning:
|
|
770
|
-
supportsEffort:
|
|
769
|
+
reasoning: true,
|
|
770
|
+
supportsEffort: true,
|
|
771
771
|
input: ["text", "image"],
|
|
772
772
|
cost: ZERO_COST,
|
|
773
773
|
contextWindow: DEFAULT_CONTEXT_WINDOW,
|
|
774
774
|
maxTokens: MAX_OUTPUT_TOKENS
|
|
775
775
|
},
|
|
776
776
|
{
|
|
777
|
-
id: "
|
|
778
|
-
upstreamKey: "
|
|
779
|
-
name: "
|
|
777
|
+
id: "Qwen3.8-Max",
|
|
778
|
+
upstreamKey: "qmodel_38max",
|
|
779
|
+
name: "Qwen3.8-Max",
|
|
780
780
|
api: "qoder-api",
|
|
781
781
|
provider: "qoder",
|
|
782
782
|
baseUrl: getQoderBaseUrl("global"),
|
|
@@ -788,9 +788,9 @@ var init_catalog = __esm({
|
|
|
788
788
|
maxTokens: MAX_OUTPUT_TOKENS
|
|
789
789
|
},
|
|
790
790
|
{
|
|
791
|
-
id: "Qwen3.8-
|
|
792
|
-
upstreamKey: "
|
|
793
|
-
name: "Qwen3.8-
|
|
791
|
+
id: "Qwen3.8-Flash",
|
|
792
|
+
upstreamKey: "qfmodel",
|
|
793
|
+
name: "Qwen3.8-Flash",
|
|
794
794
|
api: "qoder-api",
|
|
795
795
|
provider: "qoder",
|
|
796
796
|
baseUrl: getQoderBaseUrl("global"),
|
|
@@ -802,23 +802,23 @@ var init_catalog = __esm({
|
|
|
802
802
|
maxTokens: MAX_OUTPUT_TOKENS
|
|
803
803
|
},
|
|
804
804
|
{
|
|
805
|
-
id: "
|
|
806
|
-
upstreamKey: "
|
|
807
|
-
name: "
|
|
805
|
+
id: "Kimi-K3.1-Pro",
|
|
806
|
+
upstreamKey: "kmodel_latest",
|
|
807
|
+
name: "Kimi-K3.1-Pro",
|
|
808
808
|
api: "qoder-api",
|
|
809
809
|
provider: "qoder",
|
|
810
810
|
baseUrl: getQoderBaseUrl("global"),
|
|
811
|
-
reasoning:
|
|
812
|
-
supportsEffort:
|
|
811
|
+
reasoning: true,
|
|
812
|
+
supportsEffort: true,
|
|
813
813
|
input: ["text", "image"],
|
|
814
814
|
cost: ZERO_COST,
|
|
815
815
|
contextWindow: DEFAULT_CONTEXT_WINDOW,
|
|
816
816
|
maxTokens: MAX_OUTPUT_TOKENS
|
|
817
817
|
},
|
|
818
818
|
{
|
|
819
|
-
id: "
|
|
820
|
-
upstreamKey: "
|
|
821
|
-
name: "
|
|
819
|
+
id: "Kimi-K2.8-Preview",
|
|
820
|
+
upstreamKey: "kmodel",
|
|
821
|
+
name: "Kimi-K2.8-Preview",
|
|
822
822
|
api: "qoder-api",
|
|
823
823
|
provider: "qoder",
|
|
824
824
|
baseUrl: getQoderBaseUrl("global"),
|
|
@@ -830,9 +830,9 @@ var init_catalog = __esm({
|
|
|
830
830
|
maxTokens: MAX_OUTPUT_TOKENS
|
|
831
831
|
},
|
|
832
832
|
{
|
|
833
|
-
id: "
|
|
834
|
-
upstreamKey: "
|
|
835
|
-
name: "
|
|
833
|
+
id: "GLM-5.3",
|
|
834
|
+
upstreamKey: "gmodel",
|
|
835
|
+
name: "GLM-5.3",
|
|
836
836
|
api: "qoder-api",
|
|
837
837
|
provider: "qoder",
|
|
838
838
|
baseUrl: getQoderBaseUrl("global"),
|
|
@@ -844,9 +844,9 @@ var init_catalog = __esm({
|
|
|
844
844
|
maxTokens: MAX_OUTPUT_TOKENS
|
|
845
845
|
},
|
|
846
846
|
{
|
|
847
|
-
id: "GLM-5.
|
|
848
|
-
upstreamKey: "
|
|
849
|
-
name: "GLM-5.
|
|
847
|
+
id: "GLM-5.3-Flash",
|
|
848
|
+
upstreamKey: "gfmodel",
|
|
849
|
+
name: "GLM-5.3-Flash",
|
|
850
850
|
api: "qoder-api",
|
|
851
851
|
provider: "qoder",
|
|
852
852
|
baseUrl: getQoderBaseUrl("global"),
|
|
@@ -858,43 +858,42 @@ var init_catalog = __esm({
|
|
|
858
858
|
maxTokens: MAX_OUTPUT_TOKENS
|
|
859
859
|
},
|
|
860
860
|
{
|
|
861
|
-
id: "
|
|
862
|
-
upstreamKey: "
|
|
863
|
-
name: "
|
|
861
|
+
id: "DeepSeek-V4-Pro",
|
|
862
|
+
upstreamKey: "dmodel",
|
|
863
|
+
name: "DeepSeek-V4-Pro",
|
|
864
864
|
api: "qoder-api",
|
|
865
865
|
provider: "qoder",
|
|
866
866
|
baseUrl: getQoderBaseUrl("global"),
|
|
867
|
-
reasoning:
|
|
868
|
-
supportsEffort:
|
|
867
|
+
reasoning: true,
|
|
868
|
+
supportsEffort: true,
|
|
869
869
|
input: ["text", "image"],
|
|
870
870
|
cost: ZERO_COST,
|
|
871
|
-
|
|
872
|
-
contextWindow: 256e3,
|
|
871
|
+
contextWindow: DEFAULT_CONTEXT_WINDOW,
|
|
873
872
|
maxTokens: MAX_OUTPUT_TOKENS
|
|
874
873
|
},
|
|
875
874
|
{
|
|
876
|
-
id: "
|
|
877
|
-
upstreamKey: "
|
|
878
|
-
name: "
|
|
875
|
+
id: "DeepSeek-Flash",
|
|
876
|
+
upstreamKey: "dfmodel",
|
|
877
|
+
name: "DeepSeek-Flash",
|
|
879
878
|
api: "qoder-api",
|
|
880
879
|
provider: "qoder",
|
|
881
880
|
baseUrl: getQoderBaseUrl("global"),
|
|
882
|
-
reasoning:
|
|
883
|
-
supportsEffort:
|
|
881
|
+
reasoning: true,
|
|
882
|
+
supportsEffort: true,
|
|
884
883
|
input: ["text", "image"],
|
|
885
884
|
cost: ZERO_COST,
|
|
886
885
|
contextWindow: DEFAULT_CONTEXT_WINDOW,
|
|
887
886
|
maxTokens: MAX_OUTPUT_TOKENS
|
|
888
887
|
},
|
|
889
888
|
{
|
|
890
|
-
id: "MiniMax-M3",
|
|
889
|
+
id: "MiniMax-M3.1-Flash",
|
|
891
890
|
upstreamKey: "mmodel",
|
|
892
|
-
name: "MiniMax-M3",
|
|
891
|
+
name: "MiniMax-M3.1-Flash",
|
|
893
892
|
api: "qoder-api",
|
|
894
893
|
provider: "qoder",
|
|
895
894
|
baseUrl: getQoderBaseUrl("global"),
|
|
896
|
-
reasoning:
|
|
897
|
-
supportsEffort:
|
|
895
|
+
reasoning: true,
|
|
896
|
+
supportsEffort: true,
|
|
898
897
|
input: ["text", "image"],
|
|
899
898
|
cost: ZERO_COST,
|
|
900
899
|
contextWindow: DEFAULT_CONTEXT_WINDOW,
|
|
@@ -913,10 +912,9 @@ var init_catalog = __esm({
|
|
|
913
912
|
supportsEffort: false,
|
|
914
913
|
input: ["text", "image"],
|
|
915
914
|
cost: ZERO_COST,
|
|
916
|
-
|
|
917
|
-
contextWindow: 2e5,
|
|
915
|
+
contextWindow: DEFAULT_CONTEXT_WINDOW,
|
|
918
916
|
maxTokens: MAX_OUTPUT_TOKENS,
|
|
919
|
-
description: "Qoder CN smart routing;
|
|
917
|
+
description: "Qoder CN smart routing; the 2026-11 catalog advertises 1M."
|
|
920
918
|
}
|
|
921
919
|
];
|
|
922
920
|
PI_THINKING_LEVELS = ["minimal", "low", "medium", "high", "xhigh", "max"];
|
|
@@ -3008,6 +3006,21 @@ var THINKING_TAG_VARIANTS = [
|
|
|
3008
3006
|
{ open: "<thought>", close: "</thought>" }
|
|
3009
3007
|
];
|
|
3010
3008
|
var ALL_THINKING_TAGS = THINKING_TAG_VARIANTS.flatMap((variant) => [variant.open, variant.close]);
|
|
3009
|
+
function hasAnyCompleteThinkingTag(text) {
|
|
3010
|
+
return THINKING_TAG_VARIANTS.some((variant) => text.includes(variant.open) || text.includes(variant.close));
|
|
3011
|
+
}
|
|
3012
|
+
function hasOrphanThinkingCloser(text) {
|
|
3013
|
+
for (const variant of THINKING_TAG_VARIANTS) {
|
|
3014
|
+
const closePos = text.indexOf(variant.close);
|
|
3015
|
+
if (closePos === -1) continue;
|
|
3016
|
+
const openerBefore = THINKING_TAG_VARIANTS.some((other) => {
|
|
3017
|
+
const openPos = text.indexOf(other.open);
|
|
3018
|
+
return openPos !== -1 && openPos < closePos;
|
|
3019
|
+
});
|
|
3020
|
+
if (!openerBefore) return true;
|
|
3021
|
+
}
|
|
3022
|
+
return false;
|
|
3023
|
+
}
|
|
3011
3024
|
function getTrailingPossibleTagPrefixLength(text, tag) {
|
|
3012
3025
|
const maxPrefixLength = Math.min(text.length, tag.length - 1);
|
|
3013
3026
|
for (let len = maxPrefixLength; len > 0; len--) {
|
|
@@ -3055,6 +3068,23 @@ var ThinkingTagParser = class {
|
|
|
3055
3068
|
textBlockIndex = null;
|
|
3056
3069
|
lastTextBlockIndex = null;
|
|
3057
3070
|
activeEndTag = THINKING_TAG_VARIANTS[0].close;
|
|
3071
|
+
orphanCloserMeansThinking = false;
|
|
3072
|
+
/**
|
|
3073
|
+
* Must be called before the first `processChunk`.
|
|
3074
|
+
*
|
|
3075
|
+
* When the reasoning_content channel carried no thinking body this turn
|
|
3076
|
+
* (only a bare opener tag, or nothing at all), an orphan closer in the
|
|
3077
|
+
* content stream is preceded by THINKING text rather than answer text.
|
|
3078
|
+
* stream.ts decides this once, at the first content delta, because Qoder
|
|
3079
|
+
* sends the reasoning channel before the content channel.
|
|
3080
|
+
*
|
|
3081
|
+
* Default false preserves the historical behaviour: the opener and its
|
|
3082
|
+
* thinking text arrived via reasoning_content, so text before the orphan
|
|
3083
|
+
* closer in the content stream is visible answer text.
|
|
3084
|
+
*/
|
|
3085
|
+
setOrphanCloserMeansThinking(value) {
|
|
3086
|
+
this.orphanCloserMeansThinking = value;
|
|
3087
|
+
}
|
|
3058
3088
|
processChunk(chunk) {
|
|
3059
3089
|
this.textBuffer += chunk;
|
|
3060
3090
|
while (this.textBuffer.length > 0) {
|
|
@@ -3091,6 +3121,17 @@ var ThinkingTagParser = class {
|
|
|
3091
3121
|
content: block.thinking,
|
|
3092
3122
|
partial: this.output
|
|
3093
3123
|
});
|
|
3124
|
+
} else if (this.orphanCloserMeansThinking && !this.thinkingExtracted) {
|
|
3125
|
+
this.emitThinking(this.textBuffer);
|
|
3126
|
+
if (this.thinkingBlockIndex !== null) {
|
|
3127
|
+
const block = this.output.content[this.thinkingBlockIndex];
|
|
3128
|
+
this.stream.push({
|
|
3129
|
+
type: "thinking_end",
|
|
3130
|
+
contentIndex: this.thinkingBlockIndex,
|
|
3131
|
+
content: block.thinking,
|
|
3132
|
+
partial: this.output
|
|
3133
|
+
});
|
|
3134
|
+
}
|
|
3094
3135
|
} else {
|
|
3095
3136
|
this.emitText(this.textBuffer);
|
|
3096
3137
|
}
|
|
@@ -3124,6 +3165,27 @@ var ThinkingTagParser = class {
|
|
|
3124
3165
|
return;
|
|
3125
3166
|
}
|
|
3126
3167
|
if (bestCloseVariant !== null) {
|
|
3168
|
+
if (this.orphanCloserMeansThinking && bestClosePos > 0) {
|
|
3169
|
+
const thinkingText = this.textBuffer.slice(0, bestClosePos);
|
|
3170
|
+
this.emitThinking(thinkingText);
|
|
3171
|
+
if (this.thinkingBlockIndex !== null) {
|
|
3172
|
+
const block = this.output.content[this.thinkingBlockIndex];
|
|
3173
|
+
this.stream.push({
|
|
3174
|
+
type: "thinking_end",
|
|
3175
|
+
contentIndex: this.thinkingBlockIndex,
|
|
3176
|
+
content: block.thinking,
|
|
3177
|
+
partial: this.output
|
|
3178
|
+
});
|
|
3179
|
+
this.thinkingBlockIndex = null;
|
|
3180
|
+
}
|
|
3181
|
+
this.textBuffer = this.textBuffer.slice(bestClosePos + bestCloseVariant.close.length);
|
|
3182
|
+
if (this.textBuffer.startsWith("\n\n")) this.textBuffer = this.textBuffer.slice(2);
|
|
3183
|
+
else if (this.textBuffer.startsWith("\n")) this.textBuffer = this.textBuffer.slice(1);
|
|
3184
|
+
this.thinkingExtracted = true;
|
|
3185
|
+
this.lastTextBlockIndex = this.textBlockIndex;
|
|
3186
|
+
this.textBlockIndex = null;
|
|
3187
|
+
return;
|
|
3188
|
+
}
|
|
3127
3189
|
if (bestClosePos > 0) this.emitText(this.textBuffer.slice(0, bestClosePos));
|
|
3128
3190
|
this.textBuffer = this.textBuffer.slice(bestClosePos + bestCloseVariant.close.length);
|
|
3129
3191
|
if (this.textBuffer.startsWith("\n\n")) this.textBuffer = this.textBuffer.slice(2);
|
|
@@ -3133,7 +3195,9 @@ var ThinkingTagParser = class {
|
|
|
3133
3195
|
const trailingPrefixLength = getMaxTrailingPossibleTagPrefixLength(this.textBuffer, ALL_THINKING_TAGS);
|
|
3134
3196
|
const safeLen = this.textBuffer.length - trailingPrefixLength;
|
|
3135
3197
|
if (safeLen > 0) {
|
|
3136
|
-
this.
|
|
3198
|
+
const safeText = this.textBuffer.slice(0, safeLen);
|
|
3199
|
+
if (this.orphanCloserMeansThinking) this.emitThinking(safeText);
|
|
3200
|
+
else this.emitText(safeText);
|
|
3137
3201
|
this.textBuffer = this.textBuffer.slice(safeLen);
|
|
3138
3202
|
}
|
|
3139
3203
|
}
|
|
@@ -3357,6 +3421,7 @@ function transformMessagesForQoder(messages) {
|
|
|
3357
3421
|
// src/protocol/stream.ts
|
|
3358
3422
|
var healCooldowns = /* @__PURE__ */ new Map();
|
|
3359
3423
|
var HEAL_COOLDOWN_MS = 6e4;
|
|
3424
|
+
var CONTENT_CLASSIFY_BUFFER_LIMIT = 8 * 1024;
|
|
3360
3425
|
function stableHash(prefix, ...inputs) {
|
|
3361
3426
|
const hash = crypto3.createHash("sha256");
|
|
3362
3427
|
hash.update(prefix);
|
|
@@ -3739,13 +3804,43 @@ function streamQoder(model, context, options) {
|
|
|
3739
3804
|
const toolCallsState = [];
|
|
3740
3805
|
const thinkingEnabled = options?.reasoning !== false && options?.reasoning !== "off";
|
|
3741
3806
|
const thinkingParser = thinkingEnabled ? new ThinkingTagParser(output, stream) : null;
|
|
3807
|
+
let sawDone = false;
|
|
3808
|
+
let sawFinishReason = false;
|
|
3809
|
+
let sawAnyDelta = false;
|
|
3810
|
+
let reasoningChannelHadBody = false;
|
|
3811
|
+
let contentClassificationDone = false;
|
|
3812
|
+
let contentBuffer = "";
|
|
3813
|
+
const flushContentBuffer = (final = false) => {
|
|
3814
|
+
if (!contentBuffer) return;
|
|
3815
|
+
if (!contentClassificationDone && (final || hasAnyCompleteThinkingTag(contentBuffer) || contentBuffer.length >= CONTENT_CLASSIFY_BUFFER_LIMIT)) {
|
|
3816
|
+
if (thinkingParser && !reasoningChannelHadBody && hasOrphanThinkingCloser(contentBuffer)) {
|
|
3817
|
+
thinkingParser.setOrphanCloserMeansThinking(true);
|
|
3818
|
+
}
|
|
3819
|
+
contentClassificationDone = true;
|
|
3820
|
+
}
|
|
3821
|
+
if (!contentClassificationDone) return;
|
|
3822
|
+
const pending = contentBuffer;
|
|
3823
|
+
contentBuffer = "";
|
|
3824
|
+
if (thinkingParser) thinkingParser.processChunk(pending);
|
|
3825
|
+
else emitPlainText(pending);
|
|
3826
|
+
};
|
|
3827
|
+
const emitPlainText = (text) => {
|
|
3828
|
+
if (!text) return;
|
|
3829
|
+
if (contentBlockIndex === -1) {
|
|
3830
|
+
contentBlockIndex = output.content.length;
|
|
3831
|
+
output.content.push({ type: "text", text: "" });
|
|
3832
|
+
stream.push({ type: "text_start", contentIndex: contentBlockIndex, partial: output });
|
|
3833
|
+
}
|
|
3834
|
+
const block = output.content[contentBlockIndex];
|
|
3835
|
+
block.text += text;
|
|
3836
|
+
stream.push({ type: "text_delta", contentIndex: contentBlockIndex, delta: text, partial: output });
|
|
3837
|
+
};
|
|
3742
3838
|
if (!started) {
|
|
3743
3839
|
started = true;
|
|
3744
3840
|
} else {
|
|
3745
3841
|
output.content.length = 0;
|
|
3746
3842
|
}
|
|
3747
3843
|
stream.push({ type: "start", partial: output });
|
|
3748
|
-
let sawDone = false;
|
|
3749
3844
|
while (!sawDone) {
|
|
3750
3845
|
const { done, value } = await reader.read();
|
|
3751
3846
|
if (done) break;
|
|
@@ -3796,9 +3891,11 @@ function streamQoder(model, context, options) {
|
|
|
3796
3891
|
const choice = inner.choices[0];
|
|
3797
3892
|
const delta = choice.delta;
|
|
3798
3893
|
if (delta) {
|
|
3894
|
+
sawAnyDelta = true;
|
|
3799
3895
|
if (delta.reasoning_content) {
|
|
3800
3896
|
rawReasoningTail = (rawReasoningTail + delta.reasoning_content).slice(-512);
|
|
3801
3897
|
const reasoningChunk = stripDsmlResidue(stripThinkingTags(delta.reasoning_content));
|
|
3898
|
+
if (reasoningChunk.trim().length > 0) reasoningChannelHadBody = true;
|
|
3802
3899
|
if (reasoningChunk) {
|
|
3803
3900
|
if (thinkingBlockIndex === -1) {
|
|
3804
3901
|
thinkingBlockIndex = output.content.length;
|
|
@@ -3827,24 +3924,14 @@ function streamQoder(model, context, options) {
|
|
|
3827
3924
|
thinkingBlockIndex = -1;
|
|
3828
3925
|
}
|
|
3829
3926
|
if (thinkingParser) {
|
|
3830
|
-
|
|
3927
|
+
contentBuffer += delta.content;
|
|
3928
|
+
flushContentBuffer();
|
|
3831
3929
|
} else {
|
|
3832
|
-
|
|
3833
|
-
contentBlockIndex = output.content.length;
|
|
3834
|
-
output.content.push({ type: "text", text: "" });
|
|
3835
|
-
stream.push({ type: "text_start", contentIndex: contentBlockIndex, partial: output });
|
|
3836
|
-
}
|
|
3837
|
-
const block = output.content[contentBlockIndex];
|
|
3838
|
-
block.text += delta.content;
|
|
3839
|
-
stream.push({
|
|
3840
|
-
type: "text_delta",
|
|
3841
|
-
contentIndex: contentBlockIndex,
|
|
3842
|
-
delta: delta.content,
|
|
3843
|
-
partial: output
|
|
3844
|
-
});
|
|
3930
|
+
emitPlainText(delta.content);
|
|
3845
3931
|
}
|
|
3846
3932
|
}
|
|
3847
3933
|
if (delta.tool_calls && Array.isArray(delta.tool_calls)) {
|
|
3934
|
+
flushContentBuffer(true);
|
|
3848
3935
|
for (const tc of delta.tool_calls) {
|
|
3849
3936
|
const idx = tc.index ?? 0;
|
|
3850
3937
|
if (!toolCallsState[idx]) {
|
|
@@ -3883,6 +3970,7 @@ function streamQoder(model, context, options) {
|
|
|
3883
3970
|
}
|
|
3884
3971
|
}
|
|
3885
3972
|
if (choice.finish_reason) {
|
|
3973
|
+
sawFinishReason = true;
|
|
3886
3974
|
output.stopReason = choice.finish_reason;
|
|
3887
3975
|
}
|
|
3888
3976
|
}
|
|
@@ -3899,6 +3987,7 @@ function streamQoder(model, context, options) {
|
|
|
3899
3987
|
}
|
|
3900
3988
|
await reader.cancel().catch(() => {
|
|
3901
3989
|
});
|
|
3990
|
+
flushContentBuffer(true);
|
|
3902
3991
|
if (thinkingParser) {
|
|
3903
3992
|
thinkingParser.finalize();
|
|
3904
3993
|
}
|
|
@@ -3944,6 +4033,32 @@ function streamQoder(model, context, options) {
|
|
|
3944
4033
|
stream.end();
|
|
3945
4034
|
return;
|
|
3946
4035
|
}
|
|
4036
|
+
if (!sawAnyDelta) {
|
|
4037
|
+
if (!options?.signal?.aborted) {
|
|
4038
|
+
output.stopReason = "error";
|
|
4039
|
+
output.errorMessage = "Qoder server error: incomplete stream (no content deltas received)";
|
|
4040
|
+
stream.push({ type: "error", reason: "error", error: output });
|
|
4041
|
+
stream.end();
|
|
4042
|
+
return;
|
|
4043
|
+
}
|
|
4044
|
+
output.stopReason = "aborted";
|
|
4045
|
+
stream.push({ type: "error", reason: "aborted", error: output });
|
|
4046
|
+
stream.end();
|
|
4047
|
+
return;
|
|
4048
|
+
}
|
|
4049
|
+
if (!sawDone && !sawFinishReason) {
|
|
4050
|
+
if (!options?.signal?.aborted) {
|
|
4051
|
+
output.stopReason = "error";
|
|
4052
|
+
output.errorMessage = "Qoder server error: incomplete stream (response ended without [DONE] or finish_reason)";
|
|
4053
|
+
stream.push({ type: "error", reason: "error", error: output });
|
|
4054
|
+
stream.end();
|
|
4055
|
+
return;
|
|
4056
|
+
}
|
|
4057
|
+
output.stopReason = "aborted";
|
|
4058
|
+
stream.push({ type: "error", reason: "aborted", error: output });
|
|
4059
|
+
stream.end();
|
|
4060
|
+
return;
|
|
4061
|
+
}
|
|
3947
4062
|
stream.push({
|
|
3948
4063
|
type: "done",
|
|
3949
4064
|
reason: output.stopReason,
|
package/package.json
CHANGED