@oh-my-pi/pi-ai 17.4.0 → 17.4.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 +30 -0
- package/LICENSE +23 -0
- package/THIRD-PARTY-NOTICES.txt +22909 -0
- package/dist/types/auth-retry.d.ts +8 -3
- package/dist/types/auth-storage.d.ts +9 -0
- package/dist/types/dialect/qwen-xml.d.ts +6 -0
- package/dist/types/error/auth-classify.d.ts +8 -8
- package/dist/types/error/oauth.d.ts +2 -1
- package/dist/types/providers/cursor/exec-modern.d.ts +1 -1
- package/dist/types/providers/cursor-pi-args.d.ts +21 -0
- package/dist/types/providers/cursor.d.ts +23 -3
- package/dist/types/providers/google-types.d.ts +6 -0
- package/dist/types/providers/mock.d.ts +3 -1
- package/dist/types/providers/openai-shared.d.ts +6 -1
- package/dist/types/registry/oauth/google-antigravity.d.ts +2 -6
- package/dist/types/registry/oauth/index.d.ts +2 -3
- package/dist/types/registry/oauth/types.d.ts +2 -1
- package/dist/types/registry/oauth/xai-oauth.d.ts +2 -2
- package/dist/types/registry/registry.d.ts +1 -1
- package/dist/types/registry/types.d.ts +2 -1
- package/dist/types/registry/xai-oauth.d.ts +1 -1
- package/dist/types/types.d.ts +21 -0
- package/dist/types/utils/stream-markup-healing.d.ts +1 -1
- package/package.json +8 -6
- package/src/auth-retry.ts +41 -3
- package/src/auth-storage.ts +229 -75
- package/src/dialect/qwen-xml.ts +82 -0
- package/src/error/auth-classify.ts +10 -8
- package/src/error/oauth.ts +2 -1
- package/src/providers/amazon-bedrock.ts +12 -19
- package/src/providers/anthropic.ts +29 -35
- package/src/providers/cursor/exec-modern.ts +2 -0
- package/src/providers/cursor-pi-args.ts +38 -2
- package/src/providers/cursor.ts +357 -32
- package/src/providers/google-shared.ts +11 -12
- package/src/providers/google-types.ts +7 -0
- package/src/providers/mock.ts +4 -1
- package/src/providers/openai-codex-responses.ts +34 -8
- package/src/providers/openai-completions.ts +2 -2
- package/src/providers/openai-shared.ts +72 -14
- package/src/registry/oauth/google-antigravity.ts +247 -162
- package/src/registry/oauth/index.ts +3 -3
- package/src/registry/oauth/types.ts +2 -1
- package/src/registry/oauth/xai-oauth.ts +14 -7
- package/src/registry/types.ts +2 -1
- package/src/registry/xai-oauth.ts +2 -2
- package/src/stream.ts +8 -4
- package/src/types.ts +22 -0
- package/src/utils/schema/CONSTRAINTS.md +1 -0
- package/src/utils/schema/fields.ts +20 -4
- package/src/utils/stream-markup-healing.ts +9 -2
|
@@ -71,6 +71,8 @@ export interface AuthRetryKeyState {
|
|
|
71
71
|
refreshedCurrent: boolean;
|
|
72
72
|
/** Whether the legacy non-usage auth path already switched to one sibling. */
|
|
73
73
|
legacyAuthSwitchUsed: boolean;
|
|
74
|
+
/** Whether this operation already replayed once after an explicit token-refresh request. */
|
|
75
|
+
tokenRefreshReplayUsed?: boolean;
|
|
74
76
|
/** Total outbound attempts accepted for this logical operation, including the initial request. */
|
|
75
77
|
attempts: number;
|
|
76
78
|
}
|
|
@@ -83,9 +85,11 @@ export declare function resolveNextAuthRetryKey(state: AuthRetryKeyState, resolv
|
|
|
83
85
|
* retry (identical to the legacy static-key path).
|
|
84
86
|
* - A resolver → initial `attempt`, then resolver-driven retries until the
|
|
85
87
|
* applicable policy is exhausted, the resolver declines or cycles, or the
|
|
86
|
-
* operation reaches {@link AUTH_RETRY_MAX_ATTEMPTS}.
|
|
87
|
-
*
|
|
88
|
-
*
|
|
88
|
+
* operation reaches {@link AUTH_RETRY_MAX_ATTEMPTS}. An explicit typed
|
|
89
|
+
* token-refresh request gets exactly one refresh-current replay and never
|
|
90
|
+
* enters sibling rotation. Ordinary 401/auth failures retain one
|
|
91
|
+
* refresh-same plus one sibling switch; 403/usage-limit failures rotate
|
|
92
|
+
* directly through distinct siblings.
|
|
89
93
|
*
|
|
90
94
|
* Used by non-streaming consumers (image generation, web search, completion
|
|
91
95
|
* helpers). The streaming driver in `stream.ts` implements the same policy with
|
|
@@ -134,6 +138,7 @@ export interface WithOAuthAccessOptions {
|
|
|
134
138
|
* `projectId`, `enterpriseUrl`) instead of bare API-key bytes.
|
|
135
139
|
*
|
|
136
140
|
* - initial → `getOAuthAccess` (or `opts.seed`).
|
|
141
|
+
* - typed token-refresh request → one forced refresh-current replay, then stop.
|
|
137
142
|
* - 401/auth failure → one `getOAuthAccess` with `forceRefresh: true` for the
|
|
138
143
|
* current account, then sibling rotation.
|
|
139
144
|
* - 403/usage-limit failure → `rotateSessionCredential` directly, without a
|
|
@@ -742,6 +742,15 @@ export declare class AuthStorage {
|
|
|
742
742
|
* Remove a runtime API key override.
|
|
743
743
|
*/
|
|
744
744
|
removeRuntimeApiKey(provider: string): void;
|
|
745
|
+
/**
|
|
746
|
+
* Install a runtime usage provider override (not persisted to disk).
|
|
747
|
+
*
|
|
748
|
+
* Runtime overrides are checked before the configured resolver, including its
|
|
749
|
+
* built-in fallback. Removing the override restores that resolver unchanged.
|
|
750
|
+
*/
|
|
751
|
+
setRuntimeUsageProvider(provider: Provider, usageProvider: UsageProvider, apiKey?: string): void;
|
|
752
|
+
/** Remove a runtime usage provider override and restore configured/default resolution. */
|
|
753
|
+
removeRuntimeUsageProvider(provider: Provider): void;
|
|
745
754
|
/**
|
|
746
755
|
* Register a per-provider API key sourced from user configuration
|
|
747
756
|
* (e.g. `models.yml` `providers.<name>.apiKey`). Higher priority than
|
|
@@ -8,13 +8,13 @@ export declare function isDefinitiveOAuthFailure(errorMsg: string): boolean;
|
|
|
8
8
|
/** Whether an upstream response explicitly says the supplied OAuth bearer was invalidated. */
|
|
9
9
|
export declare function isInvalidatedOAuthTokenError(error: unknown): boolean;
|
|
10
10
|
/**
|
|
11
|
-
* Whether an upstream failure should
|
|
12
|
-
* `401`, a `403` (token valid but access
|
|
13
|
-
* restriction a sibling account may not
|
|
14
|
-
* denial such as Codex `cyber_policy`, a
|
|
15
|
-
* `usage_limit_reached`, Anthropic account
|
|
16
|
-
* `resource_exhausted`, OpenAI `insufficient_quota`, …), or
|
|
17
|
-
* whose payload did not preserve a richer quota code. Transient
|
|
18
|
-
* (`Too many requests`, per-minute caps) stay in the upstream-backoff lane.
|
|
11
|
+
* Whether an upstream failure should retry through the credential resolver: a
|
|
12
|
+
* typed token-refresh request, a hard `401`, a `403` (token valid but access
|
|
13
|
+
* denied — plan, model policy, or org restriction a sibling account may not
|
|
14
|
+
* share), an account-scoped policy denial such as Codex `cyber_policy`, a
|
|
15
|
+
* body-classified usage limit (Codex `usage_limit_reached`, Anthropic account
|
|
16
|
+
* rate-limit, Google `resource_exhausted`, OpenAI `insufficient_quota`, …), or
|
|
17
|
+
* a bare `429` whose payload did not preserve a richer quota code. Transient
|
|
18
|
+
* 429s (`Too many requests`, per-minute caps) stay in the upstream-backoff lane.
|
|
19
19
|
*/
|
|
20
20
|
export declare function isAuthRetryableError(error: unknown): boolean;
|
|
@@ -33,7 +33,8 @@ export interface OAuthErrorOptions {
|
|
|
33
33
|
/**
|
|
34
34
|
* A failure inside an interactive OAuth / device-code login flow. The `kind`
|
|
35
35
|
* pinpoints the stage. Timeout/polling are classified transient; everything
|
|
36
|
-
* else is a hard auth failure
|
|
36
|
+
* else is a hard auth failure. The auth-retry classifier separately treats
|
|
37
|
+
* `token-refresh` as an explicit request to refresh and replay once.
|
|
37
38
|
*/
|
|
38
39
|
export declare class OAuthError extends Error {
|
|
39
40
|
readonly kind: OAuthErrorKind;
|
|
@@ -17,7 +17,7 @@ import type { ToolResultMessage } from "../../types.js";
|
|
|
17
17
|
* virtual registry. Re-exported here because this is where the frame builders
|
|
18
18
|
* and their translation are consumed together.
|
|
19
19
|
*/
|
|
20
|
-
export { omitUndefinedArgs, piEscapeRegexLiteral, piGrepSkip, piJoinPath, piLimit, piLsPath, piReadDisplayPath, piReadPath, piReadPathHasRange, piTimeout, } from "../cursor-pi-args.js";
|
|
20
|
+
export { cursorEditOwnedReadPath, cursorRawReadPath, omitUndefinedArgs, piEscapeRegexLiteral, piGrepSkip, piJoinPath, piLimit, piLsPath, piReadDisplayPath, piReadPath, piReadPathHasRange, piTimeout, } from "../cursor-pi-args.js";
|
|
21
21
|
/** Flatten a tool result's content into the single `output` string the Pi frames carry. */
|
|
22
22
|
export declare function piOutputText(toolResult: ToolResultMessage): string;
|
|
23
23
|
/**
|
|
@@ -42,6 +42,27 @@ export declare function piReadPath(readPath: string, offset?: number, limit?: nu
|
|
|
42
42
|
* the coding-agent read tool; the selector remains embedded in the path.
|
|
43
43
|
*/
|
|
44
44
|
export declare function piReadPathHasRange(readPath: string): boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Force a Cursor exec read onto `read`'s verbatim `:raw` selector.
|
|
47
|
+
*
|
|
48
|
+
* Native `editToolCall` (StrReplace) materializes via `readArgs` then
|
|
49
|
+
* `writeArgs`. The server treats the read result as file bytes and writes
|
|
50
|
+
* them back. A hashline-formatted native read (`[path#TAG]` + `LINE:`
|
|
51
|
+
* prefixes) poisons that cycle: the write would persist the markup.
|
|
52
|
+
* `:raw` is the existing selector that drops both. A path that already
|
|
53
|
+
* carries `raw` is left alone; a range-only selector gets `raw` inserted
|
|
54
|
+
* so the range still applies without the gutter.
|
|
55
|
+
*/
|
|
56
|
+
export declare function cursorRawReadPath(readPath: string): string;
|
|
57
|
+
/**
|
|
58
|
+
* Path the edit-owned materialization read should execute.
|
|
59
|
+
*
|
|
60
|
+
* Range is composed first (`piReadPath` already uses `:raw` for a range),
|
|
61
|
+
* then a whole-file path is forced onto `:raw`. The caller must drop
|
|
62
|
+
* `offset`/`limit` after this so the bridge's `piReadPath` cannot append a
|
|
63
|
+
* second `:raw` onto the already-composed selector.
|
|
64
|
+
*/
|
|
65
|
+
export declare function cursorEditOwnedReadPath(readPath: string, offset?: number, limit?: number): string | null;
|
|
45
66
|
/**
|
|
46
67
|
* The same range as {@link piReadPath}, rendered for a transcript block rather
|
|
47
68
|
* than for execution.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import http2 from "node:http2";
|
|
2
|
-
import type { McpToolDefinition } from "@oh-my-pi/pi-catalog/discovery/cursor-proto";
|
|
2
|
+
import type { CursorRule, McpToolDefinition } from "@oh-my-pi/pi-catalog/discovery/cursor-proto";
|
|
3
3
|
import { type AgentServerMessage, type ConversationStateStructure } from "@oh-my-pi/pi-catalog/discovery/cursor-proto";
|
|
4
4
|
import { type JsonValue } from "@oh-my-pi/pi-catalog/discovery/protobuf";
|
|
5
5
|
import type { AssistantMessage, Context, CursorExecHandlerResult, CursorExecHandlers, CursorExecPairing, CursorTodoSyncHandler, CursorToolResultHandler, Message, Model, StreamFunction, StreamOptions, TextContent, ThinkingContent, Tool, ToolCall, ToolResultMessage } from "../types.js";
|
|
@@ -12,6 +12,8 @@ export interface CursorOptions extends StreamOptions {
|
|
|
12
12
|
conversationId?: string;
|
|
13
13
|
execHandlers?: CursorExecHandlers;
|
|
14
14
|
onToolResult?: CursorToolResultHandler;
|
|
15
|
+
/** Wire model id selected after thinking-effort routing (`resolveWireModelId`). */
|
|
16
|
+
wireModelId?: string;
|
|
15
17
|
}
|
|
16
18
|
/**
|
|
17
19
|
* Maps an opaque HTTP/2 negotiation failure into an actionable error.
|
|
@@ -32,7 +34,7 @@ export type ToolCallState = ToolCall & {
|
|
|
32
34
|
[kStreamingBlockIndex]: number;
|
|
33
35
|
[kStreamingPartialJson]?: string;
|
|
34
36
|
[kStreamingLastParseLen]?: number;
|
|
35
|
-
[kStreamingBlockKind]: "mcp" | "todo" | "cursor-exec" | "connect-scm" | "web-fetch";
|
|
37
|
+
[kStreamingBlockKind]: "mcp" | "todo" | "cursor-exec" | "cursor-edit" | "connect-scm" | "web-fetch";
|
|
36
38
|
[kStreamingEnvelopeId]?: string;
|
|
37
39
|
[kCursorExecResolved]?: true;
|
|
38
40
|
};
|
|
@@ -57,6 +59,17 @@ export interface BlockState {
|
|
|
57
59
|
openToolCalls: Map<string, ToolCallState>;
|
|
58
60
|
/** MCP call IDs synthesized from exec frames before their redundant streamed block arrives. */
|
|
59
61
|
resolvedMcpToolCallIds: Set<string>;
|
|
62
|
+
/**
|
|
63
|
+
* Native `editToolCall` (StrReplace) ids whose materialization `readArgs` /
|
|
64
|
+
* `writeArgs` must stay raw and must not synthesize extra transcript blocks.
|
|
65
|
+
*
|
|
66
|
+
* Optional so existing test harnesses stay valid. Both the interaction
|
|
67
|
+
* envelope `call_id` and the inner `toolCallId` are recorded — exec frames
|
|
68
|
+
* pair on the inner id.
|
|
69
|
+
*/
|
|
70
|
+
editOwnedToolCallIds?: Set<string>;
|
|
71
|
+
/** Edit blocks whose write already persisted a `toolResult`. */
|
|
72
|
+
pairedEditToolCallIds?: Set<string>;
|
|
60
73
|
firstTokenTime: number | undefined;
|
|
61
74
|
setTextBlock: (b: (TextContent & {
|
|
62
75
|
[kStreamingBlockIndex]: number;
|
|
@@ -79,7 +92,7 @@ export interface UsageState {
|
|
|
79
92
|
sawTokenDelta: boolean;
|
|
80
93
|
}
|
|
81
94
|
/** Exported for tests: drives one Cursor server message through the stream (exec waits mark the stream busy). */
|
|
82
|
-
export declare function handleServerMessage(msg: AgentServerMessage, output: AssistantMessage, stream: AssistantMessageEventStream, state: BlockState, blobStore: Map<string, Uint8Array>, h2Request: http2.ClientHttp2Stream, execHandlers: CursorExecHandlers | undefined, onToolResult: CursorToolResultHandler | undefined, usageState: UsageState, requestContextTools: McpToolDefinition[], onConversationCheckpoint?: (checkpoint: ConversationStateStructure) => void): Promise<void>;
|
|
95
|
+
export declare function handleServerMessage(msg: AgentServerMessage, output: AssistantMessage, stream: AssistantMessageEventStream, state: BlockState, blobStore: Map<string, Uint8Array>, h2Request: http2.ClientHttp2Stream, execHandlers: CursorExecHandlers | undefined, onToolResult: CursorToolResultHandler | undefined, usageState: UsageState, requestContextTools: McpToolDefinition[], requestContextRules?: CursorRule[], onConversationCheckpoint?: (checkpoint: ConversationStateStructure) => void): Promise<void>;
|
|
83
96
|
/**
|
|
84
97
|
* Exported for tests: verifies handler is invoked with correct `this` when passed as bound.
|
|
85
98
|
*
|
|
@@ -191,6 +204,13 @@ export declare function mergeCursorMcpToolCallArgs(streamed: Record<string, unkn
|
|
|
191
204
|
export declare function synthesizeCursorExecToolCall(output: AssistantMessage, stream: AssistantMessageEventStream, state: BlockState, toolCallId: string, toolName: string, args: Record<string, unknown>): void;
|
|
192
205
|
/** Exported for tests: drives one Cursor interaction update through the streaming state machine. */
|
|
193
206
|
export declare function processInteractionUpdate(update: any, output: AssistantMessage, stream: AssistantMessageEventStream, state: BlockState, usageState: UsageState): void;
|
|
207
|
+
/**
|
|
208
|
+
* Cursor AgentService reconstructs the model prompt from `requestContext.rules`,
|
|
209
|
+
* not from the client-supplied `rootPromptMessagesJson` system blobs. Map each
|
|
210
|
+
* OMP system-prompt entry to a global CursorRule so always-apply rules survive
|
|
211
|
+
* that reconstruction.
|
|
212
|
+
*/
|
|
213
|
+
export declare function buildCursorRequestContextRules(systemPrompt: readonly string[] | undefined): CursorRule[];
|
|
194
214
|
export declare function buildMcpToolDefinitions(tools: Tool[] | undefined): McpToolDefinition[];
|
|
195
215
|
/**
|
|
196
216
|
* Build `ConversationStateStructure.rootPromptMessagesJson` blob IDs for the
|
|
@@ -42,9 +42,15 @@ export interface Part {
|
|
|
42
42
|
thought?: boolean;
|
|
43
43
|
thoughtSignature?: string;
|
|
44
44
|
inlineData?: InlineDataPart;
|
|
45
|
+
fileData?: FileDataPart;
|
|
45
46
|
functionCall?: FunctionCallPart;
|
|
46
47
|
functionResponse?: FunctionResponsePart;
|
|
47
48
|
}
|
|
49
|
+
/** Remote media reference; the backend fetches `fileUri` server-side. */
|
|
50
|
+
export interface FileDataPart {
|
|
51
|
+
fileUri: string;
|
|
52
|
+
mimeType?: string;
|
|
53
|
+
}
|
|
48
54
|
/** Conversation turn. Roles: `"user"`, `"model"`, optionally absent for system instructions. */
|
|
49
55
|
export interface Content {
|
|
50
56
|
role?: string;
|
|
@@ -118,6 +118,8 @@ export interface MockModelOptions {
|
|
|
118
118
|
id?: string;
|
|
119
119
|
/** Provider string used in the returned AssistantMessage. Defaults to `"mock"`. */
|
|
120
120
|
provider?: string;
|
|
121
|
+
/** Base URL reported by the model. Defaults to `"mock://"`. */
|
|
122
|
+
baseUrl?: string;
|
|
121
123
|
/** A sequence of responses, one per call. Accepts arrays, generators, or any iterable. */
|
|
122
124
|
responses?: MockResponseSource;
|
|
123
125
|
/** Fallback handler used when `responses` is exhausted. */
|
|
@@ -141,7 +143,7 @@ export declare class MockModel implements Model<MockApi> {
|
|
|
141
143
|
readonly name: string;
|
|
142
144
|
readonly api: MockApi;
|
|
143
145
|
readonly provider: string;
|
|
144
|
-
readonly baseUrl
|
|
146
|
+
readonly baseUrl: string;
|
|
145
147
|
readonly reasoning: boolean;
|
|
146
148
|
readonly input: ("text" | "image")[];
|
|
147
149
|
readonly cost: Model["cost"];
|
|
@@ -520,7 +520,12 @@ export interface SequentialCutoffSummaryState {
|
|
|
520
520
|
export declare function createSequentialCutoffSummaryState(): SequentialCutoffSummaryState;
|
|
521
521
|
/** Chooses final reasoning text without making sequential-cutoff results disagree with emitted deltas. */
|
|
522
522
|
export declare function finalizeReasoningThinking(item: ResponseReasoningItem, streamedThinking: string, cutoff?: SequentialCutoffSummaryState): string;
|
|
523
|
-
export declare function appendReasoningSummaryTextDelta(item: ResponseReasoningItem, block: ThinkingContent, delta: string, stream: AssistantMessageEventStream, output: AssistantMessage, contentIndex: number): void;
|
|
523
|
+
export declare function appendReasoningSummaryTextDelta(item: ResponseReasoningItem, block: ThinkingContent, delta: string, stream: AssistantMessageEventStream, output: AssistantMessage, contentIndex: number, summaryIndex?: number): void;
|
|
524
|
+
/**
|
|
525
|
+
* Applies a completed reasoning-summary snapshot, including providers that omit
|
|
526
|
+
* the preceding summary-part event.
|
|
527
|
+
*/
|
|
528
|
+
export declare function applyReasoningSummaryTextDone(item: ResponseReasoningItem, block: ThinkingContent, text: string, summaryIndex: number, stream: AssistantMessageEventStream, output: AssistantMessage, contentIndex: number): void;
|
|
524
529
|
export declare function appendReasoningSummaryPartDone(item: ResponseReasoningItem, block: ThinkingContent, stream: AssistantMessageEventStream, output: AssistantMessage, contentIndex: number): void;
|
|
525
530
|
/**
|
|
526
531
|
* Applies an atomic `response.reasoning_summary_text.done` snapshot.
|
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
import type { OAuthController, OAuthCredentials } from "./types.js";
|
|
2
|
+
/** Cloud Code Assist metadata sent by native Antigravity control-plane requests. */
|
|
2
3
|
export declare const ANTIGRAVITY_LOAD_CODE_ASSIST_METADATA: Readonly<{
|
|
3
4
|
ideType: "ANTIGRAVITY";
|
|
4
5
|
}>;
|
|
5
|
-
|
|
6
|
-
ide_type: string;
|
|
7
|
-
ide_version: string;
|
|
8
|
-
ide_name: string;
|
|
9
|
-
}
|
|
10
|
-
export declare function getAntigravityOnboardMetadata(): AntigravityOnboardMetadata;
|
|
6
|
+
/** Authenticate an Antigravity account and resolve its Cloud Code Assist project. */
|
|
11
7
|
export declare function loginAntigravity(ctrl: OAuthController): Promise<OAuthCredentials>;
|
|
12
8
|
/**
|
|
13
9
|
* Refresh Antigravity token
|
|
@@ -19,10 +19,9 @@ export declare function getOAuthProvider(id: OAuthProviderId): OAuthProviderInte
|
|
|
19
19
|
*/
|
|
20
20
|
export declare function unregisterOAuthProviders(sourceId: string): void;
|
|
21
21
|
/**
|
|
22
|
-
* Refresh
|
|
23
|
-
* Saves the new credentials and returns the new access token.
|
|
22
|
+
* Refresh a built-in OAuth grant, cancelling provider work when refresh ownership ends.
|
|
24
23
|
*/
|
|
25
|
-
export declare function refreshOAuthToken(provider: OAuthProvider, credentials: OAuthCredentials): Promise<OAuthCredentials>;
|
|
24
|
+
export declare function refreshOAuthToken(provider: OAuthProvider, credentials: OAuthCredentials, signal?: AbortSignal): Promise<OAuthCredentials>;
|
|
26
25
|
/**
|
|
27
26
|
* Build API-key bytes for a provider from an already-fresh OAuth credential.
|
|
28
27
|
*
|
|
@@ -80,7 +80,8 @@ export interface OAuthProviderInterface {
|
|
|
80
80
|
readonly name: string;
|
|
81
81
|
readonly sourceId?: string;
|
|
82
82
|
login(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials | string>;
|
|
83
|
-
|
|
83
|
+
/** Refresh a stored grant; the signal bounds provider network work to refresh ownership. */
|
|
84
|
+
refreshToken?(credentials: OAuthCredentials, signal?: AbortSignal): Promise<OAuthCredentials>;
|
|
84
85
|
getApiKey?(credentials: OAuthCredentials): string;
|
|
85
86
|
/** Store resulting OAuth credentials under a different provider id. */
|
|
86
87
|
readonly storeCredentialsAs?: string;
|
|
@@ -41,6 +41,6 @@ export declare function loginXAIOAuth(ctrl: OAuthController): Promise<OAuthCrede
|
|
|
41
41
|
* Refresh an xAI OAuth access token using a stored refresh_token.
|
|
42
42
|
*
|
|
43
43
|
* Re-runs OIDC discovery and re-validates the token endpoint before sending
|
|
44
|
-
* the stored refresh token.
|
|
44
|
+
* the stored refresh token. Caller cancellation aborts every network request.
|
|
45
45
|
*/
|
|
46
|
-
export declare function refreshXAIOAuthToken(refreshToken: string, fetchOverride?: FetchImpl): Promise<OAuthCredentials>;
|
|
46
|
+
export declare function refreshXAIOAuthToken(refreshToken: string, fetchOverride?: FetchImpl, signal?: AbortSignal): Promise<OAuthCredentials>;
|
|
@@ -325,7 +325,7 @@ declare const ALL: ({
|
|
|
325
325
|
readonly id: "xai-oauth";
|
|
326
326
|
readonly name: "xAI Grok OAuth (SuperGrok or X Premium+)";
|
|
327
327
|
readonly login: (cb: import("./oauth/index.js").OAuthLoginCallbacks) => Promise<import("./oauth/index.js").OAuthCredentials>;
|
|
328
|
-
readonly refreshToken: (credentials: import("./oauth/index.js").OAuthCredentials) => Promise<import("./oauth/index.js").OAuthCredentials>;
|
|
328
|
+
readonly refreshToken: (credentials: import("./oauth/index.js").OAuthCredentials, signal?: AbortSignal) => Promise<import("./oauth/index.js").OAuthCredentials>;
|
|
329
329
|
} | {
|
|
330
330
|
readonly id: "xiaomi";
|
|
331
331
|
readonly name: "Xiaomi MiMo";
|
|
@@ -64,7 +64,8 @@ export interface ProviderDefinition {
|
|
|
64
64
|
/** Provider-owned authentication and endpoint setup for model discovery. */
|
|
65
65
|
readonly prepareModelDiscovery?: ProviderModelDiscoveryPreparer;
|
|
66
66
|
readonly login?: (callbacks: OAuthLoginCallbacks) => Promise<OAuthCredentials | string>;
|
|
67
|
-
|
|
67
|
+
/** Refresh a stored grant; the signal bounds provider network work to refresh ownership. */
|
|
68
|
+
readonly refreshToken?: (credentials: OAuthCredentials, signal?: AbortSignal) => Promise<OAuthCredentials>;
|
|
68
69
|
readonly getApiKey?: (credentials: OAuthCredentials) => string;
|
|
69
70
|
/** Store OAuth credentials under a different provider id (e.g. `openai-codex-device` ⇒ `openai-codex`). */
|
|
70
71
|
readonly storeCredentialsAs?: string;
|
|
@@ -3,5 +3,5 @@ export declare const xaiOauthProvider: {
|
|
|
3
3
|
readonly id: "xai-oauth";
|
|
4
4
|
readonly name: "xAI Grok OAuth (SuperGrok or X Premium+)";
|
|
5
5
|
readonly login: (cb: OAuthLoginCallbacks) => Promise<OAuthCredentials>;
|
|
6
|
-
readonly refreshToken: (credentials: OAuthCredentials) => Promise<OAuthCredentials>;
|
|
6
|
+
readonly refreshToken: (credentials: OAuthCredentials, signal?: AbortSignal) => Promise<OAuthCredentials>;
|
|
7
7
|
};
|
package/dist/types/types.d.ts
CHANGED
|
@@ -317,6 +317,8 @@ export interface StreamOptions {
|
|
|
317
317
|
providerSessionState?: Map<string, ProviderSessionState>;
|
|
318
318
|
/** Canonical Codex compaction classification; ignored by other providers. */
|
|
319
319
|
codexCompaction?: CodexCompactionRequestContext;
|
|
320
|
+
/** Codex Code Mode tool exposure snapshot emitted as `tool_namespaces_info` turn metadata; ignored by other providers. */
|
|
321
|
+
toolNamespacesInfo?: unknown;
|
|
320
322
|
/**
|
|
321
323
|
* Optional per-provider concurrent request cap for LLM stream calls. Keys are
|
|
322
324
|
* provider ids (`model.provider`); positive numeric values cap in-flight
|
|
@@ -540,6 +542,13 @@ export interface AnthropicServerToolContent {
|
|
|
540
542
|
[key: string]: unknown;
|
|
541
543
|
};
|
|
542
544
|
}
|
|
545
|
+
/** Provider-native uploaded file reference for image reuse without retransmitting bytes. */
|
|
546
|
+
export interface ProviderFileReference {
|
|
547
|
+
provider: "openai" | "anthropic" | "google";
|
|
548
|
+
id?: string;
|
|
549
|
+
uri?: string;
|
|
550
|
+
expiresAt?: number;
|
|
551
|
+
}
|
|
543
552
|
export interface ImageContent {
|
|
544
553
|
type: "image";
|
|
545
554
|
data: string;
|
|
@@ -550,6 +559,18 @@ export interface ImageContent {
|
|
|
550
559
|
* default `auto` downscale). Providers without a detail knob ignore it.
|
|
551
560
|
*/
|
|
552
561
|
detail?: "auto" | "low" | "high" | "original";
|
|
562
|
+
/** Provider-native file reference preferred only by its matching provider. */
|
|
563
|
+
providerFile?: ProviderFileReference;
|
|
564
|
+
/**
|
|
565
|
+
* Optional https mirror of `data`, served by a caller-run blob server.
|
|
566
|
+
* Providers whose API fetches remote images send this URL instead of the
|
|
567
|
+
* base64 payload; every other provider ignores it. `data` remains the
|
|
568
|
+
* source of truth — the URL must serve exactly those bytes, and callers
|
|
569
|
+
* are responsible for keeping it stable across turns (prefix caches hash
|
|
570
|
+
* the URL string, and Anthropic silently forgets images when a resent
|
|
571
|
+
* turn differs byte-wise).
|
|
572
|
+
*/
|
|
573
|
+
url?: string;
|
|
553
574
|
}
|
|
554
575
|
export type ComputerAction = {
|
|
555
576
|
type: "click";
|
|
@@ -11,7 +11,7 @@ export interface HealedToolCall {
|
|
|
11
11
|
readonly name: string;
|
|
12
12
|
readonly arguments: string;
|
|
13
13
|
}
|
|
14
|
-
export type StreamMarkupHealingPattern = "kimi" | "dsml" | "thinking";
|
|
14
|
+
export type StreamMarkupHealingPattern = "kimi" | "dsml" | "qwen" | "thinking";
|
|
15
15
|
export interface StreamMarkupHealingOptions {
|
|
16
16
|
readonly pattern: StreamMarkupHealingPattern;
|
|
17
17
|
}
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-ai",
|
|
4
|
-
"version": "17.4.
|
|
4
|
+
"version": "17.4.2",
|
|
5
5
|
"description": "Unified LLM API with automatic model discovery and provider configuration",
|
|
6
6
|
"homepage": "https://omp.sh",
|
|
7
|
-
"author": "
|
|
7
|
+
"author": "Stencil Labs, Inc.",
|
|
8
8
|
"contributors": [
|
|
9
9
|
"Mario Zechner"
|
|
10
10
|
],
|
|
@@ -37,10 +37,10 @@
|
|
|
37
37
|
"fmt": "biome format --write ."
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@oh-my-pi/omptype": "17.4.
|
|
41
|
-
"@oh-my-pi/pi-catalog": "17.4.
|
|
42
|
-
"@oh-my-pi/pi-utils": "17.4.
|
|
43
|
-
"@oh-my-pi/pi-wire": "17.4.
|
|
40
|
+
"@oh-my-pi/omptype": "17.4.2",
|
|
41
|
+
"@oh-my-pi/pi-catalog": "17.4.2",
|
|
42
|
+
"@oh-my-pi/pi-utils": "17.4.2",
|
|
43
|
+
"@oh-my-pi/pi-wire": "17.4.2"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@types/bun": "^1.3.14"
|
|
@@ -52,6 +52,8 @@
|
|
|
52
52
|
"src",
|
|
53
53
|
"README.md",
|
|
54
54
|
"CHANGELOG.md",
|
|
55
|
+
"LICENSE",
|
|
56
|
+
"THIRD-PARTY-NOTICES.txt",
|
|
55
57
|
"dist/types"
|
|
56
58
|
],
|
|
57
59
|
"exports": {
|
package/src/auth-retry.ts
CHANGED
|
@@ -130,6 +130,8 @@ export interface AuthRetryKeyState {
|
|
|
130
130
|
refreshedCurrent: boolean;
|
|
131
131
|
/** Whether the legacy non-usage auth path already switched to one sibling. */
|
|
132
132
|
legacyAuthSwitchUsed: boolean;
|
|
133
|
+
/** Whether this operation already replayed once after an explicit token-refresh request. */
|
|
134
|
+
tokenRefreshReplayUsed?: boolean;
|
|
133
135
|
/** Total outbound attempts accepted for this logical operation, including the initial request. */
|
|
134
136
|
attempts: number;
|
|
135
137
|
}
|
|
@@ -140,6 +142,7 @@ export function createAuthRetryKeyState(initialKey: string): AuthRetryKeyState {
|
|
|
140
142
|
lastKey: initialKey,
|
|
141
143
|
refreshedCurrent: false,
|
|
142
144
|
legacyAuthSwitchUsed: false,
|
|
145
|
+
tokenRefreshReplayUsed: false,
|
|
143
146
|
attempts: 1,
|
|
144
147
|
};
|
|
145
148
|
}
|
|
@@ -161,6 +164,14 @@ export async function resolveNextAuthRetryKey(
|
|
|
161
164
|
): Promise<string | undefined> {
|
|
162
165
|
if (signal?.aborted) return undefined;
|
|
163
166
|
if (state.attempts >= AUTH_RETRY_MAX_ATTEMPTS) return undefined;
|
|
167
|
+
if (error instanceof AIError.OAuthError && error.kind === "token-refresh") {
|
|
168
|
+
if (state.tokenRefreshReplayUsed) return undefined;
|
|
169
|
+
state.tokenRefreshReplayUsed = true;
|
|
170
|
+
const refreshed = await resolveRetryKey(resolver, false, error, signal, state.lastKey);
|
|
171
|
+
state.refreshedCurrent = true;
|
|
172
|
+
if (signal?.aborted || refreshed === undefined) return undefined;
|
|
173
|
+
return acceptRetryKey(state, refreshed, true);
|
|
174
|
+
}
|
|
164
175
|
const directRotation = isDirectCredentialRotationError(error);
|
|
165
176
|
if (!directRotation) {
|
|
166
177
|
if (state.legacyAuthSwitchUsed) return undefined;
|
|
@@ -207,9 +218,11 @@ async function runOAuthAttempt<T>(
|
|
|
207
218
|
* retry (identical to the legacy static-key path).
|
|
208
219
|
* - A resolver → initial `attempt`, then resolver-driven retries until the
|
|
209
220
|
* applicable policy is exhausted, the resolver declines or cycles, or the
|
|
210
|
-
* operation reaches {@link AUTH_RETRY_MAX_ATTEMPTS}.
|
|
211
|
-
*
|
|
212
|
-
*
|
|
221
|
+
* operation reaches {@link AUTH_RETRY_MAX_ATTEMPTS}. An explicit typed
|
|
222
|
+
* token-refresh request gets exactly one refresh-current replay and never
|
|
223
|
+
* enters sibling rotation. Ordinary 401/auth failures retain one
|
|
224
|
+
* refresh-same plus one sibling switch; 403/usage-limit failures rotate
|
|
225
|
+
* directly through distinct siblings.
|
|
213
226
|
*
|
|
214
227
|
* Used by non-streaming consumers (image generation, web search, completion
|
|
215
228
|
* helpers). The streaming driver in `stream.ts` implements the same policy with
|
|
@@ -296,6 +309,7 @@ export interface WithOAuthAccessOptions {
|
|
|
296
309
|
* `projectId`, `enterpriseUrl`) instead of bare API-key bytes.
|
|
297
310
|
*
|
|
298
311
|
* - initial → `getOAuthAccess` (or `opts.seed`).
|
|
312
|
+
* - typed token-refresh request → one forced refresh-current replay, then stop.
|
|
299
313
|
* - 401/auth failure → one `getOAuthAccess` with `forceRefresh: true` for the
|
|
300
314
|
* current account, then sibling rotation.
|
|
301
315
|
* - 403/usage-limit failure → `rotateSessionCredential` directly, without a
|
|
@@ -331,6 +345,7 @@ export async function withOAuthAccess<T>(
|
|
|
331
345
|
let attemptCount = 1;
|
|
332
346
|
let legacyAuthSwitchUsed = false;
|
|
333
347
|
let refreshedCurrent = false;
|
|
348
|
+
let tokenRefreshReplayUsed = false;
|
|
334
349
|
let attemptResult = await runOAuthAttempt(lastAccess, attempt, isAuthError);
|
|
335
350
|
if (attemptResult.ok) return attemptResult.result;
|
|
336
351
|
|
|
@@ -338,6 +353,29 @@ export async function withOAuthAccess<T>(
|
|
|
338
353
|
while (true) {
|
|
339
354
|
let next: OAuthAccess | undefined;
|
|
340
355
|
if (signal?.aborted || attemptCount >= AUTH_RETRY_MAX_ATTEMPTS) break;
|
|
356
|
+
const tokenRefreshReplay = lastError instanceof AIError.OAuthError && lastError.kind === "token-refresh";
|
|
357
|
+
if (tokenRefreshReplay) {
|
|
358
|
+
if (tokenRefreshReplayUsed) break;
|
|
359
|
+
tokenRefreshReplayUsed = true;
|
|
360
|
+
refreshedCurrent = true;
|
|
361
|
+
try {
|
|
362
|
+
next = await storage.getOAuthAccess(provider, sessionId, { forceRefresh: true, signal });
|
|
363
|
+
} catch {
|
|
364
|
+
next = undefined;
|
|
365
|
+
}
|
|
366
|
+
if (signal?.aborted || !next) break;
|
|
367
|
+
const bearer = next.accessToken;
|
|
368
|
+
if (attemptedBearers.has(bearer) || attemptCount >= AUTH_RETRY_MAX_ATTEMPTS) break;
|
|
369
|
+
attemptedCredentialIdentities.add(oauthCredentialIdentity(next));
|
|
370
|
+
attemptedBearers.add(bearer);
|
|
371
|
+
attemptCount += 1;
|
|
372
|
+
lastAccess = next;
|
|
373
|
+
attemptResult = await runOAuthAttempt(next, attempt, isAuthError);
|
|
374
|
+
if (attemptResult.ok) return attemptResult.result;
|
|
375
|
+
lastError = attemptResult.error;
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
|
|
341
379
|
const directRotation = isDirectCredentialRotationError(lastError);
|
|
342
380
|
if (!directRotation) {
|
|
343
381
|
if (legacyAuthSwitchUsed) break;
|