@oh-my-pi/pi-ai 18.12.3 → 18.12.4
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 +29 -56
- package/dist/types/auth/rank.d.ts +4 -0
- package/dist/types/auth/types.d.ts +2 -0
- package/dist/types/auth-broker/remote-store.d.ts +16 -6
- package/dist/types/providers/amazon-bedrock.d.ts +7 -5
- package/dist/types/providers/anthropic-compaction.d.ts +8 -0
- package/dist/types/providers/anthropic-state.d.ts +8 -2
- package/dist/types/providers/anthropic-wire.d.ts +3 -0
- package/dist/types/providers/anthropic.d.ts +19 -2
- package/dist/types/providers/factory-droid/tool-names.d.ts +21 -0
- package/dist/types/types.d.ts +2 -0
- package/dist/types/utils/tool-call-loop-guard.d.ts +2 -0
- package/package.json +6 -6
- package/src/auth/policy.ts +26 -10
- package/src/auth/rank.ts +6 -0
- package/src/auth/select.ts +25 -6
- package/src/auth/types.ts +2 -0
- package/src/auth-broker/discover.ts +67 -17
- package/src/auth-broker/remote-store.ts +59 -16
- package/src/images/google-antigravity.ts +0 -1
- package/src/providers/amazon-bedrock.ts +421 -142
- package/src/providers/anthropic-compaction.ts +13 -0
- package/src/providers/anthropic-state.ts +16 -2
- package/src/providers/anthropic-wire.ts +9 -1
- package/src/providers/anthropic.ts +271 -74
- package/src/providers/cursor-pi-args.ts +3 -1
- package/src/providers/devin.ts +13 -43
- package/src/providers/factory-droid/gemini.ts +1 -30
- package/src/providers/factory-droid/tool-names.ts +44 -0
- package/src/providers/factory-droid.ts +91 -20
- package/src/providers/google-gemini-cli.ts +0 -1
- package/src/types.ts +2 -0
- package/src/usage/devin.ts +3 -3
- package/src/utils/leaked-thinking-stream.ts +12 -0
- package/src/utils/thinking-loop.ts +75 -14
- package/src/utils/tool-call-loop-guard.ts +14 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,34 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [18.12.4] - 2026-10-20
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Added opt-in `reclaimAbovePct` to `auth.accountPolicies`: sessions automatically moved to a lower-priority account return to the preferred one once it has that much quota left ([#14237](https://github.com/can1357/oh-my-pi/pull/14237) by [@will-bogusz](https://github.com/will-bogusz))
|
|
10
|
+
- Added `reachesBudgetedHost()`: whether the catalog's host request budgets (inline image bytes, official image count) apply to a request, true on a model's official endpoint and on Factory Droid's own hosts ([#16288](https://github.com/can1357/oh-my-pi/pull/16288) by [@will-bogusz](https://github.com/will-bogusz))
|
|
11
|
+
- Added Inception API-key login (`/login inception`) and `INCEPTION_API_KEY` environment authentication ([#16439](https://github.com/can1357/oh-my-pi/pull/16439) by [@eggpeat](https://github.com/eggpeat))
|
|
12
|
+
- Added image support for native OpenAI Decisions and Workers AI judgments, prompted vision chat, and auth-gateway System One requests; images exceeding a judgment API's documented caps (e.g. Clef's 4 images, 4 MiB each) are rejected locally ([#15471](https://github.com/can1357/oh-my-pi/pull/15471) by [@anatoli-tsinovoy](https://github.com/anatoli-tsinovoy)).
|
|
13
|
+
- `isSurvivingAssistant` in `providers/transform-messages`: whether an assistant turn reaches a replay, malformed tool calls dropped first, so callers outside `transformMessages` pick the same latest turn ([#15791](https://github.com/can1357/oh-my-pi/pull/15791) by [@will-bogusz](https://github.com/will-bogusz))
|
|
14
|
+
- Broker account-pool files (`OMP_AUTH_BROKER_ACCOUNT_POOL_FILE`) can hide whole providers and API keys: `"*": false` makes the file an allow-list, and an entry can be `true`, `false`, or `{ "accounts": [...], "apiKeys": false }` ([#14235](https://github.com/can1357/oh-my-pi/pull/14235) by [@will-bogusz](https://github.com/will-bogusz))
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- `cursorRawReadPath()` writes a bare start line as `raw:N-` (`file:10` → `file:raw:10-`), so Cursor exec reads of such a path still read from line `N` onward now that `read` takes `N:raw` as one line ([#16438](https://github.com/can1357/oh-my-pi/pull/16438) by [@will-bogusz](https://github.com/will-bogusz))
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- Fixed Anthropic and Bedrock logging one warning per dropped thinking block (hundreds for one request) after a conversation change; the drop now logs one warning with the count ([#16392](https://github.com/can1357/oh-my-pi/pull/16392) by [@will-bogusz](https://github.com/will-bogusz))
|
|
23
|
+
- Fixed Factory Droid Core models (Kimi, GLM, DeepSeek and others) dropping calls to tools with names over 64 characters and their results from later requests; such names now shorten to 64 characters, and shorter names are sent unchanged ([#16384](https://github.com/can1357/oh-my-pi/pull/16384) by [@will-bogusz](https://github.com/will-bogusz))
|
|
24
|
+
- Fixed Devin `/usage` showing no data for accounts signed in with `/login devin`, and Devin chat sending a failing extra auth request for legacy Windsurf Enterprise keys ([#13677](https://github.com/can1357/oh-my-pi/pull/13677) by [@will-bogusz](https://github.com/will-bogusz))
|
|
25
|
+
- Turns stopped for a thinking loop now keep the tokens and cost the provider had reported, the response id, the request's start time, and its time to first token and duration, instead of recording zero usage and no timing ([#16437](https://github.com/can1357/oh-my-pi/pull/16437) by [@will-bogusz](https://github.com/will-bogusz))
|
|
26
|
+
- Fixed Claude requests answering a turn whose attachment is a video, PDF, HEIC or other format sent outside a JPEG/PNG/GIF/WebP image as if nothing were attached; the turn now fails with an error naming the format ([#16407](https://github.com/can1357/oh-my-pi/pull/16407) by [@will-bogusz](https://github.com/will-bogusz))
|
|
27
|
+
- Fixed the repeated-tool-call guard missing a loop the model interleaves with a `wait` that elapses with nothing new; a wait that returns finished work still ends the repetition ([#11874](https://github.com/can1357/oh-my-pi/pull/11874) by [@dickyudhandika](https://github.com/dickyudhandika))
|
|
28
|
+
- Fixed Google Antigravity chat and image requests failing with false 429 quota-exhausted errors ([#11849](https://github.com/can1357/oh-my-pi/pull/11849) by [@jotafurtado](https://github.com/jotafurtado))
|
|
29
|
+
- Fixed the stream loop guard never stopping a model stuck repeating punctuation or symbols (`?!?!`, `✓✓✓`) until the output limit; such runs now trip once they repeat for 1 KB, while digit and whitespace runs such as padding and hexdumps are left alone ([#14436](https://github.com/can1357/oh-my-pi/pull/14436) by [@F0Rextasy](https://github.com/F0Rextasy))
|
|
30
|
+
- Fixed native remote compaction for supported Bedrock Claude models without changing ordinary Converse turns; a custom non-AWS `baseUrl` needs `remoteCompaction.enabled: true` ([#16343](https://github.com/can1357/oh-my-pi/pull/16343) by [@anatoli-tsinovoy](https://github.com/anatoli-tsinovoy))
|
|
31
|
+
- Fixed Anthropic compaction blocks dropping their signed `tool_changes`, which made the replayed block fail its signature ([#16343](https://github.com/can1357/oh-my-pi/pull/16343) by [@anatoli-tsinovoy](https://github.com/anatoli-tsinovoy))
|
|
32
|
+
|
|
5
33
|
## [18.12.3] - 2026-10-19
|
|
6
34
|
|
|
7
35
|
### Added
|
|
@@ -2292,59 +2320,4 @@
|
|
|
2292
2320
|
- Fixed OpenRouter Anthropic Responses follow-up requests replaying prior reasoning items with stale signatures, which caused HTTP 400 `Invalid signature in thinking block` errors after a thinking turn. ([#3399](https://github.com/can1357/oh-my-pi/issues/3399))
|
|
2293
2321
|
- Fixed OpenRouter Anthropic models on the Responses path omitting `cache_control`, so prompt caching engages without forcing Chat Completions. `cacheRetention: "long"` now upgrades the breakpoint to `ttl: "1h"`. ([#3397](https://github.com/can1357/oh-my-pi/issues/3397))
|
|
2294
2322
|
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
### Fixed
|
|
2298
|
-
|
|
2299
|
-
- Fixed Anthropic-compatible thinking requests sending replayed thinking blocks without `context_management.keep: "all"`, preserving multi-turn reasoning context for API-key providers. API-key requests now also advertise the required `context-management-2025-06-27` beta header so the field is honored instead of rejected. Injected SDK clients, GitHub Copilot's Anthropic proxy, and Vertex rawPredict are excluded because this code path cannot add the beta to caller-owned clients, Copilot strips Anthropic betas and demotes thinking blocks to text upstream, and Vertex expects betas in the JSON body rather than the Anthropic HTTP beta header. ([#3288](https://github.com/can1357/oh-my-pi/issues/3288))
|
|
2300
|
-
- Fixed OpenRouter Responses native history replay leaking Gemini reasoning item `format` metadata back into follow-up requests, which caused HTTP 400 rejections while preserving encrypted reasoning replay.
|
|
2301
|
-
|
|
2302
|
-
## [16.1.15] - 2026-06-22
|
|
2303
|
-
|
|
2304
|
-
### Fixed
|
|
2305
|
-
|
|
2306
|
-
- Fixed API-key `/login` providers replacing sibling credentials instead of appending new keys for the same provider. ([#3265](https://github.com/can1357/oh-my-pi/issues/3265))
|
|
2307
|
-
- Fixed OpenAI Codex OAuth account rotation for quota failures that surface as bare HTTP 429 or `insufficient_quota`, so pre-content failures temporarily block only the exhausted credential and retry a healthy sibling. The 429 status-only fallback applies only to absent/opaque bodies; informative transient bodies (`Too many requests`, `Service overloaded 529`, `Please retry in 5s`, …) defer to `parseRateLimitReason` and stay in the provider's own backoff layer instead of burning sibling credentials. ([#3231](https://github.com/can1357/oh-my-pi/issues/3231))
|
|
2308
|
-
|
|
2309
|
-
## [16.1.14] - 2026-06-22
|
|
2310
|
-
|
|
2311
|
-
### Added
|
|
2312
|
-
|
|
2313
|
-
- Added proxy support for model providers via `PI_PROXY` and `PI_PROXY_<PROVIDER>` variables
|
|
2314
|
-
- Added `NO_PROXY` environment variable support for bypassing proxy configuration
|
|
2315
|
-
- Added support for Sakana AI provider
|
|
2316
|
-
- Added Sakana AI login and request base URL support for `SAKANA_*` / `FUGU_*` environment variables
|
|
2317
|
-
|
|
2318
|
-
### Changed
|
|
2319
|
-
|
|
2320
|
-
- Consolidated API key authentication logic across registry providers
|
|
2321
|
-
- Disabled parallel tool calls for Devin provider requests
|
|
2322
|
-
|
|
2323
|
-
### Fixed
|
|
2324
|
-
|
|
2325
|
-
- Improved proxy bypass logic to correctly handle private IP ranges and local metadata services
|
|
2326
|
-
- Enhanced memoization for proxy environment variable lookups to improve performance
|
|
2327
|
-
|
|
2328
|
-
## [16.1.13] - 2026-06-22
|
|
2329
|
-
|
|
2330
|
-
### Added
|
|
2331
|
-
|
|
2332
|
-
- Added support for Devin as a provider
|
|
2333
|
-
|
|
2334
|
-
### Changed
|
|
2335
|
-
|
|
2336
|
-
- Updated tool call arguments to use `Record<string, unknown>` and `unknown` for tool results
|
|
2337
|
-
|
|
2338
|
-
### Fixed
|
|
2339
|
-
|
|
2340
|
-
- Fixed OpenAI Responses native history replay dropping failed/incomplete image generation calls instead of resending their transient `ig_...` item IDs, preventing follow-up requests from failing with `404 Item with id ... not found`. ([#3225](https://github.com/can1357/oh-my-pi/issues/3225))
|
|
2341
|
-
- Fixed `/login fireworks` rejecting valid `fw_…` keys with `Fireworks API key validation failed (500): Error listing deployed models`. The validator pinged `/inference/v1/models`, which Fireworks serves from the per-account deployment registry and 500s for accounts without active deployments. Login now hits the static control-plane `List Models` catalog (`GET /v1/accounts/fireworks/models?filter=supports_serverless=true&pageSize=1`) — the same endpoint discovery already uses — so authentication no longer depends on the caller's deployment state. ([#3219](https://github.com/can1357/oh-my-pi/issues/3219))
|
|
2342
|
-
|
|
2343
|
-
## [16.1.11] - 2026-06-21
|
|
2344
|
-
|
|
2345
|
-
### Fixed
|
|
2346
|
-
|
|
2347
|
-
- Fixed OpenAI Responses native history replay leaking image generation provider-only fields into the next request, which made OpenAI-compatible proxies reject `pi` tool-calling sessions with `Unknown parameter: input[1].action`. ([#3201](https://github.com/can1357/oh-my-pi/issues/3201))
|
|
2348
|
-
- Fixed a stream thought-leakage issue for `gemini-3.5-flash` where the model's internal reasoning JSON could leak into the visible text stream. The stream parser now uses a brace-balanced counting algorithm to accurately slice and discard the leading thought JSON block, with a robust fallback for unescaped double quotes, dynamic tool-name derivation, and preservation of subsequent text deltas without triggering empty-response retries.
|
|
2349
|
-
|
|
2350
|
-
Older entries are archived in [packages/ai/CHANGELOG.md@5eb78e90fa4e](https://github.com/can1357/oh-my-pi/blob/5eb78e90fa4ec4318b9520302f1977fa58c48745/packages/ai/CHANGELOG.md).
|
|
2323
|
+
Older entries are archived in [packages/ai/CHANGELOG.md@47e6af055a17](https://github.com/can1357/oh-my-pi/blob/47e6af055a17862bc1cab6b5ebedd3db75ac2573/packages/ai/CHANGELOG.md).
|
|
@@ -23,6 +23,10 @@ export type UsageCandidate<T extends AuthCredential> = {
|
|
|
23
23
|
* spent, so if it still serves it draws on paid overage such as Codex credits.
|
|
24
24
|
*/
|
|
25
25
|
allowanceSpent?: boolean;
|
|
26
|
+
/** Present after policy-aware ranking: the account's configured `priority` (0 when unset). */
|
|
27
|
+
accountPriority?: number;
|
|
28
|
+
/** Present after policy-aware ranking: unblocked and measured at or above its `reclaimAbovePct`. */
|
|
29
|
+
reclaimReady?: boolean;
|
|
26
30
|
};
|
|
27
31
|
/** OAuth credential eligible for usage ranking. */
|
|
28
32
|
export type OAuthCandidate = UsageCandidate<OAuthCredential>;
|
|
@@ -36,6 +36,8 @@ export interface AuthAccountPolicy {
|
|
|
36
36
|
readonly priority?: number;
|
|
37
37
|
/** Protected remaining quota percentage for this account. */
|
|
38
38
|
readonly reservePct?: number;
|
|
39
|
+
/** Remaining quota percentage, above the reserve, at which this account takes back lower-priority warm pins. */
|
|
40
|
+
readonly reclaimAbovePct?: number;
|
|
39
41
|
}
|
|
40
42
|
/** Read-only set of per-account routing policies. */
|
|
41
43
|
export type AuthAccountPolicies = readonly AuthAccountPolicy[];
|
|
@@ -5,12 +5,21 @@ import type { Provider } from "../types.js";
|
|
|
5
5
|
import type { ClientUsageIdentity, ObservedUsageEntry, UsageReport } from "../usage.js";
|
|
6
6
|
import { type AuthBrokerClient } from "./client.js";
|
|
7
7
|
import type { SnapshotResponse } from "./types.js";
|
|
8
|
+
/** Visibility rule for one provider's broker credentials. */
|
|
9
|
+
export interface AuthBrokerProviderPool {
|
|
10
|
+
/** OAuth `identityKey`s visible to this client; omitted = every OAuth account. */
|
|
11
|
+
readonly accounts?: ReadonlySet<string>;
|
|
12
|
+
/** Whether API-key credentials are visible. Default `true`. */
|
|
13
|
+
readonly apiKeys?: boolean;
|
|
14
|
+
}
|
|
8
15
|
/**
|
|
9
|
-
* Per-provider
|
|
10
|
-
*
|
|
11
|
-
* OAuth
|
|
16
|
+
* Per-provider credential visibility for this trusted broker client. A set
|
|
17
|
+
* value lists the provider's visible OAuth identities and keeps its API keys
|
|
18
|
+
* (an empty set excludes every OAuth credential); a rule object can also hide
|
|
19
|
+
* API keys. Providers without an entry follow the `"*"` rule, which defaults
|
|
20
|
+
* to unrestricted.
|
|
12
21
|
*/
|
|
13
|
-
export type AuthBrokerAccountPool = ReadonlyMap<string, ReadonlySet<string
|
|
22
|
+
export type AuthBrokerAccountPool = ReadonlyMap<string, ReadonlySet<string> | AuthBrokerProviderPool>;
|
|
14
23
|
export interface RemoteAuthCredentialStoreOptions {
|
|
15
24
|
client: AuthBrokerClient;
|
|
16
25
|
/**
|
|
@@ -30,8 +39,9 @@ export interface RemoteAuthCredentialStoreOptions {
|
|
|
30
39
|
*/
|
|
31
40
|
onSnapshot?: (snapshot: SnapshotResponse, generation: number) => void;
|
|
32
41
|
/**
|
|
33
|
-
*
|
|
34
|
-
* routing policy, not broker
|
|
42
|
+
* Broker credentials visible through this store (OAuth identities and API
|
|
43
|
+
* keys per provider). This is a trusted-client routing policy, not broker
|
|
44
|
+
* authorization.
|
|
35
45
|
*/
|
|
36
46
|
accountPool?: AuthBrokerAccountPool;
|
|
37
47
|
/** Flush cadence for batched observed-usage reports. Default 10s. */
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Amazon Bedrock
|
|
2
|
+
* Amazon Bedrock chat transport.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* Ordinary requests use Converse Stream; server compaction uses the
|
|
5
|
+
* Anthropic Messages format over Bedrock's InvokeModelWithResponseStream API.
|
|
6
|
+
* Both call `bedrock-runtime.{region}.amazonaws.com` directly over HTTPS with
|
|
7
|
+
* shared credential resolution and SigV4 signing. The normal path decodes
|
|
8
|
+
* `application/vnd.amazon.eventstream`; no AWS SDK, Smithy, or proxy-agent
|
|
9
|
+
* dependency is required. Bun's native `HTTPS_PROXY` support is honored.
|
|
8
10
|
*
|
|
9
11
|
* A `models.yml` `baseUrl` is the request origin verbatim (VPC endpoint, gateway, …);
|
|
10
12
|
* only AWS's own regional host is re-pointed at the resolved region. SigV4 unaffected.
|
|
@@ -4,6 +4,14 @@ import type { AnthropicMessagesClientLike } from "./anthropic-client.js";
|
|
|
4
4
|
export declare function resolvesToOfficialAnthropicEndpoint(model: Model<"anthropic-messages">): boolean;
|
|
5
5
|
/** Whether model policy and the effective deployment support on-demand compaction. */
|
|
6
6
|
export declare function supportsAnthropicCompaction(model: Model<"anthropic-messages">, effectiveBaseUrl?: string): boolean;
|
|
7
|
+
/**
|
|
8
|
+
* Whether a Bedrock Converse model compacts through a Bedrock Runtime
|
|
9
|
+
* `InvokeModelWithResponseStream` side request. AWS bedrock-runtime origins
|
|
10
|
+
* (public, FIPS, PrivateLink) qualify by default; any other `baseUrl` is a
|
|
11
|
+
* gateway that may implement only Converse, so it needs
|
|
12
|
+
* `remoteCompaction.enabled: true`. `enabled: false` always opts out.
|
|
13
|
+
*/
|
|
14
|
+
export declare function supportsBedrockConverseCompaction(model: Model<"bedrock-converse-stream">): boolean;
|
|
7
15
|
/** Read a caller-owned client's endpoint for request and compaction routing. */
|
|
8
16
|
export declare function injectedClientBaseUrl(client: AnthropicMessagesClientLike): string | undefined;
|
|
9
17
|
/** Whether a caller-owned Anthropic client targets a compaction-capable endpoint. */
|
|
@@ -10,10 +10,16 @@ export declare function resolveDirectAnthropicBaseUrl(model: Model<Api>): string
|
|
|
10
10
|
/**
|
|
11
11
|
* Whether a request to `model` reaches the official endpoint its compat was built for. `compat.officialEndpoint`
|
|
12
12
|
* classifies the configured `baseUrl`, but a first-party Anthropic model is rerouted at request time by
|
|
13
|
-
* `ANTHROPIC_BASE_URL` or Foundry's `FOUNDRY_BASE_URL` without rebuilding compat.
|
|
14
|
-
* limits such as the catalog's inline image byte and image count budgets.
|
|
13
|
+
* `ANTHROPIC_BASE_URL` or Foundry's `FOUNDRY_BASE_URL` without rebuilding compat.
|
|
15
14
|
*/
|
|
16
15
|
export declare function reachesOfficialEndpoint(model: Model<Api>): boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Whether the catalog's host request budgets (inline image bytes, official image count) apply to a request to
|
|
18
|
+
* `model`: it reaches the official endpoint ({@link reachesOfficialEndpoint}), or its rules set
|
|
19
|
+
* `catalog-endpoint-budgets` (Factory Droid's hosted API) and it reaches the provider's own catalog endpoint
|
|
20
|
+
* (unset `baseUrl`, or an `endpoint-hosts` host). A custom gateway keeps its own limits.
|
|
21
|
+
*/
|
|
22
|
+
export declare function reachesBudgetedHost(model: Model<Api>): boolean;
|
|
17
23
|
/** Build the endpoint-and-model-scoped Anthropic session-state key. */
|
|
18
24
|
export declare function anthropicProviderSessionStateKey(baseUrl: string, modelId: string): string;
|
|
19
25
|
/** Re-arm fast mode across materialized Anthropic session-state entries. */
|
|
@@ -146,6 +146,8 @@ export declare const LEGACY_COMPACTION_BETA = "compact-2026-01-12";
|
|
|
146
146
|
export type CompactionBlockParam = {
|
|
147
147
|
type: "compaction";
|
|
148
148
|
content: string;
|
|
149
|
+
/** Opaque signed tool changes from a compaction response. */
|
|
150
|
+
tool_changes?: Record<string, unknown>[];
|
|
149
151
|
signature?: string;
|
|
150
152
|
encrypted_content?: string | null;
|
|
151
153
|
cache_control?: CacheControlEphemeral | null;
|
|
@@ -396,6 +398,7 @@ export type ResponseContentBlock = {
|
|
|
396
398
|
content: string;
|
|
397
399
|
signature?: string;
|
|
398
400
|
encrypted_content?: string | null;
|
|
401
|
+
tool_changes?: Record<string, unknown>[];
|
|
399
402
|
};
|
|
400
403
|
export type ContentBlockDelta = {
|
|
401
404
|
type: "text_delta";
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { Effort } from "@oh-my-pi/pi-catalog/effort";
|
|
2
|
-
import type { AnthropicCompactionRequest, AnthropicOutputEffort, Api, FetchImpl, Message, Model, ServiceTier, SimpleStreamOptions, StreamFunction, StreamOptions, Usage } from "../types.js";
|
|
2
|
+
import type { AnthropicCompactionRequest, AnthropicOutputEffort, Api, Context, FetchImpl, Message, Model, ServiceTier, SimpleStreamOptions, StreamFunction, StreamOptions, Usage } from "../types.js";
|
|
3
|
+
import { AssistantMessageEventStream } from "../utils/event-stream.js";
|
|
4
|
+
import { type RawHttpRequestDump } from "../utils/http-inspector.js";
|
|
3
5
|
import { type AnthropicFetchOptions, type AnthropicMessagesClientLike } from "./anthropic-client.js";
|
|
4
|
-
import { type FallbackParam, type MessageParam, type TextBlockParam } from "./anthropic-wire.js";
|
|
6
|
+
import { type FallbackParam, type MessageCreateParamsStreaming, type MessageParam, type TextBlockParam } from "./anthropic-wire.js";
|
|
5
7
|
import { resolvesToOfficialAnthropicEndpoint, supportsAnthropicCompaction, supportsAnthropicCompactionOnClient } from "./anthropic-compaction.js";
|
|
6
8
|
import { applyClaudeToolPrefix, deriveClaudeDeviceId, generateClaudeCloakingUserId, isClaudeCloakingUserId, resolveAnthropicMetadataUserId, stripClaudeToolPrefix } from "./anthropic-identity.js";
|
|
7
9
|
import { clearAnthropicFastModeFallback, isAnthropicFastModeFallbackDisabled, normalizeAnthropicBaseUrl } from "./anthropic-state.js";
|
|
@@ -204,6 +206,13 @@ export declare function applyAnthropicUsageExtras(usage: Usage, source: Anthropi
|
|
|
204
206
|
/** Detects the preserved-thinking error caused by rewriting a signed block's conversation prefix. */
|
|
205
207
|
export declare function isThinkingPrefixBindingError(message: string): boolean;
|
|
206
208
|
export declare function isInvalidThinkingSignatureError(message: string): boolean;
|
|
209
|
+
/**
|
|
210
|
+
* Warn once for the signed thinking blocks the API dropped from one request
|
|
211
|
+
* after the conversation prefix changed: the count and first path at warn,
|
|
212
|
+
* every path at debug. One prefix change can drop hundreds of blocks in a
|
|
213
|
+
* single request, which would otherwise flood the log one line per block.
|
|
214
|
+
*/
|
|
215
|
+
export declare function logDroppedThinking(provider: string, model: string, paths: Array<string | undefined>): void;
|
|
207
216
|
/**
|
|
208
217
|
* Prepend a pointed remediation to a thinking-signature rejection 400 when the
|
|
209
218
|
* model looks like an unmarked custom signing proxy
|
|
@@ -288,6 +297,14 @@ export declare function resolveAnthropicCompactionEffort(model: Model<"anthropic
|
|
|
288
297
|
* gate its header-level interleaved beta on the same decision.
|
|
289
298
|
*/
|
|
290
299
|
export declare function shouldStripThinkingHistory(messages: readonly Message[]): boolean;
|
|
300
|
+
/**
|
|
301
|
+
* Route Bedrock's on-demand Anthropic compaction through the shared Messages
|
|
302
|
+
* stream consumer while keeping source-provider identity and pricing.
|
|
303
|
+
*/
|
|
304
|
+
export declare function streamBedrockAnthropicCompaction(sourceModel: Model<"bedrock-converse-stream">, context: Context, options: AnthropicOptions, prepareRequest: (params: MessageCreateParamsStreaming, betas: readonly string[]) => Promise<{
|
|
305
|
+
payload: unknown;
|
|
306
|
+
rawRequestDump: RawHttpRequestDump;
|
|
307
|
+
}>, createClient: (getPayload: () => unknown) => AnthropicMessagesClientLike, prepareOptions: (options: AnthropicOptions) => AnthropicOptions, forceThinkingOffFields?: Record<string, unknown>): AssistantMessageEventStream;
|
|
291
308
|
/**
|
|
292
309
|
* A single Anthropic conversation turn, including the mid-conversation
|
|
293
310
|
* `system` role (Opus 4.8+ and Fable/Mythos 5).
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Tool } from "../../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* The CLI sanitizes tool names to `[a-zA-Z0-9_-]`; a name longer than
|
|
4
|
+
* `maxLength` (64 by default) keeps its first `maxLength - 9` chars and ends
|
|
5
|
+
* `_` + an 8-char sha256, `maxLength` in all, which stays inside the route's
|
|
6
|
+
* tool-name bound. Applied to declarations and replayed call/result names on
|
|
7
|
+
* every wire droid sanitizes.
|
|
8
|
+
*/
|
|
9
|
+
export declare function sanitizeFactoryDroidToolName(name: string, maxLength?: number): string;
|
|
10
|
+
/**
|
|
11
|
+
* Chat-completions names go out verbatim (the route accepts dots) unless they
|
|
12
|
+
* exceed `maxLength`, the target's catalog `tool-call-name-max-length`; only
|
|
13
|
+
* those take the capped, sanitized form.
|
|
14
|
+
*/
|
|
15
|
+
export declare function encodeFactoryDroidCompletionsToolName(name: string, maxLength: number): string;
|
|
16
|
+
/**
|
|
17
|
+
* Wire name -> caller tool name for the declared tools, so returned calls
|
|
18
|
+
* dispatch to the tool that was advertised. Two tools sharing one wire name
|
|
19
|
+
* could not be told apart, so that request is rejected.
|
|
20
|
+
*/
|
|
21
|
+
export declare function factoryDroidToolNamesByWire(tools: Tool[] | undefined, encode?: (name: string) => string): Map<string, string>;
|
package/dist/types/types.d.ts
CHANGED
|
@@ -885,6 +885,8 @@ export interface AnthropicCompactionPayload {
|
|
|
885
885
|
content: string;
|
|
886
886
|
/** Signature of an on-demand block; replayed verbatim. */
|
|
887
887
|
signature?: string;
|
|
888
|
+
/** Net tool changes across the summarized range; covered by the signature, replayed verbatim. */
|
|
889
|
+
toolChanges?: Record<string, unknown>[];
|
|
888
890
|
/** Legacy threshold block state (`compact-2026-01-12`); replay-only. */
|
|
889
891
|
encryptedContent?: string;
|
|
890
892
|
/**
|
|
@@ -8,6 +8,8 @@ export interface ToolCallLoopGuardOptions {
|
|
|
8
8
|
export interface ToolCallLoopTurn {
|
|
9
9
|
readonly message: AssistantMessage;
|
|
10
10
|
readonly toolResults: readonly ToolResultMessage[];
|
|
11
|
+
/** The turn also delivered context outside its tool results (extension `additionalContext`). */
|
|
12
|
+
readonly injectedContext?: boolean;
|
|
11
13
|
}
|
|
12
14
|
/** Details needed to steer the model away from a repeated tool call. */
|
|
13
15
|
export interface RepeatedToolCallDetection {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oh-my-pi/pi-ai",
|
|
3
|
-
"version": "18.12.
|
|
3
|
+
"version": "18.12.4",
|
|
4
4
|
"description": "Unified LLM API with automatic model discovery and provider configuration",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -155,11 +155,11 @@
|
|
|
155
155
|
"fmt": "oxfmt --no-error-on-unmatched-pattern 'src/**/*.{ts,tsx}' '{test,bench,examples,scripts}/**/*.ts' '*.ts'"
|
|
156
156
|
},
|
|
157
157
|
"dependencies": {
|
|
158
|
-
"@oh-my-pi/omptype": "18.12.
|
|
159
|
-
"@oh-my-pi/pi-catalog": "18.12.
|
|
160
|
-
"@oh-my-pi/pi-natives": "18.12.
|
|
161
|
-
"@oh-my-pi/pi-utils": "18.12.
|
|
162
|
-
"@oh-my-pi/pi-wire": "18.12.
|
|
158
|
+
"@oh-my-pi/omptype": "18.12.4",
|
|
159
|
+
"@oh-my-pi/pi-catalog": "18.12.4",
|
|
160
|
+
"@oh-my-pi/pi-natives": "18.12.4",
|
|
161
|
+
"@oh-my-pi/pi-utils": "18.12.4",
|
|
162
|
+
"@oh-my-pi/pi-wire": "18.12.4"
|
|
163
163
|
},
|
|
164
164
|
"devDependencies": {
|
|
165
165
|
"@types/bun": "^1.3.14"
|
package/src/auth/policy.ts
CHANGED
|
@@ -28,12 +28,13 @@ export class AccountPolicies {
|
|
|
28
28
|
#warnedUnmatched = new Set<number>();
|
|
29
29
|
|
|
30
30
|
constructor(policies: AuthAccountPolicies, defaultReservePct: number | undefined) {
|
|
31
|
-
|
|
32
|
-
this.#accountPolicies = policies;
|
|
33
|
-
this.#defaultReservePct =
|
|
31
|
+
const effectiveDefaultReservePct =
|
|
34
32
|
typeof defaultReservePct === "number" && Number.isFinite(defaultReservePct)
|
|
35
33
|
? Math.max(0, Math.min(100, defaultReservePct))
|
|
36
34
|
: DEFAULT_USAGE_RESERVE_PCT;
|
|
35
|
+
AccountPolicies.#validateAccountPolicyConfiguration(policies, effectiveDefaultReservePct);
|
|
36
|
+
this.#accountPolicies = policies;
|
|
37
|
+
this.#defaultReservePct = effectiveDefaultReservePct;
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
/** Global usage reserve (0–100) for accounts without a per-account `reservePct`. */
|
|
@@ -58,7 +59,7 @@ export class AccountPolicies {
|
|
|
58
59
|
this.#warnedUnmatched = next.#warnedUnmatched;
|
|
59
60
|
}
|
|
60
61
|
|
|
61
|
-
static #validateAccountPolicyConfiguration(accountPolicies: AuthAccountPolicies): void {
|
|
62
|
+
static #validateAccountPolicyConfiguration(accountPolicies: AuthAccountPolicies, defaultReservePct: number): void {
|
|
62
63
|
for (let index = 0; index < accountPolicies.length; index += 1) {
|
|
63
64
|
const policy = accountPolicies[index]!;
|
|
64
65
|
const path = `auth.accountPolicies[${index}]`;
|
|
@@ -95,17 +96,32 @@ export class AccountPolicies {
|
|
|
95
96
|
) {
|
|
96
97
|
throw new AIError.ConfigurationError(`${path}.reservePct must be a finite number between 0 and 100`);
|
|
97
98
|
}
|
|
99
|
+
if (policy.reclaimAbovePct !== undefined) {
|
|
100
|
+
const reservePct = policy.reservePct ?? defaultReservePct;
|
|
101
|
+
if (
|
|
102
|
+
!Number.isFinite(policy.reclaimAbovePct) ||
|
|
103
|
+
policy.reclaimAbovePct <= reservePct ||
|
|
104
|
+
policy.reclaimAbovePct > 100
|
|
105
|
+
) {
|
|
106
|
+
throw new AIError.ConfigurationError(
|
|
107
|
+
`${path}.reclaimAbovePct must be a finite number above the account's reserve (${reservePct}%) and at most 100`,
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
98
111
|
}
|
|
99
112
|
}
|
|
100
113
|
|
|
101
114
|
validateUsageCapability(provider: string, canFetchUsage: boolean): void {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
throw new AIError.ConfigurationError(
|
|
107
|
-
`auth.accountPolicies[${policyIndex}].reservePct requires a usage provider for ${provider}`,
|
|
115
|
+
if (canFetchUsage) return;
|
|
116
|
+
for (const field of ["reservePct", "reclaimAbovePct"] as const) {
|
|
117
|
+
const policyIndex = this.#accountPolicies.findIndex(
|
|
118
|
+
policy => policy.provider === provider && policy[field] !== undefined,
|
|
108
119
|
);
|
|
120
|
+
if (policyIndex !== -1) {
|
|
121
|
+
throw new AIError.ConfigurationError(
|
|
122
|
+
`auth.accountPolicies[${policyIndex}].${field} requires a usage provider for ${provider}`,
|
|
123
|
+
);
|
|
124
|
+
}
|
|
109
125
|
}
|
|
110
126
|
}
|
|
111
127
|
|
package/src/auth/rank.ts
CHANGED
|
@@ -25,6 +25,10 @@ export type UsageCandidate<T extends AuthCredential> = {
|
|
|
25
25
|
* spent, so if it still serves it draws on paid overage such as Codex credits.
|
|
26
26
|
*/
|
|
27
27
|
allowanceSpent?: boolean;
|
|
28
|
+
/** Present after policy-aware ranking: the account's configured `priority` (0 when unset). */
|
|
29
|
+
accountPriority?: number;
|
|
30
|
+
/** Present after policy-aware ranking: unblocked and measured at or above its `reclaimAbovePct`. */
|
|
31
|
+
reclaimReady?: boolean;
|
|
28
32
|
};
|
|
29
33
|
|
|
30
34
|
/** OAuth credential eligible for usage ranking. */
|
|
@@ -152,5 +156,7 @@ export function orderUsageRankedCandidates<T extends AuthCredential>(
|
|
|
152
156
|
inReserve: candidate.inReserve,
|
|
153
157
|
reserveMeasured: candidate.reserveMeasured,
|
|
154
158
|
allowanceSpent: candidate.allowanceSpent,
|
|
159
|
+
accountPriority: candidate.accountPriority,
|
|
160
|
+
reclaimReady: candidate.reclaimReady,
|
|
155
161
|
}));
|
|
156
162
|
}
|
package/src/auth/select.ts
CHANGED
|
@@ -489,6 +489,11 @@ export class CredentialSelector {
|
|
|
489
489
|
reserveMeasured: reserveFraction !== undefined && remainingFraction !== undefined,
|
|
490
490
|
accountPriority: policy?.priority === undefined || !Number.isFinite(policy.priority) ? 0 : policy.priority,
|
|
491
491
|
allowanceSpent: remainingFraction === 0,
|
|
492
|
+
reclaimReady:
|
|
493
|
+
!blocked &&
|
|
494
|
+
policy?.reclaimAbovePct !== undefined &&
|
|
495
|
+
remainingFraction !== undefined &&
|
|
496
|
+
remainingFraction * 100 >= policy.reclaimAbovePct,
|
|
492
497
|
usageMeasured,
|
|
493
498
|
hasPriorityBoost: strategy?.hasPriorityBoost?.(primary, primaryUncapped, args.rankingContext) ?? false,
|
|
494
499
|
planPriority: planPriority(args.planGate, usage),
|
|
@@ -689,13 +694,26 @@ export class CredentialSelector {
|
|
|
689
694
|
candidate.selection.index === sessionPreferredIndex,
|
|
690
695
|
);
|
|
691
696
|
const preferredCandidate = sessionPreferredCandidate === -1 ? undefined : candidates[sessionPreferredCandidate];
|
|
692
|
-
// A warm automatic pin normally wins.
|
|
697
|
+
// A warm automatic pin normally wins. Three policies may evict it, each only
|
|
693
698
|
// while a sibling is confirmed better: reserve (sibling measured outside
|
|
694
|
-
// reserve)
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
+
// reserve), spent allowance (unblocked sibling with allowance left), and
|
|
700
|
+
// opt-in reclaim (the sibling ranking picks next has higher priority and is
|
|
701
|
+
// measured at or above its `reclaimAbovePct`).
|
|
702
|
+
const automaticPinWouldBeEvicted = (excludePreflightFailures: boolean): boolean => {
|
|
703
|
+
if (sessionPinIsExplicit || preferredCandidate === undefined) return false;
|
|
704
|
+
const rankedWinner = candidates.find(
|
|
705
|
+
candidate =>
|
|
706
|
+
candidate !== preferredCandidate &&
|
|
707
|
+
!(excludePreflightFailures && preflightFailures.has(candidate)) &&
|
|
708
|
+
!this.#deps.blocks.isBlocked(provider, providerKey, candidate.selection.index, blockScopes),
|
|
709
|
+
);
|
|
710
|
+
if (
|
|
711
|
+
rankedWinner?.reclaimReady === true &&
|
|
712
|
+
(rankedWinner.accountPriority ?? 0) > (preferredCandidate.accountPriority ?? 0)
|
|
713
|
+
) {
|
|
714
|
+
return true;
|
|
715
|
+
}
|
|
716
|
+
return candidates.some(candidate => {
|
|
699
717
|
if (candidate === preferredCandidate) return false;
|
|
700
718
|
if (excludePreflightFailures && preflightFailures.has(candidate)) return false;
|
|
701
719
|
if (
|
|
@@ -712,6 +730,7 @@ export class CredentialSelector {
|
|
|
712
730
|
!this.#deps.blocks.isBlocked(provider, providerKey, candidate.selection.index, blockScopes)
|
|
713
731
|
);
|
|
714
732
|
});
|
|
733
|
+
};
|
|
715
734
|
const pinEvictedBeforePreflight = automaticPinWouldBeEvicted(false);
|
|
716
735
|
if (
|
|
717
736
|
!hasPlanRequirement &&
|
package/src/auth/types.ts
CHANGED
|
@@ -62,6 +62,8 @@ export interface AuthAccountPolicy {
|
|
|
62
62
|
readonly priority?: number;
|
|
63
63
|
/** Protected remaining quota percentage for this account. */
|
|
64
64
|
readonly reservePct?: number;
|
|
65
|
+
/** Remaining quota percentage, above the reserve, at which this account takes back lower-priority warm pins. */
|
|
66
|
+
readonly reclaimAbovePct?: number;
|
|
65
67
|
}
|
|
66
68
|
|
|
67
69
|
/** Read-only set of per-account routing policies. */
|
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
} from "../auth-storage";
|
|
28
28
|
import * as AIError from "../error";
|
|
29
29
|
import { AuthBrokerClient, AuthBrokerError } from "./client";
|
|
30
|
-
import { type AuthBrokerAccountPool, RemoteAuthCredentialStore } from "./remote-store";
|
|
30
|
+
import { type AuthBrokerAccountPool, type AuthBrokerProviderPool, RemoteAuthCredentialStore } from "./remote-store";
|
|
31
31
|
import { readAuthBrokerSnapshotCache, scheduleAuthBrokerSnapshotCacheWrite } from "./snapshot-cache";
|
|
32
32
|
import { DEFAULT_SNAPSHOT_CACHE_TTL_MS, type SnapshotResponse } from "./types";
|
|
33
33
|
|
|
@@ -122,7 +122,12 @@ function parseAuthAccountPolicies(value: unknown): AuthAccountPolicies {
|
|
|
122
122
|
}
|
|
123
123
|
const policy = entry as Record<string, unknown>;
|
|
124
124
|
const unknownPolicyFields = Object.keys(policy).filter(
|
|
125
|
-
key =>
|
|
125
|
+
key =>
|
|
126
|
+
key !== "provider" &&
|
|
127
|
+
key !== "account" &&
|
|
128
|
+
key !== "priority" &&
|
|
129
|
+
key !== "reservePct" &&
|
|
130
|
+
key !== "reclaimAbovePct",
|
|
126
131
|
);
|
|
127
132
|
if (unknownPolicyFields.length > 0) {
|
|
128
133
|
throw new AIError.ConfigurationError(`${path} has unknown fields: ${unknownPolicyFields.join(", ")}`);
|
|
@@ -171,6 +176,12 @@ function parseAuthAccountPolicies(value: unknown): AuthAccountPolicies {
|
|
|
171
176
|
) {
|
|
172
177
|
throw new AIError.ConfigurationError(`${path}.reservePct must be between 0 and 100`);
|
|
173
178
|
}
|
|
179
|
+
if (
|
|
180
|
+
policy.reclaimAbovePct !== undefined &&
|
|
181
|
+
(typeof policy.reclaimAbovePct !== "number" || !Number.isFinite(policy.reclaimAbovePct))
|
|
182
|
+
) {
|
|
183
|
+
throw new AIError.ConfigurationError(`${path}.reclaimAbovePct must be a finite number`);
|
|
184
|
+
}
|
|
174
185
|
|
|
175
186
|
return {
|
|
176
187
|
provider,
|
|
@@ -182,6 +193,7 @@ function parseAuthAccountPolicies(value: unknown): AuthAccountPolicies {
|
|
|
182
193
|
},
|
|
183
194
|
...(typeof policy.priority === "number" ? { priority: policy.priority } : {}),
|
|
184
195
|
...(typeof policy.reservePct === "number" ? { reservePct: policy.reservePct } : {}),
|
|
196
|
+
...(typeof policy.reclaimAbovePct === "number" ? { reclaimAbovePct: policy.reclaimAbovePct } : {}),
|
|
185
197
|
};
|
|
186
198
|
});
|
|
187
199
|
}
|
|
@@ -272,7 +284,7 @@ export async function loadAuthBrokerAccountPool(): Promise<AuthBrokerAccountPool
|
|
|
272
284
|
throw new AIError.ConfigurationError("OMP_AUTH_BROKER_ACCOUNT_POOL_FILE must contain a JSON object");
|
|
273
285
|
}
|
|
274
286
|
|
|
275
|
-
const accountPool = new Map<string, ReadonlySet<string
|
|
287
|
+
const accountPool = new Map<string, ReadonlySet<string> | AuthBrokerProviderPool>();
|
|
276
288
|
for (const [provider, value] of Object.entries(parsed)) {
|
|
277
289
|
const normalizedProvider = provider.trim();
|
|
278
290
|
if (normalizedProvider.length === 0) {
|
|
@@ -283,28 +295,66 @@ export async function loadAuthBrokerAccountPool(): Promise<AuthBrokerAccountPool
|
|
|
283
295
|
"OMP_AUTH_BROKER_ACCOUNT_POOL_FILE contains a provider id with surrounding whitespace",
|
|
284
296
|
);
|
|
285
297
|
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
298
|
+
accountPool.set(provider, parseAccountPoolEntry(provider, value));
|
|
299
|
+
}
|
|
300
|
+
return accountPool;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* One pool-file entry: an identity array (visible OAuth accounts, API keys
|
|
305
|
+
* kept), `true` (everything visible), `false` (nothing visible), or a rule
|
|
306
|
+
* object `{ "accounts"?: [...], "apiKeys"?: boolean }`.
|
|
307
|
+
*/
|
|
308
|
+
function parseAccountPoolEntry(provider: string, value: unknown): ReadonlySet<string> | AuthBrokerProviderPool {
|
|
309
|
+
if (Array.isArray(value)) return parseAccountPoolIdentities(provider, value);
|
|
310
|
+
if (value === true) return {};
|
|
311
|
+
if (value === false) return { accounts: new Set(), apiKeys: false };
|
|
312
|
+
if (value === null || typeof value !== "object") {
|
|
313
|
+
throw new AIError.ConfigurationError(
|
|
314
|
+
`OMP_AUTH_BROKER_ACCOUNT_POOL_FILE entry for ${provider} must be an array of identity keys, a boolean, or an object with "accounts"/"apiKeys"`,
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
const rule: { accounts?: ReadonlySet<string>; apiKeys?: boolean } = {};
|
|
318
|
+
for (const [key, field] of Object.entries(value)) {
|
|
319
|
+
if (key === "accounts") {
|
|
320
|
+
if (!Array.isArray(field)) {
|
|
294
321
|
throw new AIError.ConfigurationError(
|
|
295
|
-
`OMP_AUTH_BROKER_ACCOUNT_POOL_FILE entry for ${provider}
|
|
322
|
+
`OMP_AUTH_BROKER_ACCOUNT_POOL_FILE entry for ${provider} has "accounts" that is not an array of identity keys`,
|
|
296
323
|
);
|
|
297
324
|
}
|
|
298
|
-
|
|
325
|
+
rule.accounts = parseAccountPoolIdentities(provider, field);
|
|
326
|
+
} else if (key === "apiKeys") {
|
|
327
|
+
if (typeof field !== "boolean") {
|
|
299
328
|
throw new AIError.ConfigurationError(
|
|
300
|
-
`OMP_AUTH_BROKER_ACCOUNT_POOL_FILE entry for ${provider}
|
|
329
|
+
`OMP_AUTH_BROKER_ACCOUNT_POOL_FILE entry for ${provider} has "apiKeys" that is not a boolean`,
|
|
301
330
|
);
|
|
302
331
|
}
|
|
303
|
-
|
|
332
|
+
rule.apiKeys = field;
|
|
333
|
+
} else {
|
|
334
|
+
throw new AIError.ConfigurationError(
|
|
335
|
+
`OMP_AUTH_BROKER_ACCOUNT_POOL_FILE entry for ${provider} has unknown field ${JSON.stringify(key)}`,
|
|
336
|
+
);
|
|
304
337
|
}
|
|
305
|
-
accountPool.set(provider, identities);
|
|
306
338
|
}
|
|
307
|
-
return
|
|
339
|
+
return rule;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function parseAccountPoolIdentities(provider: string, value: unknown[]): Set<string> {
|
|
343
|
+
const identities = new Set<string>();
|
|
344
|
+
for (const identity of value) {
|
|
345
|
+
if (typeof identity !== "string" || identity.length === 0) {
|
|
346
|
+
throw new AIError.ConfigurationError(
|
|
347
|
+
`OMP_AUTH_BROKER_ACCOUNT_POOL_FILE entry for ${provider} contains an invalid identity key`,
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
if (identity !== identity.trim()) {
|
|
351
|
+
throw new AIError.ConfigurationError(
|
|
352
|
+
`OMP_AUTH_BROKER_ACCOUNT_POOL_FILE entry for ${provider} contains an identity key with surrounding whitespace`,
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
identities.add(identity);
|
|
356
|
+
}
|
|
357
|
+
return identities;
|
|
308
358
|
}
|
|
309
359
|
|
|
310
360
|
function resolveSnapshotTtlMs(): number {
|