@f5-sales-demo/pi-ai 19.51.2
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 +1997 -0
- package/README.md +1160 -0
- package/package.json +135 -0
- package/src/api-registry.ts +95 -0
- package/src/auth-storage.ts +2694 -0
- package/src/cli.ts +493 -0
- package/src/index.ts +42 -0
- package/src/model-cache.ts +97 -0
- package/src/model-manager.ts +349 -0
- package/src/model-thinking.ts +561 -0
- package/src/models.json +49439 -0
- package/src/models.json.d.ts +9 -0
- package/src/models.ts +56 -0
- package/src/prompts/turn-aborted-guidance.md +4 -0
- package/src/provider-details.ts +81 -0
- package/src/provider-models/descriptors.ts +285 -0
- package/src/provider-models/google.ts +90 -0
- package/src/provider-models/index.ts +4 -0
- package/src/provider-models/openai-compat.ts +2074 -0
- package/src/provider-models/special.ts +106 -0
- package/src/providers/amazon-bedrock.ts +706 -0
- package/src/providers/anthropic.ts +1682 -0
- package/src/providers/azure-openai-responses.ts +391 -0
- package/src/providers/cursor/gen/agent_pb.ts +15274 -0
- package/src/providers/cursor/proto/agent.proto +3526 -0
- package/src/providers/cursor/proto/buf.gen.yaml +6 -0
- package/src/providers/cursor/proto/buf.yaml +17 -0
- package/src/providers/cursor.ts +2218 -0
- package/src/providers/github-copilot-headers.ts +140 -0
- package/src/providers/gitlab-duo.ts +381 -0
- package/src/providers/google-gemini-cli.ts +1133 -0
- package/src/providers/google-shared.ts +354 -0
- package/src/providers/google-vertex.ts +436 -0
- package/src/providers/google.ts +381 -0
- package/src/providers/kimi.ts +151 -0
- package/src/providers/openai-codex/constants.ts +43 -0
- package/src/providers/openai-codex/request-transformer.ts +158 -0
- package/src/providers/openai-codex/response-handler.ts +81 -0
- package/src/providers/openai-codex-responses.ts +2345 -0
- package/src/providers/openai-completions-compat.ts +159 -0
- package/src/providers/openai-completions.ts +1290 -0
- package/src/providers/openai-responses-shared.ts +452 -0
- package/src/providers/openai-responses.ts +519 -0
- package/src/providers/register-builtins.ts +329 -0
- package/src/providers/synthetic.ts +154 -0
- package/src/providers/transform-messages.ts +234 -0
- package/src/rate-limit-utils.ts +84 -0
- package/src/stream.ts +728 -0
- package/src/types.ts +546 -0
- package/src/usage/claude.ts +337 -0
- package/src/usage/gemini.ts +248 -0
- package/src/usage/github-copilot.ts +421 -0
- package/src/usage/google-antigravity.ts +200 -0
- package/src/usage/kimi.ts +286 -0
- package/src/usage/minimax-code.ts +31 -0
- package/src/usage/openai-codex.ts +387 -0
- package/src/usage/zai.ts +247 -0
- package/src/usage.ts +130 -0
- package/src/utils/abort.ts +36 -0
- package/src/utils/anthropic-auth.ts +293 -0
- package/src/utils/discovery/antigravity.ts +261 -0
- package/src/utils/discovery/codex.ts +371 -0
- package/src/utils/discovery/cursor.ts +306 -0
- package/src/utils/discovery/gemini.ts +248 -0
- package/src/utils/discovery/index.ts +5 -0
- package/src/utils/discovery/openai-compatible.ts +224 -0
- package/src/utils/event-stream.ts +209 -0
- package/src/utils/http-inspector.ts +165 -0
- package/src/utils/idle-iterator.ts +176 -0
- package/src/utils/json-parse.ts +28 -0
- package/src/utils/oauth/alibaba-coding-plan.ts +59 -0
- package/src/utils/oauth/anthropic.ts +134 -0
- package/src/utils/oauth/api-key-validation.ts +92 -0
- package/src/utils/oauth/callback-server.ts +276 -0
- package/src/utils/oauth/cerebras.ts +59 -0
- package/src/utils/oauth/cloudflare-ai-gateway.ts +48 -0
- package/src/utils/oauth/cursor.ts +157 -0
- package/src/utils/oauth/github-copilot.ts +358 -0
- package/src/utils/oauth/gitlab-duo.ts +123 -0
- package/src/utils/oauth/google-antigravity.ts +275 -0
- package/src/utils/oauth/google-gemini-cli.ts +334 -0
- package/src/utils/oauth/huggingface.ts +62 -0
- package/src/utils/oauth/index.ts +512 -0
- package/src/utils/oauth/kagi.ts +47 -0
- package/src/utils/oauth/kilo.ts +87 -0
- package/src/utils/oauth/kimi.ts +251 -0
- package/src/utils/oauth/litellm.ts +81 -0
- package/src/utils/oauth/lm-studio.ts +40 -0
- package/src/utils/oauth/minimax-code.ts +78 -0
- package/src/utils/oauth/moonshot.ts +59 -0
- package/src/utils/oauth/nanogpt.ts +51 -0
- package/src/utils/oauth/nvidia.ts +70 -0
- package/src/utils/oauth/oauth.html +199 -0
- package/src/utils/oauth/ollama.ts +47 -0
- package/src/utils/oauth/openai-codex.ts +190 -0
- package/src/utils/oauth/opencode.ts +49 -0
- package/src/utils/oauth/parallel.ts +46 -0
- package/src/utils/oauth/perplexity.ts +200 -0
- package/src/utils/oauth/pkce.ts +18 -0
- package/src/utils/oauth/qianfan.ts +58 -0
- package/src/utils/oauth/qwen-portal.ts +60 -0
- package/src/utils/oauth/synthetic.ts +60 -0
- package/src/utils/oauth/tavily.ts +46 -0
- package/src/utils/oauth/together.ts +59 -0
- package/src/utils/oauth/types.ts +89 -0
- package/src/utils/oauth/venice.ts +59 -0
- package/src/utils/oauth/vercel-ai-gateway.ts +47 -0
- package/src/utils/oauth/vllm.ts +40 -0
- package/src/utils/oauth/xiaomi.ts +88 -0
- package/src/utils/oauth/zai.ts +60 -0
- package/src/utils/oauth/zenmux.ts +51 -0
- package/src/utils/overflow.ts +134 -0
- package/src/utils/retry-after.ts +110 -0
- package/src/utils/retry.ts +93 -0
- package/src/utils/schema/CONSTRAINTS.md +160 -0
- package/src/utils/schema/adapt.ts +20 -0
- package/src/utils/schema/compatibility.ts +397 -0
- package/src/utils/schema/dereference.ts +93 -0
- package/src/utils/schema/equality.ts +93 -0
- package/src/utils/schema/fields.ts +147 -0
- package/src/utils/schema/index.ts +9 -0
- package/src/utils/schema/normalize-cca.ts +479 -0
- package/src/utils/schema/sanitize-google.ts +212 -0
- package/src/utils/schema/strict-mode.ts +385 -0
- package/src/utils/schema/types.ts +5 -0
- package/src/utils/tool-choice.ts +81 -0
- package/src/utils/validation.ts +664 -0
- package/src/utils.ts +147 -0
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type GenerateContentConfig,
|
|
3
|
+
type GenerateContentParameters,
|
|
4
|
+
GoogleGenAI,
|
|
5
|
+
type ThinkingConfig,
|
|
6
|
+
} from "@google/genai";
|
|
7
|
+
import { calculateCost } from "../models";
|
|
8
|
+
import { getEnvApiKey } from "../stream";
|
|
9
|
+
import type {
|
|
10
|
+
Api,
|
|
11
|
+
AssistantMessage,
|
|
12
|
+
Context,
|
|
13
|
+
Model,
|
|
14
|
+
StreamFunction,
|
|
15
|
+
StreamOptions,
|
|
16
|
+
TextContent,
|
|
17
|
+
ThinkingContent,
|
|
18
|
+
ToolCall,
|
|
19
|
+
} from "../types";
|
|
20
|
+
import { AssistantMessageEventStream } from "../utils/event-stream";
|
|
21
|
+
import { finalizeErrorMessage, type RawHttpRequestDump } from "../utils/http-inspector";
|
|
22
|
+
import type { GoogleThinkingLevel } from "./google-gemini-cli";
|
|
23
|
+
import {
|
|
24
|
+
convertMessages,
|
|
25
|
+
convertTools,
|
|
26
|
+
isThinkingPart,
|
|
27
|
+
mapStopReason,
|
|
28
|
+
mapToolChoice,
|
|
29
|
+
retainThoughtSignature,
|
|
30
|
+
} from "./google-shared";
|
|
31
|
+
|
|
32
|
+
export interface GoogleOptions extends StreamOptions {
|
|
33
|
+
toolChoice?: "auto" | "none" | "any";
|
|
34
|
+
thinking?: {
|
|
35
|
+
enabled: boolean;
|
|
36
|
+
budgetTokens?: number; // -1 for dynamic, 0 to disable
|
|
37
|
+
level?: GoogleThinkingLevel;
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
interface GoogleSamplingConfig extends GenerateContentConfig {
|
|
42
|
+
topP?: number;
|
|
43
|
+
topK?: number;
|
|
44
|
+
minP?: number;
|
|
45
|
+
presencePenalty?: number;
|
|
46
|
+
repetitionPenalty?: number;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Counter for generating unique tool call IDs
|
|
50
|
+
let toolCallCounter = 0;
|
|
51
|
+
|
|
52
|
+
export const streamGoogle: StreamFunction<"google-generative-ai"> = (
|
|
53
|
+
model: Model<"google-generative-ai">,
|
|
54
|
+
context: Context,
|
|
55
|
+
options?: GoogleOptions,
|
|
56
|
+
): AssistantMessageEventStream => {
|
|
57
|
+
const stream = new AssistantMessageEventStream();
|
|
58
|
+
|
|
59
|
+
(async () => {
|
|
60
|
+
const startTime = Date.now();
|
|
61
|
+
let firstTokenTime: number | undefined;
|
|
62
|
+
|
|
63
|
+
const output: AssistantMessage = {
|
|
64
|
+
role: "assistant",
|
|
65
|
+
content: [],
|
|
66
|
+
api: "google-generative-ai" as Api,
|
|
67
|
+
provider: model.provider,
|
|
68
|
+
model: model.id,
|
|
69
|
+
usage: {
|
|
70
|
+
input: 0,
|
|
71
|
+
output: 0,
|
|
72
|
+
cacheRead: 0,
|
|
73
|
+
cacheWrite: 0,
|
|
74
|
+
totalTokens: 0,
|
|
75
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
76
|
+
},
|
|
77
|
+
stopReason: "stop",
|
|
78
|
+
timestamp: Date.now(),
|
|
79
|
+
};
|
|
80
|
+
let rawRequestDump: RawHttpRequestDump | undefined;
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
const apiKey = options?.apiKey || getEnvApiKey(model.provider);
|
|
84
|
+
const client = createClient(model, apiKey);
|
|
85
|
+
const params = buildParams(model, context, options);
|
|
86
|
+
options?.onPayload?.(params);
|
|
87
|
+
rawRequestDump = {
|
|
88
|
+
provider: model.provider,
|
|
89
|
+
api: output.api,
|
|
90
|
+
model: model.id,
|
|
91
|
+
method: "POST",
|
|
92
|
+
url: model.baseUrl ? `${model.baseUrl}/models/${model.id}:streamGenerateContent` : undefined,
|
|
93
|
+
body: params,
|
|
94
|
+
};
|
|
95
|
+
const googleStream = await client.models.generateContentStream(params);
|
|
96
|
+
|
|
97
|
+
stream.push({ type: "start", partial: output });
|
|
98
|
+
let currentBlock: TextContent | ThinkingContent | null = null;
|
|
99
|
+
const blocks = output.content;
|
|
100
|
+
const blockIndex = () => blocks.length - 1;
|
|
101
|
+
for await (const chunk of googleStream) {
|
|
102
|
+
const candidate = chunk.candidates?.[0];
|
|
103
|
+
if (candidate?.content?.parts) {
|
|
104
|
+
for (const part of candidate.content.parts) {
|
|
105
|
+
if (part.text !== undefined) {
|
|
106
|
+
const isThinking = isThinkingPart(part);
|
|
107
|
+
if (!firstTokenTime) firstTokenTime = Date.now();
|
|
108
|
+
if (
|
|
109
|
+
!currentBlock ||
|
|
110
|
+
(isThinking && currentBlock.type !== "thinking") ||
|
|
111
|
+
(!isThinking && currentBlock.type !== "text")
|
|
112
|
+
) {
|
|
113
|
+
if (currentBlock) {
|
|
114
|
+
if (currentBlock.type === "text") {
|
|
115
|
+
stream.push({
|
|
116
|
+
type: "text_end",
|
|
117
|
+
contentIndex: blocks.length - 1,
|
|
118
|
+
content: currentBlock.text,
|
|
119
|
+
partial: output,
|
|
120
|
+
});
|
|
121
|
+
} else {
|
|
122
|
+
stream.push({
|
|
123
|
+
type: "thinking_end",
|
|
124
|
+
contentIndex: blockIndex(),
|
|
125
|
+
content: currentBlock.thinking,
|
|
126
|
+
partial: output,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (isThinking) {
|
|
131
|
+
currentBlock = { type: "thinking", thinking: "", thinkingSignature: undefined };
|
|
132
|
+
output.content.push(currentBlock);
|
|
133
|
+
stream.push({ type: "thinking_start", contentIndex: blockIndex(), partial: output });
|
|
134
|
+
} else {
|
|
135
|
+
currentBlock = { type: "text", text: "" };
|
|
136
|
+
output.content.push(currentBlock);
|
|
137
|
+
stream.push({ type: "text_start", contentIndex: blockIndex(), partial: output });
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (currentBlock.type === "thinking") {
|
|
141
|
+
currentBlock.thinking += part.text;
|
|
142
|
+
currentBlock.thinkingSignature = retainThoughtSignature(
|
|
143
|
+
currentBlock.thinkingSignature,
|
|
144
|
+
part.thoughtSignature,
|
|
145
|
+
);
|
|
146
|
+
stream.push({
|
|
147
|
+
type: "thinking_delta",
|
|
148
|
+
contentIndex: blockIndex(),
|
|
149
|
+
delta: part.text,
|
|
150
|
+
partial: output,
|
|
151
|
+
});
|
|
152
|
+
} else {
|
|
153
|
+
currentBlock.text += part.text;
|
|
154
|
+
stream.push({
|
|
155
|
+
type: "text_delta",
|
|
156
|
+
contentIndex: blockIndex(),
|
|
157
|
+
delta: part.text,
|
|
158
|
+
partial: output,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (part.functionCall) {
|
|
164
|
+
if (currentBlock) {
|
|
165
|
+
if (currentBlock.type === "text") {
|
|
166
|
+
stream.push({
|
|
167
|
+
type: "text_end",
|
|
168
|
+
contentIndex: blockIndex(),
|
|
169
|
+
content: currentBlock.text,
|
|
170
|
+
partial: output,
|
|
171
|
+
});
|
|
172
|
+
} else {
|
|
173
|
+
stream.push({
|
|
174
|
+
type: "thinking_end",
|
|
175
|
+
contentIndex: blockIndex(),
|
|
176
|
+
content: currentBlock.thinking,
|
|
177
|
+
partial: output,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
currentBlock = null;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Generate unique ID if not provided or if it's a duplicate
|
|
184
|
+
const providedId = part.functionCall.id;
|
|
185
|
+
const needsNewId =
|
|
186
|
+
!providedId || output.content.some(b => b.type === "toolCall" && b.id === providedId);
|
|
187
|
+
const toolCallId = needsNewId
|
|
188
|
+
? `${part.functionCall.name}_${Date.now()}_${++toolCallCounter}`
|
|
189
|
+
: providedId;
|
|
190
|
+
|
|
191
|
+
const toolCall: ToolCall = {
|
|
192
|
+
type: "toolCall",
|
|
193
|
+
id: toolCallId,
|
|
194
|
+
name: part.functionCall.name || "",
|
|
195
|
+
arguments: (part.functionCall.args ?? {}) as Record<string, any>,
|
|
196
|
+
...(part.thoughtSignature && { thoughtSignature: part.thoughtSignature }),
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
output.content.push(toolCall);
|
|
200
|
+
stream.push({ type: "toolcall_start", contentIndex: blockIndex(), partial: output });
|
|
201
|
+
stream.push({
|
|
202
|
+
type: "toolcall_delta",
|
|
203
|
+
contentIndex: blockIndex(),
|
|
204
|
+
delta: JSON.stringify(toolCall.arguments),
|
|
205
|
+
partial: output,
|
|
206
|
+
});
|
|
207
|
+
stream.push({ type: "toolcall_end", contentIndex: blockIndex(), toolCall, partial: output });
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (candidate?.finishReason) {
|
|
213
|
+
output.stopReason = mapStopReason(candidate.finishReason);
|
|
214
|
+
if (output.content.some(b => b.type === "toolCall")) {
|
|
215
|
+
output.stopReason = "toolUse";
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (chunk.usageMetadata) {
|
|
220
|
+
// promptTokenCount includes cachedContentTokenCount when cached content is used.
|
|
221
|
+
// Subtract to get non-cached input, matching the OpenAI convention where
|
|
222
|
+
// input = uncached prompt tokens and cacheRead = cached tokens so that
|
|
223
|
+
// input + cacheRead = total prompt tokens (no double-counting).
|
|
224
|
+
// Ref: https://ai.google.dev/api/generate-content#v1beta.GenerateContentResponse.UsageMetadata
|
|
225
|
+
const cachedTokens = chunk.usageMetadata.cachedContentTokenCount || 0;
|
|
226
|
+
output.usage = {
|
|
227
|
+
input: (chunk.usageMetadata.promptTokenCount || 0) - cachedTokens,
|
|
228
|
+
output:
|
|
229
|
+
(chunk.usageMetadata.candidatesTokenCount || 0) + (chunk.usageMetadata.thoughtsTokenCount || 0),
|
|
230
|
+
cacheRead: cachedTokens,
|
|
231
|
+
cacheWrite: 0,
|
|
232
|
+
totalTokens: chunk.usageMetadata.totalTokenCount || 0,
|
|
233
|
+
cost: {
|
|
234
|
+
input: 0,
|
|
235
|
+
output: 0,
|
|
236
|
+
cacheRead: 0,
|
|
237
|
+
cacheWrite: 0,
|
|
238
|
+
total: 0,
|
|
239
|
+
},
|
|
240
|
+
};
|
|
241
|
+
calculateCost(model, output.usage);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (currentBlock) {
|
|
246
|
+
if (currentBlock.type === "text") {
|
|
247
|
+
stream.push({
|
|
248
|
+
type: "text_end",
|
|
249
|
+
contentIndex: blockIndex(),
|
|
250
|
+
content: currentBlock.text,
|
|
251
|
+
partial: output,
|
|
252
|
+
});
|
|
253
|
+
} else {
|
|
254
|
+
stream.push({
|
|
255
|
+
type: "thinking_end",
|
|
256
|
+
contentIndex: blockIndex(),
|
|
257
|
+
content: currentBlock.thinking,
|
|
258
|
+
partial: output,
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (options?.signal?.aborted) {
|
|
264
|
+
throw new Error("Request was aborted");
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (output.stopReason === "aborted" || output.stopReason === "error") {
|
|
268
|
+
throw new Error("An unknown error occurred");
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
output.duration = Date.now() - startTime;
|
|
272
|
+
if (firstTokenTime) output.ttft = firstTokenTime - startTime;
|
|
273
|
+
stream.push({ type: "done", reason: output.stopReason, message: output });
|
|
274
|
+
stream.end();
|
|
275
|
+
} catch (error) {
|
|
276
|
+
// Remove internal index property used during streaming
|
|
277
|
+
for (const block of output.content) {
|
|
278
|
+
if ("index" in block) {
|
|
279
|
+
delete (block as { index?: number }).index;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
output.stopReason = options?.signal?.aborted ? "aborted" : "error";
|
|
283
|
+
output.errorMessage = await finalizeErrorMessage(error, rawRequestDump);
|
|
284
|
+
output.duration = Date.now() - startTime;
|
|
285
|
+
if (firstTokenTime) output.ttft = firstTokenTime - startTime;
|
|
286
|
+
stream.push({ type: "error", reason: output.stopReason, error: output });
|
|
287
|
+
stream.end();
|
|
288
|
+
}
|
|
289
|
+
})();
|
|
290
|
+
|
|
291
|
+
return stream;
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
function createClient(model: Model<"google-generative-ai">, apiKey?: string): GoogleGenAI {
|
|
295
|
+
const httpOptions: { baseUrl?: string; apiVersion?: string; headers?: Record<string, string> } = {};
|
|
296
|
+
if (model.baseUrl) {
|
|
297
|
+
httpOptions.baseUrl = model.baseUrl;
|
|
298
|
+
httpOptions.apiVersion = ""; // baseUrl already includes version path, don't append
|
|
299
|
+
}
|
|
300
|
+
if (model.headers) {
|
|
301
|
+
httpOptions.headers = model.headers;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
return new GoogleGenAI({
|
|
305
|
+
apiKey,
|
|
306
|
+
httpOptions: Object.keys(httpOptions).length > 0 ? httpOptions : undefined,
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function buildParams(
|
|
311
|
+
model: Model<"google-generative-ai">,
|
|
312
|
+
context: Context,
|
|
313
|
+
options: GoogleOptions = {},
|
|
314
|
+
): GenerateContentParameters {
|
|
315
|
+
const contents = convertMessages(model, context);
|
|
316
|
+
|
|
317
|
+
const generationConfig: GoogleSamplingConfig = {};
|
|
318
|
+
if (options.temperature !== undefined) {
|
|
319
|
+
generationConfig.temperature = options.temperature;
|
|
320
|
+
}
|
|
321
|
+
if (options.maxTokens !== undefined) {
|
|
322
|
+
generationConfig.maxOutputTokens = options.maxTokens;
|
|
323
|
+
}
|
|
324
|
+
if (options.topP !== undefined) {
|
|
325
|
+
generationConfig.topP = options.topP;
|
|
326
|
+
}
|
|
327
|
+
if (options.topK !== undefined) {
|
|
328
|
+
generationConfig.topK = options.topK;
|
|
329
|
+
}
|
|
330
|
+
if (options.minP !== undefined) {
|
|
331
|
+
generationConfig.minP = options.minP;
|
|
332
|
+
}
|
|
333
|
+
if (options.presencePenalty !== undefined) {
|
|
334
|
+
generationConfig.presencePenalty = options.presencePenalty;
|
|
335
|
+
}
|
|
336
|
+
if (options.repetitionPenalty !== undefined) {
|
|
337
|
+
generationConfig.repetitionPenalty = options.repetitionPenalty;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const config: GenerateContentConfig = {
|
|
341
|
+
...(Object.keys(generationConfig).length > 0 && generationConfig),
|
|
342
|
+
...(context.systemPrompt && { systemInstruction: context.systemPrompt.toWellFormed() }),
|
|
343
|
+
...(context.tools && context.tools.length > 0 && { tools: convertTools(context.tools, model) }),
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
if (context.tools && context.tools.length > 0 && options.toolChoice) {
|
|
347
|
+
config.toolConfig = {
|
|
348
|
+
functionCallingConfig: {
|
|
349
|
+
mode: mapToolChoice(options.toolChoice),
|
|
350
|
+
},
|
|
351
|
+
};
|
|
352
|
+
} else {
|
|
353
|
+
config.toolConfig = undefined;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (options.thinking?.enabled && model.reasoning) {
|
|
357
|
+
const cfg: ThinkingConfig = { includeThoughts: true };
|
|
358
|
+
if (options.thinking.level !== undefined) {
|
|
359
|
+
// Cast to any since our GoogleThinkingLevel mirrors Google's ThinkingLevel enum values
|
|
360
|
+
cfg.thinkingLevel = options.thinking.level as any;
|
|
361
|
+
} else if (options.thinking.budgetTokens !== undefined) {
|
|
362
|
+
cfg.thinkingBudget = options.thinking.budgetTokens;
|
|
363
|
+
}
|
|
364
|
+
config.thinkingConfig = cfg;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if (options.signal) {
|
|
368
|
+
if (options.signal.aborted) {
|
|
369
|
+
throw new Error("Request aborted");
|
|
370
|
+
}
|
|
371
|
+
config.abortSignal = options.signal;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
const params: GenerateContentParameters = {
|
|
375
|
+
model: model.id,
|
|
376
|
+
contents,
|
|
377
|
+
config,
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
return params;
|
|
381
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kimi Code provider - wraps OpenAI or Anthropic API based on format setting.
|
|
3
|
+
*
|
|
4
|
+
* Kimi offers both OpenAI-compatible and Anthropic-compatible APIs:
|
|
5
|
+
* - OpenAI: https://api.kimi.com/coding/v1/chat/completions
|
|
6
|
+
* - Anthropic: https://api.kimi.com/coding/v1/messages
|
|
7
|
+
*
|
|
8
|
+
* The Anthropic API is generally more stable and recommended.
|
|
9
|
+
* Note: Kimi calculates TPM rate limits based on max_tokens, not actual output.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { ANTHROPIC_THINKING } from "../stream";
|
|
13
|
+
import type { Api, Context, Model, SimpleStreamOptions } from "../types";
|
|
14
|
+
import { AssistantMessageEventStream } from "../utils/event-stream";
|
|
15
|
+
import { getKimiCommonHeaders } from "../utils/oauth/kimi";
|
|
16
|
+
import { streamAnthropic } from "./anthropic";
|
|
17
|
+
import { streamOpenAICompletions } from "./openai-completions";
|
|
18
|
+
|
|
19
|
+
export type KimiApiFormat = "openai" | "anthropic";
|
|
20
|
+
|
|
21
|
+
// Note: Anthropic SDK appends /v1/messages, so base URL should not include /v1
|
|
22
|
+
const KIMI_ANTHROPIC_BASE_URL = "https://api.kimi.com/coding";
|
|
23
|
+
|
|
24
|
+
export interface KimiOptions extends SimpleStreamOptions {
|
|
25
|
+
/** API format: "openai" or "anthropic". Default: "anthropic" */
|
|
26
|
+
format?: KimiApiFormat;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Stream from Kimi Code, routing to either OpenAI or Anthropic API based on format.
|
|
31
|
+
* Returns synchronously like other providers - async header fetching happens internally.
|
|
32
|
+
*/
|
|
33
|
+
export function streamKimi(
|
|
34
|
+
model: Model<"openai-completions">,
|
|
35
|
+
context: Context,
|
|
36
|
+
options?: KimiOptions,
|
|
37
|
+
): AssistantMessageEventStream {
|
|
38
|
+
const stream = new AssistantMessageEventStream();
|
|
39
|
+
const format = options?.format ?? "anthropic";
|
|
40
|
+
|
|
41
|
+
// Async IIFE to handle header fetching and stream piping
|
|
42
|
+
(async () => {
|
|
43
|
+
try {
|
|
44
|
+
const kimiHeaders = await getKimiCommonHeaders();
|
|
45
|
+
const mergedHeaders = { ...kimiHeaders, ...options?.headers };
|
|
46
|
+
|
|
47
|
+
if (format === "anthropic") {
|
|
48
|
+
// Create a synthetic Anthropic model pointing to Kimi's endpoint
|
|
49
|
+
const anthropicModel: Model<"anthropic-messages"> = {
|
|
50
|
+
id: model.id,
|
|
51
|
+
name: model.name,
|
|
52
|
+
api: "anthropic-messages",
|
|
53
|
+
provider: model.provider,
|
|
54
|
+
baseUrl: KIMI_ANTHROPIC_BASE_URL,
|
|
55
|
+
headers: mergedHeaders,
|
|
56
|
+
contextWindow: model.contextWindow,
|
|
57
|
+
maxTokens: model.maxTokens,
|
|
58
|
+
reasoning: model.reasoning,
|
|
59
|
+
input: model.input,
|
|
60
|
+
cost: model.cost,
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// Calculate thinking budget from reasoning level
|
|
64
|
+
const reasoning = options?.reasoning;
|
|
65
|
+
const reasoningEffort = reasoning;
|
|
66
|
+
const thinkingEnabled = !!reasoningEffort && model.reasoning;
|
|
67
|
+
const thinkingBudget = reasoningEffort
|
|
68
|
+
? (options?.thinkingBudgets?.[reasoningEffort] ?? ANTHROPIC_THINKING[reasoningEffort])
|
|
69
|
+
: undefined;
|
|
70
|
+
|
|
71
|
+
const innerStream = streamAnthropic(anthropicModel, context, {
|
|
72
|
+
apiKey: options?.apiKey,
|
|
73
|
+
temperature: options?.temperature,
|
|
74
|
+
topP: options?.topP,
|
|
75
|
+
topK: options?.topK,
|
|
76
|
+
minP: options?.minP,
|
|
77
|
+
presencePenalty: options?.presencePenalty,
|
|
78
|
+
repetitionPenalty: options?.repetitionPenalty,
|
|
79
|
+
maxTokens: options?.maxTokens ?? Math.min(model.maxTokens, 32000),
|
|
80
|
+
signal: options?.signal,
|
|
81
|
+
headers: mergedHeaders,
|
|
82
|
+
sessionId: options?.sessionId,
|
|
83
|
+
onPayload: options?.onPayload,
|
|
84
|
+
thinkingEnabled,
|
|
85
|
+
thinkingBudgetTokens: thinkingBudget,
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
for await (const event of innerStream) {
|
|
89
|
+
stream.push(event);
|
|
90
|
+
}
|
|
91
|
+
} else {
|
|
92
|
+
// OpenAI format - use original model with Kimi headers
|
|
93
|
+
const reasoningEffort = options?.reasoning;
|
|
94
|
+
const innerStream = streamOpenAICompletions(model, context, {
|
|
95
|
+
apiKey: options?.apiKey,
|
|
96
|
+
temperature: options?.temperature,
|
|
97
|
+
topP: options?.topP,
|
|
98
|
+
topK: options?.topK,
|
|
99
|
+
minP: options?.minP,
|
|
100
|
+
presencePenalty: options?.presencePenalty,
|
|
101
|
+
repetitionPenalty: options?.repetitionPenalty,
|
|
102
|
+
maxTokens: options?.maxTokens ?? model.maxTokens,
|
|
103
|
+
signal: options?.signal,
|
|
104
|
+
headers: mergedHeaders,
|
|
105
|
+
sessionId: options?.sessionId,
|
|
106
|
+
onPayload: options?.onPayload,
|
|
107
|
+
reasoning: reasoningEffort,
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
for await (const event of innerStream) {
|
|
111
|
+
stream.push(event);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
} catch (err) {
|
|
115
|
+
stream.push({
|
|
116
|
+
type: "error",
|
|
117
|
+
reason: "error",
|
|
118
|
+
error: createErrorMessage(model, err),
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
})();
|
|
122
|
+
|
|
123
|
+
return stream;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function createErrorMessage(model: Model<Api>, err: unknown) {
|
|
127
|
+
return {
|
|
128
|
+
role: "assistant" as const,
|
|
129
|
+
content: [{ type: "text" as const, text: err instanceof Error ? err.message : String(err) }],
|
|
130
|
+
api: model.api,
|
|
131
|
+
provider: model.provider,
|
|
132
|
+
model: model.id,
|
|
133
|
+
usage: {
|
|
134
|
+
input: 0,
|
|
135
|
+
output: 0,
|
|
136
|
+
cacheRead: 0,
|
|
137
|
+
cacheWrite: 0,
|
|
138
|
+
totalTokens: 0,
|
|
139
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
140
|
+
},
|
|
141
|
+
stopReason: "error" as const,
|
|
142
|
+
timestamp: Date.now(),
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Check if a model is a Kimi Code model.
|
|
148
|
+
*/
|
|
149
|
+
export function isKimiModel(model: Model<Api>): boolean {
|
|
150
|
+
return model.provider === "kimi-code";
|
|
151
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Constants for OpenAI Codex (ChatGPT OAuth) backend
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export const CODEX_BASE_URL = "https://chatgpt.com/backend-api";
|
|
6
|
+
|
|
7
|
+
export const OPENAI_HEADERS = {
|
|
8
|
+
BETA: "OpenAI-Beta",
|
|
9
|
+
ACCOUNT_ID: "chatgpt-account-id",
|
|
10
|
+
ORIGINATOR: "originator",
|
|
11
|
+
SESSION_ID: "session_id",
|
|
12
|
+
CONVERSATION_ID: "conversation_id",
|
|
13
|
+
} as const;
|
|
14
|
+
|
|
15
|
+
export const OPENAI_HEADER_VALUES = {
|
|
16
|
+
BETA_RESPONSES: "responses=experimental",
|
|
17
|
+
BETA_RESPONSES_WEBSOCKETS_V2: "responses_websockets=2026-02-06",
|
|
18
|
+
ORIGINATOR_CODEX: "pi",
|
|
19
|
+
} as const;
|
|
20
|
+
|
|
21
|
+
export const URL_PATHS = {
|
|
22
|
+
RESPONSES: "/responses",
|
|
23
|
+
CODEX_RESPONSES: "/codex/responses",
|
|
24
|
+
} as const;
|
|
25
|
+
|
|
26
|
+
export const JWT_CLAIM_PATH = "https://api.openai.com/auth" as const;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Extract account ID from a Codex JWT access token.
|
|
30
|
+
* Returns undefined if the token is not a valid Codex JWT.
|
|
31
|
+
*/
|
|
32
|
+
export function getCodexAccountId(accessToken: string): string | undefined {
|
|
33
|
+
try {
|
|
34
|
+
const parts = accessToken.split(".");
|
|
35
|
+
if (parts.length !== 3) return undefined;
|
|
36
|
+
const decoded = Buffer.from(parts[1] ?? "", "base64").toString("utf-8");
|
|
37
|
+
const payload = JSON.parse(decoded) as Record<string, unknown>;
|
|
38
|
+
const auth = payload[JWT_CLAIM_PATH] as { chatgpt_account_id?: string } | undefined;
|
|
39
|
+
return auth?.chatgpt_account_id ?? undefined;
|
|
40
|
+
} catch {
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
}
|