pi-free 2.2.6 → 2.2.8
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 +31 -4
- package/README.md +3 -2
- package/config.ts +815 -803
- package/constants.ts +113 -121
- package/index.ts +6 -7
- package/lib/built-in-toggle.ts +426 -426
- package/lib/model-detection.ts +1 -1
- package/package.json +74 -74
- package/provider-helper.ts +29 -7
- package/providers/anyapi/anyapi.ts +270 -0
- package/providers/cline/cline-auth.ts +473 -473
- package/providers/cline/cline-xml-bridge.ts +1506 -1506
- package/providers/cline/cline.ts +205 -205
- package/providers/dynamic-built-in/index.ts +718 -718
- package/providers/kilo/kilo-auth.ts +152 -155
- package/providers/kilo/kilo.ts +14 -13
- package/providers/opencode-session.ts +514 -463
- package/providers/qoder/auth.ts +548 -548
- package/providers/qoder/qoder.ts +119 -119
- package/providers/qoder/stream.ts +585 -585
- package/providers/qoder/thinking-parser.ts +251 -251
- package/providers/qoder/transform.ts +192 -192
- package/providers/tokenrouter/tokenrouter.ts +636 -637
- package/providers/qwen/qwen-auth.ts +0 -416
- package/providers/qwen/qwen-models.ts +0 -101
- package/providers/qwen/qwen.ts +0 -200
|
@@ -1,192 +1,192 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Message format transformation between Pi's internal format and Qoder's
|
|
3
|
-
* proprietary API format.
|
|
4
|
-
*
|
|
5
|
-
* Pi uses a structured message format with typed content blocks (TextContent,
|
|
6
|
-
* ThinkingContent, ImageContent, ToolCall). Qoder's API expects an
|
|
7
|
-
* OpenAI-compatible format with some custom extensions.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import type {
|
|
11
|
-
AssistantMessage,
|
|
12
|
-
ImageContent,
|
|
13
|
-
Message,
|
|
14
|
-
TextContent,
|
|
15
|
-
ThinkingContent,
|
|
16
|
-
Tool,
|
|
17
|
-
ToolCall,
|
|
18
|
-
ToolResultMessage,
|
|
19
|
-
} from "@earendil-works/pi-ai";
|
|
20
|
-
|
|
21
|
-
/** OpenAI-style tool definition sent to the Qoder API. */
|
|
22
|
-
interface QoderTool {
|
|
23
|
-
type: "function";
|
|
24
|
-
function: {
|
|
25
|
-
name: string;
|
|
26
|
-
description?: string;
|
|
27
|
-
parameters?: unknown;
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/** OpenAI-style tool call within an assistant message. */
|
|
32
|
-
interface QoderToolCall {
|
|
33
|
-
id?: string;
|
|
34
|
-
type: "function";
|
|
35
|
-
function: { name?: string; arguments: string };
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
type QoderTextPart = { type: "text"; text: string };
|
|
39
|
-
type QoderImagePart = { type: "image_url"; image_url: { url: string } };
|
|
40
|
-
type QoderContent = string | Array<QoderTextPart | QoderImagePart>;
|
|
41
|
-
|
|
42
|
-
/** OpenAI-style message sent to the Qoder API. */
|
|
43
|
-
interface QoderMessage {
|
|
44
|
-
role: "user" | "assistant" | "tool" | "system";
|
|
45
|
-
content: QoderContent | null;
|
|
46
|
-
tool_calls?: QoderToolCall[];
|
|
47
|
-
tool_call_id?: string;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Extract text content from a message, joining all text/thinking blocks.
|
|
52
|
-
*/
|
|
53
|
-
export function getContentText(msg: Message): string {
|
|
54
|
-
if (typeof msg.content === "string") return msg.content;
|
|
55
|
-
if (Array.isArray(msg.content)) {
|
|
56
|
-
return msg.content
|
|
57
|
-
.map((c) => {
|
|
58
|
-
if (c.type === "text") return (c as TextContent).text;
|
|
59
|
-
if (c.type === "thinking") return (c as ThinkingContent).thinking;
|
|
60
|
-
return "";
|
|
61
|
-
})
|
|
62
|
-
.join("");
|
|
63
|
-
}
|
|
64
|
-
return "";
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Convert Pi's Tool[] to Qoder's tool format.
|
|
69
|
-
*/
|
|
70
|
-
export function transformTools(tools: Tool[]): QoderTool[] {
|
|
71
|
-
return tools.map((t) => ({
|
|
72
|
-
type: "function",
|
|
73
|
-
function: {
|
|
74
|
-
name: t.name,
|
|
75
|
-
description: t.description,
|
|
76
|
-
parameters: t.parameters,
|
|
77
|
-
},
|
|
78
|
-
}));
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Convert Pi's internal messages to Qoder's expected format.
|
|
83
|
-
*
|
|
84
|
-
* Handles:
|
|
85
|
-
* - User messages with text and/or image content
|
|
86
|
-
* - Assistant messages with text, thinking, and tool calls
|
|
87
|
-
* - Tool result messages
|
|
88
|
-
* - Skips error/aborted assistant messages
|
|
89
|
-
*/
|
|
90
|
-
export function transformMessagesForQoder(messages: Message[]): QoderMessage[] {
|
|
91
|
-
const normalizedMessages: QoderMessage[] = [];
|
|
92
|
-
for (const msg of messages) {
|
|
93
|
-
if (isSkippableMessage(msg)) continue;
|
|
94
|
-
if (msg.role === "user") {
|
|
95
|
-
normalizedMessages.push(transformUserMessage(msg));
|
|
96
|
-
} else if (msg.role === "assistant") {
|
|
97
|
-
normalizedMessages.push(
|
|
98
|
-
transformAssistantMessage(msg as AssistantMessage),
|
|
99
|
-
);
|
|
100
|
-
} else if (msg.role === "toolResult") {
|
|
101
|
-
normalizedMessages.push(
|
|
102
|
-
transformToolResultMessage(msg as ToolResultMessage),
|
|
103
|
-
);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
return normalizedMessages;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
function isSkippableMessage(msg: Message): boolean {
|
|
110
|
-
if (msg.role !== "assistant") return false;
|
|
111
|
-
const am = msg as AssistantMessage;
|
|
112
|
-
return am.stopReason === "error" || am.stopReason === "aborted";
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function transformUserMessage(msg: Message): QoderMessage {
|
|
116
|
-
let content: QoderContent = "";
|
|
117
|
-
if (typeof msg.content === "string") {
|
|
118
|
-
content = msg.content;
|
|
119
|
-
} else if (Array.isArray(msg.content)) {
|
|
120
|
-
const hasImage = msg.content.some((c) => c.type === "image");
|
|
121
|
-
if (hasImage) {
|
|
122
|
-
content = msg.content
|
|
123
|
-
.map((c): QoderTextPart | QoderImagePart | null => {
|
|
124
|
-
if (c.type === "text") {
|
|
125
|
-
return { type: "text", text: (c as TextContent).text };
|
|
126
|
-
}
|
|
127
|
-
if (c.type === "image") {
|
|
128
|
-
const img = c as ImageContent;
|
|
129
|
-
return {
|
|
130
|
-
type: "image_url",
|
|
131
|
-
image_url: { url: `data:${img.mimeType};base64,${img.data}` },
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
return null;
|
|
135
|
-
})
|
|
136
|
-
.filter((p): p is QoderTextPart | QoderImagePart => p !== null);
|
|
137
|
-
} else {
|
|
138
|
-
content = getContentText(msg);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
return { role: "user", content };
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
function transformAssistantMessage(am: AssistantMessage): QoderMessage {
|
|
145
|
-
let content = "";
|
|
146
|
-
const toolCalls: QoderToolCall[] = [];
|
|
147
|
-
|
|
148
|
-
if (Array.isArray(am.content)) {
|
|
149
|
-
for (const block of am.content) {
|
|
150
|
-
if (block.type === "text") {
|
|
151
|
-
content += (block as TextContent).text;
|
|
152
|
-
} else if (block.type === "thinking") {
|
|
153
|
-
// Skip sending previous thinking blocks for the OpenAI-compatible API.
|
|
154
|
-
// Qoder current API produces reasoning_content natively during streaming;
|
|
155
|
-
// re-emitting prior reasoning as <thinking> tags is unnecessary and
|
|
156
|
-
// can confuse newer model versions.
|
|
157
|
-
} else if (block.type === "toolCall") {
|
|
158
|
-
const tc = block as ToolCall;
|
|
159
|
-
toolCalls.push({
|
|
160
|
-
id: tc.id,
|
|
161
|
-
type: "function",
|
|
162
|
-
function: {
|
|
163
|
-
name: tc.name,
|
|
164
|
-
arguments:
|
|
165
|
-
typeof tc.arguments === "string"
|
|
166
|
-
? tc.arguments
|
|
167
|
-
: JSON.stringify(tc.arguments),
|
|
168
|
-
},
|
|
169
|
-
});
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
} else {
|
|
173
|
-
content = am.content || "";
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
const mapped: QoderMessage = {
|
|
177
|
-
role: "assistant",
|
|
178
|
-
content: content || null,
|
|
179
|
-
};
|
|
180
|
-
if (toolCalls.length > 0) {
|
|
181
|
-
mapped.tool_calls = toolCalls;
|
|
182
|
-
}
|
|
183
|
-
return mapped;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
function transformToolResultMessage(tr: ToolResultMessage): QoderMessage {
|
|
187
|
-
return {
|
|
188
|
-
role: "tool",
|
|
189
|
-
tool_call_id: tr.toolCallId,
|
|
190
|
-
content: getContentText(tr),
|
|
191
|
-
};
|
|
192
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Message format transformation between Pi's internal format and Qoder's
|
|
3
|
+
* proprietary API format.
|
|
4
|
+
*
|
|
5
|
+
* Pi uses a structured message format with typed content blocks (TextContent,
|
|
6
|
+
* ThinkingContent, ImageContent, ToolCall). Qoder's API expects an
|
|
7
|
+
* OpenAI-compatible format with some custom extensions.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type {
|
|
11
|
+
AssistantMessage,
|
|
12
|
+
ImageContent,
|
|
13
|
+
Message,
|
|
14
|
+
TextContent,
|
|
15
|
+
ThinkingContent,
|
|
16
|
+
Tool,
|
|
17
|
+
ToolCall,
|
|
18
|
+
ToolResultMessage,
|
|
19
|
+
} from "@earendil-works/pi-ai/compat";
|
|
20
|
+
|
|
21
|
+
/** OpenAI-style tool definition sent to the Qoder API. */
|
|
22
|
+
interface QoderTool {
|
|
23
|
+
type: "function";
|
|
24
|
+
function: {
|
|
25
|
+
name: string;
|
|
26
|
+
description?: string;
|
|
27
|
+
parameters?: unknown;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** OpenAI-style tool call within an assistant message. */
|
|
32
|
+
interface QoderToolCall {
|
|
33
|
+
id?: string;
|
|
34
|
+
type: "function";
|
|
35
|
+
function: { name?: string; arguments: string };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
type QoderTextPart = { type: "text"; text: string };
|
|
39
|
+
type QoderImagePart = { type: "image_url"; image_url: { url: string } };
|
|
40
|
+
type QoderContent = string | Array<QoderTextPart | QoderImagePart>;
|
|
41
|
+
|
|
42
|
+
/** OpenAI-style message sent to the Qoder API. */
|
|
43
|
+
interface QoderMessage {
|
|
44
|
+
role: "user" | "assistant" | "tool" | "system";
|
|
45
|
+
content: QoderContent | null;
|
|
46
|
+
tool_calls?: QoderToolCall[];
|
|
47
|
+
tool_call_id?: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Extract text content from a message, joining all text/thinking blocks.
|
|
52
|
+
*/
|
|
53
|
+
export function getContentText(msg: Message): string {
|
|
54
|
+
if (typeof msg.content === "string") return msg.content;
|
|
55
|
+
if (Array.isArray(msg.content)) {
|
|
56
|
+
return msg.content
|
|
57
|
+
.map((c) => {
|
|
58
|
+
if (c.type === "text") return (c as TextContent).text;
|
|
59
|
+
if (c.type === "thinking") return (c as ThinkingContent).thinking;
|
|
60
|
+
return "";
|
|
61
|
+
})
|
|
62
|
+
.join("");
|
|
63
|
+
}
|
|
64
|
+
return "";
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Convert Pi's Tool[] to Qoder's tool format.
|
|
69
|
+
*/
|
|
70
|
+
export function transformTools(tools: Tool[]): QoderTool[] {
|
|
71
|
+
return tools.map((t) => ({
|
|
72
|
+
type: "function",
|
|
73
|
+
function: {
|
|
74
|
+
name: t.name,
|
|
75
|
+
description: t.description,
|
|
76
|
+
parameters: t.parameters,
|
|
77
|
+
},
|
|
78
|
+
}));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Convert Pi's internal messages to Qoder's expected format.
|
|
83
|
+
*
|
|
84
|
+
* Handles:
|
|
85
|
+
* - User messages with text and/or image content
|
|
86
|
+
* - Assistant messages with text, thinking, and tool calls
|
|
87
|
+
* - Tool result messages
|
|
88
|
+
* - Skips error/aborted assistant messages
|
|
89
|
+
*/
|
|
90
|
+
export function transformMessagesForQoder(messages: Message[]): QoderMessage[] {
|
|
91
|
+
const normalizedMessages: QoderMessage[] = [];
|
|
92
|
+
for (const msg of messages) {
|
|
93
|
+
if (isSkippableMessage(msg)) continue;
|
|
94
|
+
if (msg.role === "user") {
|
|
95
|
+
normalizedMessages.push(transformUserMessage(msg));
|
|
96
|
+
} else if (msg.role === "assistant") {
|
|
97
|
+
normalizedMessages.push(
|
|
98
|
+
transformAssistantMessage(msg as AssistantMessage),
|
|
99
|
+
);
|
|
100
|
+
} else if (msg.role === "toolResult") {
|
|
101
|
+
normalizedMessages.push(
|
|
102
|
+
transformToolResultMessage(msg as ToolResultMessage),
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return normalizedMessages;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function isSkippableMessage(msg: Message): boolean {
|
|
110
|
+
if (msg.role !== "assistant") return false;
|
|
111
|
+
const am = msg as AssistantMessage;
|
|
112
|
+
return am.stopReason === "error" || am.stopReason === "aborted";
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function transformUserMessage(msg: Message): QoderMessage {
|
|
116
|
+
let content: QoderContent = "";
|
|
117
|
+
if (typeof msg.content === "string") {
|
|
118
|
+
content = msg.content;
|
|
119
|
+
} else if (Array.isArray(msg.content)) {
|
|
120
|
+
const hasImage = msg.content.some((c) => c.type === "image");
|
|
121
|
+
if (hasImage) {
|
|
122
|
+
content = msg.content
|
|
123
|
+
.map((c): QoderTextPart | QoderImagePart | null => {
|
|
124
|
+
if (c.type === "text") {
|
|
125
|
+
return { type: "text", text: (c as TextContent).text };
|
|
126
|
+
}
|
|
127
|
+
if (c.type === "image") {
|
|
128
|
+
const img = c as ImageContent;
|
|
129
|
+
return {
|
|
130
|
+
type: "image_url",
|
|
131
|
+
image_url: { url: `data:${img.mimeType};base64,${img.data}` },
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
return null;
|
|
135
|
+
})
|
|
136
|
+
.filter((p): p is QoderTextPart | QoderImagePart => p !== null);
|
|
137
|
+
} else {
|
|
138
|
+
content = getContentText(msg);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return { role: "user", content };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function transformAssistantMessage(am: AssistantMessage): QoderMessage {
|
|
145
|
+
let content = "";
|
|
146
|
+
const toolCalls: QoderToolCall[] = [];
|
|
147
|
+
|
|
148
|
+
if (Array.isArray(am.content)) {
|
|
149
|
+
for (const block of am.content) {
|
|
150
|
+
if (block.type === "text") {
|
|
151
|
+
content += (block as TextContent).text;
|
|
152
|
+
} else if (block.type === "thinking") {
|
|
153
|
+
// Skip sending previous thinking blocks for the OpenAI-compatible API.
|
|
154
|
+
// Qoder current API produces reasoning_content natively during streaming;
|
|
155
|
+
// re-emitting prior reasoning as <thinking> tags is unnecessary and
|
|
156
|
+
// can confuse newer model versions.
|
|
157
|
+
} else if (block.type === "toolCall") {
|
|
158
|
+
const tc = block as ToolCall;
|
|
159
|
+
toolCalls.push({
|
|
160
|
+
id: tc.id,
|
|
161
|
+
type: "function",
|
|
162
|
+
function: {
|
|
163
|
+
name: tc.name,
|
|
164
|
+
arguments:
|
|
165
|
+
typeof tc.arguments === "string"
|
|
166
|
+
? tc.arguments
|
|
167
|
+
: JSON.stringify(tc.arguments),
|
|
168
|
+
},
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
} else {
|
|
173
|
+
content = am.content || "";
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const mapped: QoderMessage = {
|
|
177
|
+
role: "assistant",
|
|
178
|
+
content: content || null,
|
|
179
|
+
};
|
|
180
|
+
if (toolCalls.length > 0) {
|
|
181
|
+
mapped.tool_calls = toolCalls;
|
|
182
|
+
}
|
|
183
|
+
return mapped;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function transformToolResultMessage(tr: ToolResultMessage): QoderMessage {
|
|
187
|
+
return {
|
|
188
|
+
role: "tool",
|
|
189
|
+
tool_call_id: tr.toolCallId,
|
|
190
|
+
content: getContentText(tr),
|
|
191
|
+
};
|
|
192
|
+
}
|