@oh-my-pi/pi-ai 3.20.1 → 3.35.0
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 +69 -12
- package/package.json +3 -10
- package/src/cli.ts +89 -89
- package/src/index.ts +2 -2
- package/src/models.generated.ts +949 -178
- package/src/models.ts +11 -17
- package/src/providers/anthropic.ts +94 -29
- package/src/providers/google-gemini-cli.ts +270 -134
- package/src/providers/google-shared.ts +48 -5
- package/src/providers/google-vertex.ts +15 -4
- package/src/providers/google.ts +15 -4
- package/src/providers/openai-codex/index.ts +7 -0
- package/src/providers/openai-codex/prompts/codex.ts +26 -59
- package/src/providers/openai-codex/prompts/pi-codex-bridge.ts +38 -31
- package/src/providers/openai-codex/prompts/system-prompt.ts +26 -0
- package/src/providers/openai-codex/request-transformer.ts +38 -203
- package/src/providers/openai-codex-responses.ts +96 -26
- package/src/providers/openai-completions.ts +35 -27
- package/src/providers/openai-responses.ts +3 -2
- package/src/providers/transorm-messages.ts +4 -3
- package/src/stream.ts +34 -25
- package/src/types.ts +21 -4
- package/src/utils/oauth/github-copilot.ts +38 -3
- package/src/utils/oauth/google-antigravity.ts +146 -55
- package/src/utils/oauth/google-gemini-cli.ts +146 -55
- package/src/utils/oauth/index.ts +5 -5
- package/src/utils/oauth/openai-codex.ts +129 -54
- package/src/utils/overflow.ts +1 -1
- package/src/utils/retry-after.ts +110 -0
- package/src/bun-imports.d.ts +0 -14
package/src/models.ts
CHANGED
|
@@ -12,34 +12,28 @@ for (const [provider, models] of Object.entries(MODELS)) {
|
|
|
12
12
|
modelRegistry.set(provider, providerModels);
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
type ProviderModels = typeof MODELS;
|
|
16
|
-
type ProviderWithModels = keyof ProviderModels;
|
|
17
|
-
|
|
18
15
|
type ModelApi<
|
|
19
|
-
TProvider extends
|
|
20
|
-
TModelId extends keyof
|
|
21
|
-
> =
|
|
16
|
+
TProvider extends KnownProvider,
|
|
17
|
+
TModelId extends keyof (typeof MODELS)[TProvider],
|
|
18
|
+
> = (typeof MODELS)[TProvider][TModelId] extends { api: infer TApi } ? (TApi extends Api ? TApi : never) : never;
|
|
22
19
|
|
|
23
|
-
export function getModel<TProvider extends
|
|
20
|
+
export function getModel<TProvider extends KnownProvider, TModelId extends keyof (typeof MODELS)[TProvider]>(
|
|
24
21
|
provider: TProvider,
|
|
25
22
|
modelId: TModelId,
|
|
26
|
-
): Model<ModelApi<TProvider, TModelId
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
return modelRegistry.get(provider)?.get(modelId as string) as Model<Api> | undefined;
|
|
23
|
+
): Model<ModelApi<TProvider, TModelId>> {
|
|
24
|
+
const providerModels = modelRegistry.get(provider);
|
|
25
|
+
return providerModels?.get(modelId as string) as Model<ModelApi<TProvider, TModelId>>;
|
|
30
26
|
}
|
|
31
27
|
|
|
32
28
|
export function getProviders(): KnownProvider[] {
|
|
33
29
|
return Array.from(modelRegistry.keys()) as KnownProvider[];
|
|
34
30
|
}
|
|
35
31
|
|
|
36
|
-
export function getModels<TProvider extends
|
|
32
|
+
export function getModels<TProvider extends KnownProvider>(
|
|
37
33
|
provider: TProvider,
|
|
38
|
-
): Model<ModelApi<TProvider, keyof
|
|
39
|
-
export function getModels(provider: KnownProvider): Model<Api>[];
|
|
40
|
-
export function getModels(provider: KnownProvider): Model<Api>[] {
|
|
34
|
+
): Model<ModelApi<TProvider, keyof (typeof MODELS)[TProvider]>>[] {
|
|
41
35
|
const models = modelRegistry.get(provider);
|
|
42
|
-
return models ? (Array.from(models.values()) as Model<
|
|
36
|
+
return models ? (Array.from(models.values()) as Model<ModelApi<TProvider, keyof (typeof MODELS)[TProvider]>>[]) : [];
|
|
43
37
|
}
|
|
44
38
|
|
|
45
39
|
export function calculateCost<TApi extends Api>(model: Model<TApi>, usage: Usage): Usage["cost"] {
|
|
@@ -56,7 +50,7 @@ const XHIGH_MODELS = new Set(["gpt-5.1-codex-max", "gpt-5.2", "gpt-5.2-codex"]);
|
|
|
56
50
|
|
|
57
51
|
/**
|
|
58
52
|
* Check if a model supports xhigh thinking level.
|
|
59
|
-
* Currently only certain OpenAI models support this.
|
|
53
|
+
* Currently only certain OpenAI Codex models support this.
|
|
60
54
|
*/
|
|
61
55
|
export function supportsXhigh<TApi extends Api>(model: Model<TApi>): boolean {
|
|
62
56
|
return XHIGH_MODELS.has(model.id);
|
|
@@ -3,7 +3,7 @@ import type {
|
|
|
3
3
|
ContentBlockParam,
|
|
4
4
|
MessageCreateParamsStreaming,
|
|
5
5
|
MessageParam,
|
|
6
|
-
} from "@anthropic-ai/sdk/resources/messages
|
|
6
|
+
} from "@anthropic-ai/sdk/resources/messages";
|
|
7
7
|
import { calculateCost } from "../models";
|
|
8
8
|
import { getEnvApiKey } from "../stream";
|
|
9
9
|
import type {
|
|
@@ -24,10 +24,21 @@ import type {
|
|
|
24
24
|
} from "../types";
|
|
25
25
|
import { AssistantMessageEventStream } from "../utils/event-stream";
|
|
26
26
|
import { parseStreamingJson } from "../utils/json-parse";
|
|
27
|
+
import { formatErrorMessageWithRetryAfter } from "../utils/retry-after";
|
|
27
28
|
import { sanitizeSurrogates } from "../utils/sanitize-unicode";
|
|
28
29
|
|
|
29
30
|
import { transformMessages } from "./transorm-messages";
|
|
30
31
|
|
|
32
|
+
// Stealth mode: Mimic Claude Code's tool naming exactly
|
|
33
|
+
const claudeCodeVersion = "2.1.2";
|
|
34
|
+
|
|
35
|
+
// Prefix all tool names to avoid collisions with Claude Code's built-in tools
|
|
36
|
+
const toolNamePrefix = "cli_";
|
|
37
|
+
|
|
38
|
+
const toClaudeCodeName = (name: string) => toolNamePrefix + name;
|
|
39
|
+
const fromClaudeCodeName = (name: string) =>
|
|
40
|
+
name.startsWith(toolNamePrefix) ? name.slice(toolNamePrefix.length) : name;
|
|
41
|
+
|
|
31
42
|
/**
|
|
32
43
|
* Convert content blocks to Anthropic API format
|
|
33
44
|
*/
|
|
@@ -157,7 +168,7 @@ export const streamAnthropic: StreamFunction<"anthropic-messages"> = (
|
|
|
157
168
|
const block: Block = {
|
|
158
169
|
type: "toolCall",
|
|
159
170
|
id: event.content_block.id,
|
|
160
|
-
name: event.content_block.name,
|
|
171
|
+
name: isOAuthToken ? fromClaudeCodeName(event.content_block.name) : event.content_block.name,
|
|
161
172
|
arguments: event.content_block.input as Record<string, any>,
|
|
162
173
|
partialJson: "",
|
|
163
174
|
index: event.index,
|
|
@@ -269,7 +280,7 @@ export const streamAnthropic: StreamFunction<"anthropic-messages"> = (
|
|
|
269
280
|
} catch (error) {
|
|
270
281
|
for (const block of output.content) delete (block as any).index;
|
|
271
282
|
output.stopReason = options?.signal?.aborted ? "aborted" : "error";
|
|
272
|
-
output.errorMessage =
|
|
283
|
+
output.errorMessage = formatErrorMessageWithRetryAfter(error);
|
|
273
284
|
stream.push({ type: "error", reason: output.stopReason, error: output });
|
|
274
285
|
stream.end();
|
|
275
286
|
}
|
|
@@ -278,23 +289,68 @@ export const streamAnthropic: StreamFunction<"anthropic-messages"> = (
|
|
|
278
289
|
return stream;
|
|
279
290
|
};
|
|
280
291
|
|
|
292
|
+
function isOAuthToken(apiKey: string): boolean {
|
|
293
|
+
return apiKey.includes("sk-ant-oat");
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Build deduplicated beta header string
|
|
297
|
+
function buildBetaHeader(baseBetas: string[], extraBetas: string[]): string {
|
|
298
|
+
const seen = new Set<string>();
|
|
299
|
+
const result: string[] = [];
|
|
300
|
+
for (const beta of [...baseBetas, ...extraBetas]) {
|
|
301
|
+
const trimmed = beta.trim();
|
|
302
|
+
if (trimmed && !seen.has(trimmed)) {
|
|
303
|
+
seen.add(trimmed);
|
|
304
|
+
result.push(trimmed);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
return result.join(",");
|
|
308
|
+
}
|
|
309
|
+
|
|
281
310
|
function createClient(
|
|
282
311
|
model: Model<"anthropic-messages">,
|
|
283
312
|
apiKey: string,
|
|
284
313
|
interleavedThinking: boolean,
|
|
285
314
|
): { client: Anthropic; isOAuthToken: boolean } {
|
|
286
|
-
const
|
|
287
|
-
|
|
288
|
-
|
|
315
|
+
const oauthToken = isOAuthToken(apiKey);
|
|
316
|
+
|
|
317
|
+
// Base betas required for Claude Code compatibility
|
|
318
|
+
const baseBetas = oauthToken
|
|
319
|
+
? [
|
|
320
|
+
"claude-code-20250219",
|
|
321
|
+
"oauth-2025-04-20",
|
|
322
|
+
"interleaved-thinking-2025-05-14",
|
|
323
|
+
"fine-grained-tool-streaming-2025-05-14",
|
|
324
|
+
]
|
|
325
|
+
: ["fine-grained-tool-streaming-2025-05-14"];
|
|
326
|
+
|
|
327
|
+
// Add interleaved thinking if requested (and not already in base)
|
|
328
|
+
const extraBetas: string[] = [];
|
|
329
|
+
if (interleavedThinking && !oauthToken) {
|
|
330
|
+
extraBetas.push("interleaved-thinking-2025-05-14");
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Include any betas from model headers
|
|
334
|
+
const modelBeta = model.headers?.["anthropic-beta"];
|
|
335
|
+
if (modelBeta) {
|
|
336
|
+
extraBetas.push(...modelBeta.split(","));
|
|
289
337
|
}
|
|
290
338
|
|
|
291
|
-
|
|
339
|
+
const betaHeader = buildBetaHeader(baseBetas, extraBetas);
|
|
340
|
+
|
|
341
|
+
if (oauthToken) {
|
|
342
|
+
// Stealth mode: Mimic Claude Code's headers exactly
|
|
292
343
|
const defaultHeaders = {
|
|
293
344
|
accept: "application/json",
|
|
294
345
|
"anthropic-dangerous-direct-browser-access": "true",
|
|
295
|
-
"anthropic-beta":
|
|
346
|
+
"anthropic-beta": betaHeader,
|
|
347
|
+
"user-agent": `claude-cli/${claudeCodeVersion} (external, cli)`,
|
|
348
|
+
"x-app": "cli",
|
|
296
349
|
...(model.headers || {}),
|
|
297
350
|
};
|
|
351
|
+
// Don't duplicate anthropic-beta from model.headers
|
|
352
|
+
delete (defaultHeaders as Record<string, string>)["anthropic-beta"];
|
|
353
|
+
(defaultHeaders as Record<string, string>)["anthropic-beta"] = betaHeader;
|
|
298
354
|
|
|
299
355
|
const client = new Anthropic({
|
|
300
356
|
apiKey: null,
|
|
@@ -305,23 +361,25 @@ function createClient(
|
|
|
305
361
|
});
|
|
306
362
|
|
|
307
363
|
return { client, isOAuthToken: true };
|
|
308
|
-
}
|
|
309
|
-
const defaultHeaders = {
|
|
310
|
-
accept: "application/json",
|
|
311
|
-
"anthropic-dangerous-direct-browser-access": "true",
|
|
312
|
-
"anthropic-beta": betaFeatures.join(","),
|
|
313
|
-
...(model.headers || {}),
|
|
314
|
-
};
|
|
364
|
+
}
|
|
315
365
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
366
|
+
const defaultHeaders = {
|
|
367
|
+
accept: "application/json",
|
|
368
|
+
"anthropic-dangerous-direct-browser-access": "true",
|
|
369
|
+
"anthropic-beta": betaHeader,
|
|
370
|
+
...(model.headers || {}),
|
|
371
|
+
};
|
|
372
|
+
// Ensure our beta header takes precedence
|
|
373
|
+
(defaultHeaders as Record<string, string>)["anthropic-beta"] = betaHeader;
|
|
374
|
+
|
|
375
|
+
const client = new Anthropic({
|
|
376
|
+
apiKey,
|
|
377
|
+
baseURL: model.baseUrl,
|
|
378
|
+
dangerouslyAllowBrowser: true,
|
|
379
|
+
defaultHeaders,
|
|
380
|
+
});
|
|
322
381
|
|
|
323
|
-
|
|
324
|
-
}
|
|
382
|
+
return { client, isOAuthToken: false };
|
|
325
383
|
}
|
|
326
384
|
|
|
327
385
|
function buildParams(
|
|
@@ -332,7 +390,7 @@ function buildParams(
|
|
|
332
390
|
): MessageCreateParamsStreaming {
|
|
333
391
|
const params: MessageCreateParamsStreaming = {
|
|
334
392
|
model: model.id,
|
|
335
|
-
messages: convertMessages(context.messages, model),
|
|
393
|
+
messages: convertMessages(context.messages, model, isOAuthToken),
|
|
336
394
|
max_tokens: options?.maxTokens || (model.maxTokens / 3) | 0,
|
|
337
395
|
stream: true,
|
|
338
396
|
};
|
|
@@ -375,7 +433,7 @@ function buildParams(
|
|
|
375
433
|
}
|
|
376
434
|
|
|
377
435
|
if (context.tools) {
|
|
378
|
-
params.tools = convertTools(context.tools);
|
|
436
|
+
params.tools = convertTools(context.tools, isOAuthToken);
|
|
379
437
|
}
|
|
380
438
|
|
|
381
439
|
if (options?.thinkingEnabled && model.reasoning) {
|
|
@@ -388,6 +446,9 @@ function buildParams(
|
|
|
388
446
|
if (options?.toolChoice) {
|
|
389
447
|
if (typeof options.toolChoice === "string") {
|
|
390
448
|
params.tool_choice = { type: options.toolChoice };
|
|
449
|
+
} else if (isOAuthToken && options.toolChoice.name) {
|
|
450
|
+
// Prefix tool name in tool_choice for OAuth mode
|
|
451
|
+
params.tool_choice = { ...options.toolChoice, name: toClaudeCodeName(options.toolChoice.name) };
|
|
391
452
|
} else {
|
|
392
453
|
params.tool_choice = options.toolChoice;
|
|
393
454
|
}
|
|
@@ -402,7 +463,11 @@ function sanitizeToolCallId(id: string): string {
|
|
|
402
463
|
return id.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
403
464
|
}
|
|
404
465
|
|
|
405
|
-
function convertMessages(
|
|
466
|
+
function convertMessages(
|
|
467
|
+
messages: Message[],
|
|
468
|
+
model: Model<"anthropic-messages">,
|
|
469
|
+
isOAuthToken: boolean,
|
|
470
|
+
): MessageParam[] {
|
|
406
471
|
const params: MessageParam[] = [];
|
|
407
472
|
|
|
408
473
|
// Transform messages for cross-provider compatibility
|
|
@@ -481,7 +546,7 @@ function convertMessages(messages: Message[], model: Model<"anthropic-messages">
|
|
|
481
546
|
blocks.push({
|
|
482
547
|
type: "tool_use",
|
|
483
548
|
id: sanitizeToolCallId(block.id),
|
|
484
|
-
name: block.name,
|
|
549
|
+
name: isOAuthToken ? toClaudeCodeName(block.name) : block.name,
|
|
485
550
|
input: block.arguments,
|
|
486
551
|
});
|
|
487
552
|
}
|
|
@@ -547,14 +612,14 @@ function convertMessages(messages: Message[], model: Model<"anthropic-messages">
|
|
|
547
612
|
return params;
|
|
548
613
|
}
|
|
549
614
|
|
|
550
|
-
function convertTools(tools: Tool[]): Anthropic.Messages.Tool[] {
|
|
615
|
+
function convertTools(tools: Tool[], isOAuthToken: boolean): Anthropic.Messages.Tool[] {
|
|
551
616
|
if (!tools) return [];
|
|
552
617
|
|
|
553
618
|
return tools.map((tool) => {
|
|
554
619
|
const jsonSchema = tool.parameters as any; // TypeBox already generates JSON Schema
|
|
555
620
|
|
|
556
621
|
return {
|
|
557
|
-
name: tool.name,
|
|
622
|
+
name: isOAuthToken ? toClaudeCodeName(tool.name) : tool.name,
|
|
558
623
|
description: tool.description,
|
|
559
624
|
input_schema: {
|
|
560
625
|
type: "object" as const,
|