pi-provider-litellm 1.2.1 → 1.2.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.
@@ -1,5 +1,7 @@
1
1
  import type { ProviderModelConfig } from "@earendil-works/pi-coding-agent";
2
2
  import type { DiscoveryOptions, DiscoveryResult } from "./types.js";
3
3
  export declare function normalizeBaseUrl(input: string): string;
4
+ export declare function isMoonshotModel(modelId: string): boolean;
5
+ export declare function shouldSuppressReasoningContent(modelId: string): boolean;
4
6
  export declare function buildCompat(modelId: string): ProviderModelConfig["compat"];
5
7
  export declare function discoverModels(baseUrl: string, apiKey: string, options?: DiscoveryOptions): Promise<DiscoveryResult>;
package/dist/discover.js CHANGED
@@ -11,8 +11,15 @@ export function normalizeBaseUrl(input) {
11
11
  // through the proxy, so prompt caching silently no-ops on Claude models.
12
12
  const ANTHROPIC_MODEL_PATTERN = /^(anthropic\/|claude|opus|sonnet|haiku)/i;
13
13
  const MOONSHOT_MODEL_PATTERN = /^(moonshotai\/|moonshot\/|kimi[-/])/i;
14
+ const FORCED_THINKING_MODEL_PATTERN = /(?:^|[-/])thinking(?:[-/]|$)/i;
15
+ export function isMoonshotModel(modelId) {
16
+ return MOONSHOT_MODEL_PATTERN.test(modelId);
17
+ }
18
+ export function shouldSuppressReasoningContent(modelId) {
19
+ return isMoonshotModel(modelId) && !FORCED_THINKING_MODEL_PATTERN.test(modelId);
20
+ }
14
21
  export function buildCompat(modelId) {
15
- if (MOONSHOT_MODEL_PATTERN.test(modelId)) {
22
+ if (isMoonshotModel(modelId)) {
16
23
  return {
17
24
  supportsStore: false,
18
25
  supportsDeveloperRole: false,
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import { join } from "node:path";
3
3
  import { AuthStorage, getAgentDir } from "@earendil-works/pi-coding-agent";
4
4
  import { fingerprint, readCache, writeCache } from "./cache.js";
5
5
  import { setupLiteLLMCostTracking } from "./cost.js";
6
- import { discoverModels, normalizeBaseUrl } from "./discover.js";
6
+ import { discoverModels, normalizeBaseUrl, shouldSuppressReasoningContent } from "./discover.js";
7
7
  import { getSessionIdFromFile } from "./litellm.js";
8
8
  const PROVIDER_NAME = "litellm";
9
9
  const ENV_BASE_URL = "LITELLM_BASE_URL";
@@ -109,6 +109,87 @@ function modifyLiteLLMModels(models, cred) {
109
109
  return models;
110
110
  return models.map((m) => (m.provider === PROVIDER_NAME ? { ...m, baseUrl: `${baseUrl}/v1` } : m));
111
111
  }
112
+ function prepareLiteLLMRequestPayload(payload, modelId, sessionId) {
113
+ let next;
114
+ const update = (key, value) => {
115
+ if (payload[key] !== undefined)
116
+ return;
117
+ next ??= { ...payload };
118
+ next[key] = value;
119
+ };
120
+ if (modelId && shouldSuppressReasoningContent(modelId)) {
121
+ update("include_reasoning", false);
122
+ update("reasoning_content", false);
123
+ update("merge_reasoning_content_in_choices", true);
124
+ update("thinking", { type: "disabled" });
125
+ }
126
+ if (sessionId) {
127
+ next ??= { ...payload };
128
+ next.litellm_session_id = sessionId;
129
+ }
130
+ return next;
131
+ }
132
+ function normalizeThinkTags(message) {
133
+ if (message.provider !== PROVIDER_NAME || !shouldSuppressReasoningContent(message.model))
134
+ return;
135
+ let changed = false;
136
+ const content = [];
137
+ const appendText = (text) => {
138
+ if (!text)
139
+ return;
140
+ const last = content.at(-1);
141
+ if (last?.type === "text") {
142
+ last.text += text;
143
+ return;
144
+ }
145
+ content.push({ type: "text", text });
146
+ };
147
+ const appendThinking = (thinking) => {
148
+ if (!thinking)
149
+ return;
150
+ const last = content.at(-1);
151
+ if (last?.type === "thinking") {
152
+ last.thinking += thinking;
153
+ return;
154
+ }
155
+ content.push({ type: "thinking", thinking });
156
+ };
157
+ for (let blockIndex = 0; blockIndex < message.content.length; blockIndex++) {
158
+ const block = message.content[blockIndex];
159
+ if (block.type !== "text") {
160
+ content.push(block);
161
+ continue;
162
+ }
163
+ let index = 0;
164
+ while (index < block.text.length) {
165
+ const start = block.text.indexOf("<think>", index);
166
+ if (start === -1) {
167
+ appendText(block.text.slice(index));
168
+ break;
169
+ }
170
+ changed = true;
171
+ appendText(block.text.slice(index, start));
172
+ const thinkingStart = start + "<think>".length;
173
+ const end = block.text.indexOf("</think>", thinkingStart);
174
+ if (end === -1) {
175
+ const isBeforeNonTextContent = message.content
176
+ .slice(blockIndex + 1)
177
+ .some((nextBlock) => nextBlock.type !== "text");
178
+ if (isBeforeNonTextContent)
179
+ appendThinking(block.text.slice(thinkingStart));
180
+ else
181
+ appendText(block.text.slice(thinkingStart));
182
+ index = block.text.length;
183
+ break;
184
+ }
185
+ appendThinking(block.text.slice(thinkingStart, end));
186
+ index = end + "</think>".length;
187
+ }
188
+ }
189
+ if (!changed)
190
+ return;
191
+ return { ...message, content };
192
+ }
112
193
  export default async function (pi) {
113
194
  const creds = await resolveCredentials();
114
195
  const cache = await readCache(getCachePath());
@@ -214,14 +295,19 @@ export default async function (pi) {
214
295
  pi.on("session_start", async (_event, ctx) => {
215
296
  sessionId = getSessionIdFromFile(ctx.sessionManager.getSessionFile());
216
297
  });
217
- pi.on("before_provider_request", (event) => {
218
- if (!sessionId)
298
+ pi.on("before_provider_request", (event, ctx) => {
299
+ if (ctx.model?.provider !== PROVIDER_NAME)
219
300
  return;
220
301
  if (typeof event.payload !== "object" || event.payload === null)
221
302
  return;
222
- return {
223
- ...event.payload,
224
- litellm_session_id: sessionId,
225
- };
303
+ return prepareLiteLLMRequestPayload(event.payload, ctx.model?.id, sessionId);
304
+ });
305
+ pi.on("message_end", (event) => {
306
+ if (event.message.role !== "assistant")
307
+ return;
308
+ const message = normalizeThinkTags(event.message);
309
+ if (!message)
310
+ return;
311
+ return { message };
226
312
  });
227
313
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-provider-litellm",
3
- "version": "1.2.1",
3
+ "version": "1.2.2",
4
4
  "description": "LiteLLM proxy provider extension for Pi",
5
5
  "type": "module",
6
6
  "license": "MIT",