@oh-my-pi/pi-ai 17.3.3 → 17.3.5
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 -0
- package/dist/types/auth-broker/client.d.ts +8 -1
- package/dist/types/auth-storage.d.ts +15 -6
- package/dist/types/error/rate-limit.d.ts +5 -2
- package/dist/types/index.d.ts +1 -0
- package/dist/types/oneshot-retry.d.ts +89 -0
- package/dist/types/providers/anthropic-wire.d.ts +18 -5
- package/dist/types/providers/openai-shared.d.ts +1 -1
- package/dist/types/types.d.ts +5 -4
- package/dist/types/usage/kimi.d.ts +3 -1
- package/dist/types/utils/schema/strict-tool-validation.d.ts +10 -1
- package/dist/types/utils/schema/wire.d.ts +6 -0
- package/dist/types/utils/thinking-loop.d.ts +12 -9
- package/package.json +5 -5
- package/src/auth-broker/client.ts +24 -7
- package/src/auth-broker/remote-store.ts +48 -3
- package/src/auth-broker/server.ts +1 -1
- package/src/auth-storage.ts +182 -52
- package/src/error/flags.ts +6 -2
- package/src/error/rate-limit.ts +31 -2
- package/src/index.ts +1 -0
- package/src/oneshot-retry.ts +230 -0
- package/src/providers/anthropic-messages-server.ts +5 -5
- package/src/providers/anthropic-wire.ts +31 -5
- package/src/providers/anthropic.ts +15 -4
- package/src/providers/cursor.ts +44 -4
- package/src/providers/openai-completions.ts +107 -63
- package/src/providers/openai-responses.ts +13 -10
- package/src/providers/openai-shared.ts +5 -3
- package/src/registry/alibaba-coding-plan.ts +2 -2
- package/src/registry/alibaba-token-plan.ts +6 -2
- package/src/registry/oauth/github-copilot.ts +1 -22
- package/src/registry/oauth/kimi.ts +20 -0
- package/src/stream.ts +59 -57
- package/src/types.ts +5 -4
- package/src/usage/alibaba-token-plan.ts +89 -34
- package/src/usage/kimi.ts +15 -0
- package/src/usage/umans.ts +93 -20
- package/src/utils/leaked-thinking-stream.ts +2 -2
- package/src/utils/schema/strict-tool-validation.ts +39 -5
- package/src/utils/schema/wire.ts +31 -0
- package/src/utils/thinking-loop.ts +96 -63
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,37 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [17.3.5] - 2026-08-16
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Added retryable oneshot completion support (`retryTransientCompletion`) so non-agent LLM calls correctly retry on transient provider failures (Anthropic overload/rate-limit errors, HTTP 429/500/502/503/529), honoring provider-supplied retry-after timing before giving up.
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- Fixed xAI availability detection so paid-key-only setups correctly default to `xai/grok-4.5` instead of the free SuperGrok catalog; explicit `xai-oauth/…` selectors still work as before.
|
|
14
|
+
- Fixed xAI Responses requests sending unsupported parameters (reasoning summary, presence/frequency penalties) that some models rejected.
|
|
15
|
+
- Fixed Umans usage reporting incorrectly marking quota as exhausted based on raw request counts instead of actual weighted usage, and improved the usage display to show both a soft-cap warning and a hard exhaustion limit with an accurate countdown to reset.
|
|
16
|
+
- Fixed `omp usage invalidate` to fully clear stale usage data and force a fresh refresh, so upgraded subscriptions no longer show outdated quota information.
|
|
17
|
+
- Improved session recovery to correctly treat certain Cursor HTTP/2 connection errors as transient instead of ending the session.
|
|
18
|
+
- Fixed OpenAI-compatible streams (e.g. DeepSeek) that are cut off mid-generation being silently treated as a completed response instead of being retried.
|
|
19
|
+
- Fixed DeepSeek resource-exhaustion interruptions not being automatically retried.
|
|
20
|
+
- Fixed tool-call IDs being lost during same-model replay, which could break correlation with custom gateways.
|
|
21
|
+
- Fixed Kimi Code multi-account routing to prefer accounts with more available quota, respect usage-limit cooldowns, and keep consistent usage history across token refreshes.
|
|
22
|
+
- Fixed Anthropic custom signing-proxy conversations losing tool-search results and thinking content during replay.
|
|
23
|
+
- Fixed rare runaway response loops across model providers so they now fail gracefully instead of repeating indefinitely.
|
|
24
|
+
- Fixed xAI rejecting entire turns due to certain MCP tool schema shapes, restoring compatibility while isolating any remaining incompatible tools rather than failing the whole request.
|
|
25
|
+
- Fixed Alibaba DashScope/Bailian transient per-minute rate limits being misclassified as full quota exhaustion, causing unnecessary long backoffs instead of quick retries.
|
|
26
|
+
- Fixed Anthropic-compatible streams dropping thinking content, which broke replay of prior reasoning.
|
|
27
|
+
- Updated the Alibaba Coding Plan China login flow to point to the current Bailian API-key management console.
|
|
28
|
+
|
|
29
|
+
## [17.3.4] - 2026-08-14
|
|
30
|
+
|
|
31
|
+
### Fixed
|
|
32
|
+
|
|
33
|
+
- Fixed `omp usage invalidate` to discard stale OAuth and API-key usage snapshots, then force a cache-bypassing, per-provider serialized refresh with a broker request budget sized for the full unfiltered account batch, so upgraded subscriptions do not silently retain pre-change quota data.
|
|
34
|
+
- Fixed quota reporting and Cookie capture guidance for China (Beijing) Alibaba Token Plan credentials ([#8509](https://github.com/can1357/oh-my-pi/issues/8509)).
|
|
35
|
+
|
|
5
36
|
## [17.3.3] - 2026-08-14
|
|
6
37
|
|
|
7
38
|
### Fixed
|
|
@@ -66,7 +66,14 @@ export declare class AuthBrokerClient {
|
|
|
66
66
|
openSnapshotStream(opts?: {
|
|
67
67
|
signal?: AbortSignal;
|
|
68
68
|
}): AsyncGenerator<SnapshotStreamEvent>;
|
|
69
|
-
|
|
69
|
+
/**
|
|
70
|
+
* Fetch aggregate broker usage with a timeout sized for serialized
|
|
71
|
+
* same-provider account probes.
|
|
72
|
+
*/
|
|
73
|
+
fetchUsage(options?: {
|
|
74
|
+
signal?: AbortSignal;
|
|
75
|
+
maxAccountsPerProvider?: number;
|
|
76
|
+
}): Promise<UsageResponse>;
|
|
70
77
|
/** Recorded usage-limit snapshots from the broker host, oldest first. */
|
|
71
78
|
fetchUsageHistory(query?: {
|
|
72
79
|
sinceMs?: number;
|
|
@@ -804,10 +804,20 @@ export declare class AuthStorage {
|
|
|
804
804
|
*/
|
|
805
805
|
has(provider: string): boolean;
|
|
806
806
|
/**
|
|
807
|
-
*
|
|
808
|
-
* Unlike getApiKey
|
|
807
|
+
* Dedicated auth for default-model availability (picker / `getAvailable`).
|
|
808
|
+
* Unlike {@link getApiKey}, this does not refresh OAuth tokens, and unlike
|
|
809
|
+
* {@link hasResolvableAuth} it ignores cross-provider env aliases so
|
|
810
|
+
* `XAI_API_KEY` does not auto-select SuperGrok (`xai-oauth`).
|
|
809
811
|
*/
|
|
810
812
|
hasAuth(provider: string): boolean;
|
|
813
|
+
/**
|
|
814
|
+
* Whether a request could resolve a key for this provider, including
|
|
815
|
+
* cross-provider env aliases (`xai-oauth` borrowing `XAI_API_KEY`).
|
|
816
|
+
* Use this for explicit model preflight (`xai-oauth/grok-4.5`); use
|
|
817
|
+
* {@link hasAuth} for auto-availability so the default picker stays on
|
|
818
|
+
* paid `xai` when only `XAI_API_KEY` is set.
|
|
819
|
+
*/
|
|
820
|
+
hasResolvableAuth(provider: string): boolean;
|
|
811
821
|
/**
|
|
812
822
|
* True iff a dedicated, non-env credential source is configured for this
|
|
813
823
|
* provider — i.e. anything in the cascade EXCEPT `getEnvApiKey(provider)`.
|
|
@@ -1111,10 +1121,9 @@ export declare class AuthStorage {
|
|
|
1111
1121
|
signal?: AbortSignal;
|
|
1112
1122
|
}): Promise<ResetCreditRedeemOutcome>;
|
|
1113
1123
|
/**
|
|
1114
|
-
*
|
|
1115
|
-
*
|
|
1116
|
-
*
|
|
1117
|
-
* store are invalidated.
|
|
1124
|
+
* Discard cached usage reports before a user-requested refresh. The next
|
|
1125
|
+
* read probes upstream serially per provider; a failure reports no fresh
|
|
1126
|
+
* usage instead of replaying an invalidated last-good snapshot.
|
|
1118
1127
|
*/
|
|
1119
1128
|
invalidateUsageCache(provider?: string, signal?: AbortSignal): Promise<void>;
|
|
1120
1129
|
invalidateCredentialMatching(provider: string, apiKey: string, options?: InvalidateCredentialMatchingOptions): Promise<boolean>;
|
|
@@ -3,11 +3,14 @@
|
|
|
3
3
|
* Ported from opencode-antigravity-auth plugin for consistency.
|
|
4
4
|
*/
|
|
5
5
|
export type RateLimitReason = "QUOTA_EXHAUSTED" | "INSUFFICIENT_G1_CREDITS_BALANCE" | "RATE_LIMIT_EXCEEDED" | "CONCURRENT_LIMIT" | "MODEL_CAPACITY_EXHAUSTED" | "SERVER_ERROR" | "UNKNOWN";
|
|
6
|
+
/** True for DashScope/Bailian's documented OpenAI-compatible TPM/TPS throttle. */
|
|
7
|
+
export declare function isDashScopeTokenLimitText(errorMessage: string): boolean;
|
|
6
8
|
/**
|
|
7
9
|
* Classify a rate-limit error message into a reason category.
|
|
8
10
|
* Priority order: explicit details in a resource-exhausted error > QUOTA
|
|
9
|
-
* (Antigravity "quota will reset") >
|
|
10
|
-
*
|
|
11
|
+
* (Antigravity "quota will reset") > CN quota > DASHSCOPE_TOKEN_LIMIT (TPM/TPS
|
|
12
|
+
* throttle) > CONCURRENT_LIMIT > MODEL_CAPACITY > QUOTA (account) > RATE_LIMIT >
|
|
13
|
+
* QUOTA (generic) > SERVER_ERROR > bare resource-exhausted > UNKNOWN.
|
|
11
14
|
*
|
|
12
15
|
* Bare "resource exhausted" / "resource_exhausted" maps to MODEL_CAPACITY (transient, short wait).
|
|
13
16
|
* Explicit details such as "quota exceeded" retain their normal classification.
|
package/dist/types/index.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export * from "./auth-gateway/types.js";
|
|
|
6
6
|
export * from "./auth-retry.js";
|
|
7
7
|
export * from "./auth-storage.js";
|
|
8
8
|
export * from "./error/rate-limit.js";
|
|
9
|
+
export * from "./oneshot-retry.js";
|
|
9
10
|
export * from "./provider-details.js";
|
|
10
11
|
export * from "./providers/anthropic.js";
|
|
11
12
|
export * from "./providers/anthropic-client.js";
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type { AssistantMessage } from "./types.js";
|
|
2
|
+
import { type HeadersLike } from "./utils/retry-after.js";
|
|
3
|
+
/**
|
|
4
|
+
* Transient-failure retry for **oneshot** (non-agent-loop) completions.
|
|
5
|
+
*
|
|
6
|
+
* Why this exists: `streamSimple`/`completeSimple` retry *auth* failures
|
|
7
|
+
* (credential rotation) but deliberately surface *transient* provider failures
|
|
8
|
+
* — Anthropic `overloaded_error`, `rate_limit_error`, HTTP 429/500/502/503/529
|
|
9
|
+
* — as a **resolved** `AssistantMessage` with `stopReason: "error"`. For the
|
|
10
|
+
* main agent turn that is correct: `TurnRecovery` owns recovery there, and it
|
|
11
|
+
* must refuse to replay once tool calls or visible text have streamed.
|
|
12
|
+
*
|
|
13
|
+
* Oneshots have no such hazard. A summary, title, handoff, or image
|
|
14
|
+
* description produces no side effects, so re-issuing the whole request is
|
|
15
|
+
* safe and is almost always what the caller wants. Before this helper every
|
|
16
|
+
* oneshot call site had to re-implement that decision, and most did not —
|
|
17
|
+
* failing on the first blip, or swallowing it into `null` so a transient
|
|
18
|
+
* overload was indistinguishable from a legitimate empty result.
|
|
19
|
+
*
|
|
20
|
+
* Classification reuses the existing provider predicates (`AIError`), so the
|
|
21
|
+
* set of retryable Anthropic failures stays defined in exactly one place.
|
|
22
|
+
* Usage limits are included: unlike the provider loop — which excludes them so
|
|
23
|
+
* credential rotation can own them — a oneshot has no rotation layer above it,
|
|
24
|
+
* and the retry hint the provider supplies (`retry-after`, "try again in ~5m")
|
|
25
|
+
* is honored, so waiting is the correct response.
|
|
26
|
+
*/
|
|
27
|
+
export interface OneshotRetryOptions {
|
|
28
|
+
/** Total attempts, including the first. Default 3. Values < 1 are treated as 1. */
|
|
29
|
+
maxAttempts?: number;
|
|
30
|
+
/** First backoff step in ms; doubles per attempt. Default 500. */
|
|
31
|
+
baseDelayMs?: number;
|
|
32
|
+
/**
|
|
33
|
+
* Upper bound for a single wait. Default 30_000. A provider retry hint
|
|
34
|
+
* longer than this aborts the retry instead of parking the caller — the
|
|
35
|
+
* error surfaces so higher-level recovery (or the user) can decide.
|
|
36
|
+
*/
|
|
37
|
+
maxDelayMs?: number;
|
|
38
|
+
/**
|
|
39
|
+
* Stops further attempts. Two distinct paths, both preserving the caller's
|
|
40
|
+
* intent: an abort already visible when an attempt settles surfaces that
|
|
41
|
+
* attempt's own result (`completeSimple` reports `stopReason: "aborted"`),
|
|
42
|
+
* while an abort that lands during the backoff wait rejects with the abort
|
|
43
|
+
* reason — a user cancel stays a cancel and is never relabelled as the
|
|
44
|
+
* provider failure we happened to be waiting on.
|
|
45
|
+
*
|
|
46
|
+
* This helper does NOT pass the signal into `run` — cancelling the in-flight
|
|
47
|
+
* request is the closure's job, because a per-attempt deadline must be
|
|
48
|
+
* rebuilt on every attempt. Construct it inside `run`
|
|
49
|
+
* (`signal: AbortSignal.timeout(MS)`, or `AbortSignal.any([outer, perAttempt])`);
|
|
50
|
+
* a deadline captured outside would fire once and then abort every retry,
|
|
51
|
+
* silently turning this helper into a single attempt.
|
|
52
|
+
*/
|
|
53
|
+
signal?: AbortSignal;
|
|
54
|
+
/**
|
|
55
|
+
* Headers of the attempt that just failed, used to honor `retry-after`.
|
|
56
|
+
*
|
|
57
|
+
* Load-bearing: a transient Anthropic failure arrives as a **resolved**
|
|
58
|
+
* `AssistantMessage`, and `AssistantMessage` carries no headers — so without
|
|
59
|
+
* this the real `retry-after` / `x-ratelimit-reset` values on a 429/529 are
|
|
60
|
+
* invisible and only the (usually hint-free) error text is available.
|
|
61
|
+
* Callers that already capture headers via `SimpleStreamOptions.onResponse`
|
|
62
|
+
* should return the latest capture here; it is read once per failed attempt.
|
|
63
|
+
* Thrown errors need no wiring — headers are recovered from the error itself.
|
|
64
|
+
*/
|
|
65
|
+
getResponseHeaders?: () => HeadersLike;
|
|
66
|
+
/** Observability hook. Fires immediately before sleeping. */
|
|
67
|
+
onRetry?: (info: OneshotRetryInfo) => void;
|
|
68
|
+
}
|
|
69
|
+
export interface OneshotRetryInfo {
|
|
70
|
+
/** 1-based index of the attempt that just failed. */
|
|
71
|
+
attempt: number;
|
|
72
|
+
maxAttempts: number;
|
|
73
|
+
delayMs: number;
|
|
74
|
+
/** True when `delayMs` came from a provider retry hint rather than backoff. */
|
|
75
|
+
fromRetryHint: boolean;
|
|
76
|
+
errorMessage: string;
|
|
77
|
+
/** `AIError` classification bits of the failure. */
|
|
78
|
+
errorId: number;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Run a oneshot completion, retrying transient provider failures.
|
|
82
|
+
*
|
|
83
|
+
* Handles both failure shapes: a resolved `AssistantMessage` carrying
|
|
84
|
+
* `stopReason: "error"` (what `completeSimple` produces) and a thrown error
|
|
85
|
+
* (what the raw HTTP helpers produce). A non-retryable failure is returned or
|
|
86
|
+
* rethrown unchanged, so existing caller error handling keeps working — this
|
|
87
|
+
* only removes the *first-blip* failure mode.
|
|
88
|
+
*/
|
|
89
|
+
export declare function retryTransientCompletion(run: (attempt: number) => Promise<AssistantMessage>, options?: OneshotRetryOptions): Promise<AssistantMessage>;
|
|
@@ -69,6 +69,10 @@ export type ServerToolUseBlockParam = {
|
|
|
69
69
|
export type WebSearchServerToolUseBlockParam = ServerToolUseBlockParam & {
|
|
70
70
|
name: "web_search";
|
|
71
71
|
};
|
|
72
|
+
/** Tool-search server-tool call whose matching result is replayable by omp. */
|
|
73
|
+
export type ToolSearchServerToolUseBlockParam = ServerToolUseBlockParam & {
|
|
74
|
+
name: "tool_search_tool_regex" | "tool_search_tool_bm25";
|
|
75
|
+
};
|
|
72
76
|
/** Native web-search result replayed inside an assistant turn. */
|
|
73
77
|
export type WebSearchToolResultBlockParam = {
|
|
74
78
|
type: "web_search_tool_result";
|
|
@@ -76,14 +80,23 @@ export type WebSearchToolResultBlockParam = {
|
|
|
76
80
|
content: unknown;
|
|
77
81
|
[key: string]: unknown;
|
|
78
82
|
};
|
|
79
|
-
/**
|
|
80
|
-
export
|
|
83
|
+
/** Native tool-search result replayed inside an assistant turn. */
|
|
84
|
+
export type ToolSearchToolResultBlockParam = {
|
|
85
|
+
type: "tool_search_tool_result";
|
|
86
|
+
tool_use_id: string;
|
|
87
|
+
content: unknown;
|
|
88
|
+
[key: string]: unknown;
|
|
89
|
+
};
|
|
90
|
+
/** Anthropic server-tool history variants omp can replay atomically. */
|
|
91
|
+
export type AnthropicServerToolHistoryBlockParam = WebSearchServerToolUseBlockParam | WebSearchToolResultBlockParam | ToolSearchServerToolUseBlockParam | ToolSearchToolResultBlockParam;
|
|
92
|
+
/** True when a block is complete Anthropic server-tool history omp can replay. */
|
|
93
|
+
export declare function isAnthropicServerToolHistoryBlock(block: {
|
|
81
94
|
type: string;
|
|
82
95
|
name?: unknown;
|
|
83
96
|
id?: unknown;
|
|
84
97
|
tool_use_id?: unknown;
|
|
85
98
|
content?: unknown;
|
|
86
|
-
}): block is
|
|
99
|
+
}): block is AnthropicServerToolHistoryBlockParam;
|
|
87
100
|
export type ThinkingBlockParam = {
|
|
88
101
|
type: "thinking";
|
|
89
102
|
thinking: string;
|
|
@@ -108,7 +121,7 @@ export type FallbackBlockParam = {
|
|
|
108
121
|
model: string;
|
|
109
122
|
};
|
|
110
123
|
};
|
|
111
|
-
export type ContentBlockParam = TextBlockParam | ImageBlockParam | ToolUseBlockParam | ToolResultBlockParam | ServerToolUseBlockParam | WebSearchToolResultBlockParam | ThinkingBlockParam | RedactedThinkingBlockParam | FallbackBlockParam;
|
|
124
|
+
export type ContentBlockParam = TextBlockParam | ImageBlockParam | ToolUseBlockParam | ToolResultBlockParam | ServerToolUseBlockParam | WebSearchToolResultBlockParam | ToolSearchToolResultBlockParam | ThinkingBlockParam | RedactedThinkingBlockParam | FallbackBlockParam;
|
|
112
125
|
/**
|
|
113
126
|
* A single conversation turn.
|
|
114
127
|
*
|
|
@@ -284,7 +297,7 @@ export type ResponseContentBlock = {
|
|
|
284
297
|
id: string;
|
|
285
298
|
name: string;
|
|
286
299
|
input?: Record<string, unknown> | null;
|
|
287
|
-
} | ServerToolUseBlockParam | WebSearchToolResultBlockParam | {
|
|
300
|
+
} | ServerToolUseBlockParam | WebSearchToolResultBlockParam | ToolSearchToolResultBlockParam | {
|
|
288
301
|
type: "fallback";
|
|
289
302
|
from: {
|
|
290
303
|
model: string;
|
|
@@ -582,7 +582,7 @@ type CommonSamplingOptions = Pick<StreamOptions, "temperature" | "topP" | "topK"
|
|
|
582
582
|
* reflect the model's context window rather than the upstream output limit.
|
|
583
583
|
*/
|
|
584
584
|
export declare function applyCommonResponsesSamplingParams<P extends CommonResponsesParams>(params: P, options: CommonSamplingOptions | undefined, model: Pick<Model, "provider" | "api" | "id" | "omitMaxOutputTokens" | "maxTokens"> & {
|
|
585
|
-
compat: Pick<ResolvedOpenAISharedCompat, "supportsSamplingParams">;
|
|
585
|
+
compat: Pick<ResolvedOpenAISharedCompat, "supportsSamplingParams" | "supportsPenaltyAndStopParams">;
|
|
586
586
|
}): void;
|
|
587
587
|
type ReasoningOptions = {
|
|
588
588
|
reasoning?: string;
|
package/dist/types/types.d.ts
CHANGED
|
@@ -521,19 +521,20 @@ export interface AnthropicFallbackContent {
|
|
|
521
521
|
};
|
|
522
522
|
}
|
|
523
523
|
/**
|
|
524
|
-
* Verbatim Anthropic web-search call/result retained for
|
|
525
|
-
* history replay. Other providers discard it in
|
|
524
|
+
* Verbatim Anthropic web-search or tool-search call/result retained for
|
|
525
|
+
* same-provider history replay. Other providers discard it in
|
|
526
|
+
* `transformMessages`.
|
|
526
527
|
*/
|
|
527
528
|
export interface AnthropicServerToolContent {
|
|
528
529
|
type: "anthropicServerTool";
|
|
529
530
|
block: {
|
|
530
531
|
type: "server_tool_use";
|
|
531
532
|
id: string;
|
|
532
|
-
name: "web_search";
|
|
533
|
+
name: "web_search" | "tool_search_tool_regex" | "tool_search_tool_bm25";
|
|
533
534
|
input?: Record<string, unknown> | null;
|
|
534
535
|
[key: string]: unknown;
|
|
535
536
|
} | {
|
|
536
|
-
type: "web_search_tool_result";
|
|
537
|
+
type: "web_search_tool_result" | "tool_search_tool_result";
|
|
537
538
|
tool_use_id: string;
|
|
538
539
|
content: unknown;
|
|
539
540
|
[key: string]: unknown;
|
|
@@ -1,2 +1,4 @@
|
|
|
1
|
-
import type { UsageProvider } from "../usage.js";
|
|
1
|
+
import type { CredentialRankingStrategy, UsageProvider } from "../usage.js";
|
|
2
2
|
export declare const kimiUsageProvider: UsageProvider;
|
|
3
|
+
/** Ranks Kimi OAuth accounts by the canonical 5-hour and 7-day quota windows. */
|
|
4
|
+
export declare const kimiRankingStrategy: CredentialRankingStrategy;
|
|
@@ -7,10 +7,19 @@
|
|
|
7
7
|
* copied onto a `type: "null"` branch, or an `enum` placed on an `array`
|
|
8
8
|
* schema instead of its `items`). One such tool 400s the entire turn, so
|
|
9
9
|
* callers quarantine just the offending tool. See issue #2652.
|
|
10
|
+
*
|
|
11
|
+
* xAI additionally rejects a leftover *root* `anyOf`/`oneOf` whose branches
|
|
12
|
+
* are not objects ("tool parameter root must be an object type"). That class
|
|
13
|
+
* is opt-in via {@link FindStrictToolSchemaViolationOptions.rejectXaiRootObjectUnion}
|
|
14
|
+
* so OpenAI/Azure/Codex keep valid object-root unions.
|
|
10
15
|
*/
|
|
16
|
+
export interface FindStrictToolSchemaViolationOptions {
|
|
17
|
+
/** xAI (paid + OAuth) only: leftover object-root unions 400 the whole turn. */
|
|
18
|
+
rejectXaiRootObjectUnion?: boolean;
|
|
19
|
+
}
|
|
11
20
|
/**
|
|
12
21
|
* Walk a tool parameter schema for OpenAI-strict `enum`/`const`-vs-`type`
|
|
13
22
|
* contradictions. Returns a JSON-pointer-ish path to the first offending node,
|
|
14
23
|
* or `null` when the schema is safe to emit.
|
|
15
24
|
*/
|
|
16
|
-
export declare function findStrictToolSchemaViolation(schema: unknown, path?: string): string | null;
|
|
25
|
+
export declare function findStrictToolSchemaViolation(schema: unknown, path?: string, options?: FindStrictToolSchemaViolationOptions): string | null;
|
|
@@ -14,6 +14,12 @@ import type { Tool } from "../../types.js";
|
|
|
14
14
|
* methods, while raw JSON Schema is a plain object.
|
|
15
15
|
*/
|
|
16
16
|
export declare function isArkSchema(value: unknown): value is Type;
|
|
17
|
+
/**
|
|
18
|
+
* Return an xAI-compatible copy of an object-root schema whose union consists
|
|
19
|
+
* only of typeless required-key fragments. Other providers must retain the
|
|
20
|
+
* union because it is a real model-facing constraint.
|
|
21
|
+
*/
|
|
22
|
+
export declare function flattenExclusiveRequiredRootUnion(schema: Record<string, unknown>): Record<string, unknown>;
|
|
17
23
|
/**
|
|
18
24
|
* Normalize `{}` (an unconstrained schema) to boolean `true` in every
|
|
19
25
|
* schema-valued position. JSON Schema draft 2020-12 §4.3.1 defines them as
|
|
@@ -5,11 +5,12 @@ import { AssistantMessageEventStream } from "./event-stream.js";
|
|
|
5
5
|
* classifiers treat it as a transient (retryable) stop without bespoke rules. */
|
|
6
6
|
export declare const THINKING_LOOP_ERROR_MARKER = "Thinking loop detected";
|
|
7
7
|
/**
|
|
8
|
-
* True when `model.id` belongs to a family guarded
|
|
9
|
-
* Gemini, DeepSeek, or Grok.
|
|
8
|
+
* True when `model.id` belongs to a family guarded by the semantic loop
|
|
9
|
+
* heuristics: Gemini, DeepSeek, or Grok. Exact suffix-cycle detection applies to
|
|
10
|
+
* every enabled model independently of this predicate.
|
|
10
11
|
*
|
|
11
12
|
* Model identity is derived only from its id; provider and compatibility metadata
|
|
12
|
-
* do not opt opaque aliases into
|
|
13
|
+
* do not opt opaque aliases into semantic detection.
|
|
13
14
|
*/
|
|
14
15
|
export declare function isLoopGuardedModel(model: Model<Api>, options?: StreamOptions): boolean;
|
|
15
16
|
/**
|
|
@@ -19,6 +20,8 @@ export declare function isLoopGuardedModel(model: Model<Api>, options?: StreamOp
|
|
|
19
20
|
*/
|
|
20
21
|
export declare class ThinkingLoopDetector {
|
|
21
22
|
#private;
|
|
23
|
+
private readonly semanticHeuristics;
|
|
24
|
+
constructor(semanticHeuristics?: boolean);
|
|
22
25
|
push(delta: string): string | null;
|
|
23
26
|
/** Process the buffered trailing paragraph (one with no blank-line / heading
|
|
24
27
|
* terminator). Called when the thinking block ends so the final segment —
|
|
@@ -75,12 +78,12 @@ export declare class GeminiHeaderRunDetector {
|
|
|
75
78
|
*/
|
|
76
79
|
export declare function guardThinkingLoopStream(inner: AssistantMessageEventStream, model: Model<Api>, controller: AbortController, options?: StreamOptions): AssistantMessageEventStream;
|
|
77
80
|
/**
|
|
78
|
-
* Apply the loop guard around a provider dispatch.
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
* result-awaiting caller.
|
|
81
|
+
* Apply the loop guard around a provider dispatch. Unless explicitly disabled,
|
|
82
|
+
* every model gets exact suffix-cycle detection; Gemini, DeepSeek, and Grok also
|
|
83
|
+
* get the semantic heuristics selected by {@link isLoopGuardedModel}. The guard
|
|
84
|
+
* injects an abort signal into the provider call so a detected loop tears down
|
|
85
|
+
* the upstream, then wraps the returned stream. Bounding result-path re-samples
|
|
86
|
+
* lives in the result-awaiting caller.
|
|
84
87
|
*/
|
|
85
88
|
export declare function withThinkingLoopGuard<O extends {
|
|
86
89
|
signal?: AbortSignal;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-ai",
|
|
4
|
-
"version": "17.3.
|
|
4
|
+
"version": "17.3.5",
|
|
5
5
|
"description": "Unified LLM API with automatic model discovery and provider configuration",
|
|
6
6
|
"homepage": "https://omp.sh",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -38,10 +38,10 @@
|
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"@bufbuild/protobuf": "^2.12.1",
|
|
41
|
-
"@oh-my-pi/omptype": "17.3.
|
|
42
|
-
"@oh-my-pi/pi-catalog": "17.3.
|
|
43
|
-
"@oh-my-pi/pi-utils": "17.3.
|
|
44
|
-
"@oh-my-pi/pi-wire": "17.3.
|
|
41
|
+
"@oh-my-pi/omptype": "17.3.5",
|
|
42
|
+
"@oh-my-pi/pi-catalog": "17.3.5",
|
|
43
|
+
"@oh-my-pi/pi-utils": "17.3.5",
|
|
44
|
+
"@oh-my-pi/pi-wire": "17.3.5"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"@bufbuild/protoc-gen-es": "^2.12.1",
|
|
@@ -249,12 +249,23 @@ export class AuthBrokerClient {
|
|
|
249
249
|
}
|
|
250
250
|
}
|
|
251
251
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
252
|
+
/**
|
|
253
|
+
* Fetch aggregate broker usage with a timeout sized for serialized
|
|
254
|
+
* same-provider account probes.
|
|
255
|
+
*/
|
|
256
|
+
fetchUsage(options: { signal?: AbortSignal; maxAccountsPerProvider?: number } = {}): Promise<UsageResponse> {
|
|
257
|
+
const requestedAccountCount = options.maxAccountsPerProvider;
|
|
258
|
+
const accountCount =
|
|
259
|
+
typeof requestedAccountCount === "number" && Number.isFinite(requestedAccountCount)
|
|
260
|
+
? Math.max(1, Math.floor(requestedAccountCount))
|
|
261
|
+
: 1;
|
|
262
|
+
const perAccountTimeoutMs = Math.max(DEFAULT_TIMEOUT_MS, this.#timeoutMs);
|
|
263
|
+
const timeoutMs = perAccountTimeoutMs * (accountCount + 1);
|
|
264
|
+
return this.#request<UsageResponse>("GET", "/v1/usage", {
|
|
265
|
+
schema: "usageResponseSchema",
|
|
266
|
+
signal: options.signal,
|
|
267
|
+
timeoutMs,
|
|
268
|
+
});
|
|
258
269
|
}
|
|
259
270
|
|
|
260
271
|
/** Recorded usage-limit snapshots from the broker host, oldest first. */
|
|
@@ -369,7 +380,13 @@ export class AuthBrokerClient {
|
|
|
369
380
|
async #request<t>(
|
|
370
381
|
method: "GET" | "POST" | "DELETE",
|
|
371
382
|
path: string,
|
|
372
|
-
opts: {
|
|
383
|
+
opts: {
|
|
384
|
+
schema: AuthBrokerResponseSchemaName;
|
|
385
|
+
auth?: boolean;
|
|
386
|
+
body?: unknown;
|
|
387
|
+
signal?: AbortSignal;
|
|
388
|
+
timeoutMs?: number;
|
|
389
|
+
},
|
|
373
390
|
): Promise<t> {
|
|
374
391
|
const response = await this.#fetchRaw(method, path, opts);
|
|
375
392
|
const text = await response.text();
|
|
@@ -253,6 +253,9 @@ export class RemoteAuthCredentialStore implements AuthCredentialStore {
|
|
|
253
253
|
#usageInflight?: Promise<UsageReport[] | null>;
|
|
254
254
|
#credentialBlockReconcileAfter: Map<string, number> = new Map();
|
|
255
255
|
#usageCacheEpoch = 0;
|
|
256
|
+
/** Raw broker credentials retained to size aggregate usage requests before account-pool filtering. */
|
|
257
|
+
#brokerUsageProviderByCredentialId = new Map<number, Provider>();
|
|
258
|
+
#brokerUsageAccountCounts = new Map<Provider, number>();
|
|
256
259
|
/** Per-snapshot lookup of oauth credentials by provider; rebuilt when `#snapshot` is replaced. */
|
|
257
260
|
#usageFilterLookup?: { snapshot: SnapshotResponse; byProvider: Map<Provider, OAuthCredential[]> };
|
|
258
261
|
/** Memoized `#filterUsageReports` output, keyed on (input identity, lookup identity). */
|
|
@@ -296,6 +299,7 @@ export class RemoteAuthCredentialStore implements AuthCredentialStore {
|
|
|
296
299
|
|
|
297
300
|
#applySnapshot(snapshot: SnapshotResponse, generation: number, protectNewBlocks = true): void {
|
|
298
301
|
const nowMs = Date.now();
|
|
302
|
+
this.#replaceBrokerUsageAccounts(snapshot.credentials);
|
|
299
303
|
const previousCredentials = this.#snapshot.credentials;
|
|
300
304
|
const credentials = snapshot.credentials
|
|
301
305
|
.filter(entry => isCredentialInAccountPool(entry, this.#accountPool))
|
|
@@ -427,8 +431,9 @@ export class RemoteAuthCredentialStore implements AuthCredentialStore {
|
|
|
427
431
|
generation: number,
|
|
428
432
|
serverNowMs: number,
|
|
429
433
|
): void {
|
|
434
|
+
this.#upsertBrokerUsageAccount(entry);
|
|
430
435
|
if (!isCredentialInAccountPool(entry, this.#accountPool)) {
|
|
431
|
-
this.#removeStreamCredential(entry.id, refresher, generation, serverNowMs);
|
|
436
|
+
this.#removeStreamCredential(entry.id, refresher, generation, serverNowMs, { retainBrokerUsageAccount: true });
|
|
432
437
|
return;
|
|
433
438
|
}
|
|
434
439
|
const incoming = this.#normalizeSnapshotEntryBlocks(entry, Date.now());
|
|
@@ -446,7 +451,14 @@ export class RemoteAuthCredentialStore implements AuthCredentialStore {
|
|
|
446
451
|
this.#snapshotReceivedAt = Date.now();
|
|
447
452
|
}
|
|
448
453
|
|
|
449
|
-
#removeStreamCredential(
|
|
454
|
+
#removeStreamCredential(
|
|
455
|
+
id: number,
|
|
456
|
+
refresher: RefresherSchedule,
|
|
457
|
+
generation: number,
|
|
458
|
+
serverNowMs: number,
|
|
459
|
+
options?: { retainBrokerUsageAccount?: boolean },
|
|
460
|
+
): void {
|
|
461
|
+
if (!options?.retainBrokerUsageAccount) this.#removeBrokerUsageAccount(id);
|
|
450
462
|
const removed = this.#snapshot.credentials.find(entry => entry.id === id);
|
|
451
463
|
if (removed?.blocks && removed.blocks.length > 0) this.#invalidateUsageCache();
|
|
452
464
|
const credentials = this.#snapshot.credentials.filter(entry => entry.id !== id);
|
|
@@ -1066,6 +1078,39 @@ export class RemoteAuthCredentialStore implements AuthCredentialStore {
|
|
|
1066
1078
|
});
|
|
1067
1079
|
}
|
|
1068
1080
|
|
|
1081
|
+
#replaceBrokerUsageAccounts(entries: readonly SnapshotEntry[]): void {
|
|
1082
|
+
this.#brokerUsageProviderByCredentialId.clear();
|
|
1083
|
+
this.#brokerUsageAccountCounts.clear();
|
|
1084
|
+
for (const entry of entries) this.#upsertBrokerUsageAccount(entry);
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
#upsertBrokerUsageAccount(entry: Pick<SnapshotEntry, "id" | "provider">): void {
|
|
1088
|
+
const previous = this.#brokerUsageProviderByCredentialId.get(entry.id);
|
|
1089
|
+
if (previous === entry.provider) return;
|
|
1090
|
+
if (previous !== undefined) {
|
|
1091
|
+
const count = this.#brokerUsageAccountCounts.get(previous) ?? 0;
|
|
1092
|
+
if (count <= 1) this.#brokerUsageAccountCounts.delete(previous);
|
|
1093
|
+
else this.#brokerUsageAccountCounts.set(previous, count - 1);
|
|
1094
|
+
}
|
|
1095
|
+
this.#brokerUsageProviderByCredentialId.set(entry.id, entry.provider);
|
|
1096
|
+
this.#brokerUsageAccountCounts.set(entry.provider, (this.#brokerUsageAccountCounts.get(entry.provider) ?? 0) + 1);
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
#removeBrokerUsageAccount(id: number): void {
|
|
1100
|
+
const provider = this.#brokerUsageProviderByCredentialId.get(id);
|
|
1101
|
+
if (provider === undefined) return;
|
|
1102
|
+
this.#brokerUsageProviderByCredentialId.delete(id);
|
|
1103
|
+
const count = this.#brokerUsageAccountCounts.get(provider) ?? 0;
|
|
1104
|
+
if (count <= 1) this.#brokerUsageAccountCounts.delete(provider);
|
|
1105
|
+
else this.#brokerUsageAccountCounts.set(provider, count - 1);
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
#maxBrokerUsageAccounts(): number {
|
|
1109
|
+
let maximum = 1;
|
|
1110
|
+
for (const count of this.#brokerUsageAccountCounts.values()) maximum = Math.max(maximum, count);
|
|
1111
|
+
return maximum;
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1069
1114
|
#loadUsageReports(): Promise<UsageReport[] | null> {
|
|
1070
1115
|
const cached = this.#usageCache;
|
|
1071
1116
|
if (cached && Date.now() - cached.fetchedAt < USAGE_CACHE_TTL_MS) {
|
|
@@ -1074,7 +1119,7 @@ export class RemoteAuthCredentialStore implements AuthCredentialStore {
|
|
|
1074
1119
|
if (this.#usageInflight) return this.#usageInflight;
|
|
1075
1120
|
const epoch = this.#usageCacheEpoch;
|
|
1076
1121
|
const inflight = this.#client
|
|
1077
|
-
.fetchUsage()
|
|
1122
|
+
.fetchUsage({ maxAccountsPerProvider: this.#maxBrokerUsageAccounts() })
|
|
1078
1123
|
.then(body => {
|
|
1079
1124
|
if (epoch !== this.#usageCacheEpoch) return this.#loadUsageReports();
|
|
1080
1125
|
this.#usageCache = { reports: body.reports, fetchedAt: Date.now() };
|
|
@@ -741,7 +741,7 @@ export function startAuthBroker(opts: AuthBrokerServerOptions): AuthBrokerServer
|
|
|
741
741
|
}
|
|
742
742
|
if (req.method === "POST" && pathname === "/v1/usage/stale") {
|
|
743
743
|
try {
|
|
744
|
-
opts.storage.invalidateUsageCache?.();
|
|
744
|
+
await opts.storage.invalidateUsageCache?.();
|
|
745
745
|
logger.info("auth-broker usage cache invalidated", { peer });
|
|
746
746
|
return json(200, { ok: true });
|
|
747
747
|
} catch (error) {
|