@oh-my-pi/pi-ai 17.1.1 → 17.1.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 +16 -0
- package/dist/types/auth-broker/client.d.ts +12 -1
- package/dist/types/auth-broker/remote-store.d.ts +10 -1
- package/dist/types/auth-broker/types.d.ts +21 -1
- package/dist/types/auth-broker/wire-schemas.d.ts +55 -0
- package/dist/types/auth-storage.d.ts +35 -1
- package/dist/types/providers/anthropic-wire.d.ts +29 -2
- package/dist/types/providers/anthropic.d.ts +1 -1
- package/dist/types/types.d.ts +20 -1
- package/dist/types/usage.d.ts +50 -0
- package/package.json +4 -4
- package/src/auth-broker/client.ts +39 -0
- package/src/auth-broker/remote-store.ts +76 -3
- package/src/auth-broker/server.ts +40 -0
- package/src/auth-broker/types.ts +25 -1
- package/src/auth-broker/wire-schemas.ts +71 -0
- package/src/auth-storage.ts +198 -0
- package/src/providers/anthropic-messages-server.ts +47 -3
- package/src/providers/anthropic-wire.ts +41 -0
- package/src/providers/anthropic.ts +81 -33
- package/src/providers/transform-messages.ts +8 -0
- package/src/types.ts +23 -0
- package/src/usage/zai.ts +3 -3
- package/src/usage.ts +55 -0
- package/src/utils/leaked-thinking-stream.ts +38 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [17.1.2] - 2026-07-24
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Added `GET /v1/usage/history` to the auth broker (recorded usage-limit snapshots with `sinceMs`/`provider` filters) and `AuthBrokerClient.fetchUsageHistory` — in broker deployments the broker host performs every upstream usage fetch, so its durable history is the only complete utilization record
|
|
10
|
+
- Added per-client burn tracking to the auth broker: clients batch observed request usage per (provider, model) and flush it to `POST /v1/usage/observed` every 10 seconds (install id as client key, hostname as display name); the broker persists 5-minute buckets in `client_usage`/`clients` and serves aggregates from `GET /v1/usage/clients`. Brokers without the endpoint disable reporting for the process lifetime
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- Renamed the Z.AI feature quota row to `ZAI Zread Quota` (tier/id slug `zread`), replacing the 74-char `ZAI Web Search / Reader / Zread Quota (web-search-reader-zread)` title that wrapped `omp usage` rows
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- Fixed every Claude (`anthropic-messages`) model on the `opencode-zen` provider failing with `401 Missing API key`: the gateway requires `x-api-key`, so `opencode-zen` now uses X-Api-Key auth like `opencode-go`/`umans` instead of bearer-only, and no longer sends the `context_management` field its Anthropic proxy rejects on thinking requests ([#6510](https://github.com/can1357/oh-my-pi/issues/6510)).
|
|
19
|
+
- Fixed Anthropic native server-tool blocks being dropped from persisted assistant turns, preserving signed web-search continuations in their original response order ([#6495](https://github.com/can1357/oh-my-pi/issues/6495))
|
|
20
|
+
|
|
5
21
|
## [17.1.1] - 2026-07-24
|
|
6
22
|
|
|
7
23
|
### Added
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AuthCredential } from "../auth-storage.js";
|
|
2
|
-
import type { CredentialBlockRequest, CredentialBlockResponse, CredentialBlocksDeleteResponse, CredentialDisableResponse, CredentialRefreshResponse, CredentialUploadResponse, HealthzResponse, SnapshotResponse, SnapshotStreamEvent, UsageResponse, UsageStaleResponse } from "./types.js";
|
|
2
|
+
import type { ClientUsageReportRequest, ClientUsageReportResponse, ClientUsageSummaryResponse, CredentialBlockRequest, CredentialBlockResponse, CredentialBlocksDeleteResponse, CredentialDisableResponse, CredentialRefreshResponse, CredentialUploadResponse, HealthzResponse, SnapshotResponse, SnapshotStreamEvent, UsageHistoryResponse, UsageResponse, UsageStaleResponse } from "./types.js";
|
|
3
3
|
export interface AuthBrokerClientOptions {
|
|
4
4
|
/** Base URL (e.g. `https://broker.tailnet:8765`). Trailing slashes are trimmed. */
|
|
5
5
|
url: string;
|
|
@@ -60,6 +60,17 @@ export declare class AuthBrokerClient {
|
|
|
60
60
|
signal?: AbortSignal;
|
|
61
61
|
}): AsyncGenerator<SnapshotStreamEvent>;
|
|
62
62
|
fetchUsage(signal?: AbortSignal): Promise<UsageResponse>;
|
|
63
|
+
/** Recorded usage-limit snapshots from the broker host, oldest first. */
|
|
64
|
+
fetchUsageHistory(query?: {
|
|
65
|
+
sinceMs?: number;
|
|
66
|
+
provider?: string;
|
|
67
|
+
}, signal?: AbortSignal): Promise<UsageHistoryResponse>;
|
|
68
|
+
/** Report this client's batched observed request usage for per-install burn tracking. */
|
|
69
|
+
reportClientUsage(report: ClientUsageReportRequest, signal?: AbortSignal): Promise<ClientUsageReportResponse>;
|
|
70
|
+
/** Per-client token burn aggregates recorded by the broker host. */
|
|
71
|
+
fetchClientUsageSummary(query?: {
|
|
72
|
+
sinceMs?: number;
|
|
73
|
+
}, signal?: AbortSignal): Promise<ClientUsageSummaryResponse>;
|
|
63
74
|
notifyUsageStale(signal?: AbortSignal): Promise<UsageStaleResponse>;
|
|
64
75
|
refreshCredential(id: number, signal?: AbortSignal): Promise<CredentialRefreshResponse>;
|
|
65
76
|
disableCredential(id: number, cause: string, signal?: AbortSignal): Promise<CredentialDisableResponse>;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type AuthCredential, type AuthCredentialStore, type OAuthCredential, type StoredAuthCredential, type StoredCredentialBlock } from "../auth-storage.js";
|
|
2
2
|
import type { OAuthCredentials } from "../registry/oauth/types.js";
|
|
3
3
|
import type { Provider } from "../types.js";
|
|
4
|
-
import type { UsageReport } from "../usage.js";
|
|
4
|
+
import type { ObservedUsageEntry, UsageReport } from "../usage.js";
|
|
5
5
|
import { type AuthBrokerClient } from "./client.js";
|
|
6
6
|
import type { SnapshotResponse } from "./types.js";
|
|
7
7
|
/**
|
|
@@ -33,6 +33,8 @@ export interface RemoteAuthCredentialStoreOptions {
|
|
|
33
33
|
* routing policy, not broker authorization.
|
|
34
34
|
*/
|
|
35
35
|
accountPool?: AuthBrokerAccountPool;
|
|
36
|
+
/** Flush cadence for batched observed-usage reports. Default 10s. */
|
|
37
|
+
observedUsageFlushMs?: number;
|
|
36
38
|
}
|
|
37
39
|
export declare class RemoteAuthCredentialStore implements AuthCredentialStore {
|
|
38
40
|
#private;
|
|
@@ -120,5 +122,12 @@ export declare class RemoteAuthCredentialStore implements AuthCredentialStore {
|
|
|
120
122
|
*/
|
|
121
123
|
getUsageReport(provider: Provider, credential: OAuthCredential, signal?: AbortSignal): Promise<UsageReport | null>;
|
|
122
124
|
ingestUsageReport(provider: Provider, credential: OAuthCredential, report: UsageReport): boolean;
|
|
125
|
+
/**
|
|
126
|
+
* Fold locally observed request usage into the pending report and schedule
|
|
127
|
+
* a flush. One `POST /v1/usage/observed` at most per flush interval; on
|
|
128
|
+
* failure the batch is retained and retried with the next flush. A 404
|
|
129
|
+
* (pre-endpoint broker) disables reporting for the life of this store.
|
|
130
|
+
*/
|
|
131
|
+
recordObservedUsage(entries: ObservedUsageEntry[]): void;
|
|
123
132
|
close(): void;
|
|
124
133
|
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* credential expires or a 401 surfaces on a supposedly-fresh credential.
|
|
7
7
|
*/
|
|
8
8
|
import type { AuthCredential, AuthCredentialSnapshot, AuthCredentialSnapshotEntry, StoredCredentialBlock } from "../auth-storage.js";
|
|
9
|
-
import type { UsageReport } from "../usage.js";
|
|
9
|
+
import type { ClientUsageClientSummary, ClientUsageReport, UsageHistoryEntry, UsageReport } from "../usage.js";
|
|
10
10
|
/** GET /v1/healthz response body. */
|
|
11
11
|
export interface HealthzResponse {
|
|
12
12
|
ok: boolean;
|
|
@@ -34,6 +34,26 @@ export interface UsageResponse {
|
|
|
34
34
|
generatedAt: number;
|
|
35
35
|
reports: UsageReport[];
|
|
36
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* GET /v1/usage/history response body. Entries come from the broker host's
|
|
39
|
+
* durable `usage_history` — the broker performs every upstream usage fetch in
|
|
40
|
+
* broker deployments, so this is the only complete utilization record.
|
|
41
|
+
*/
|
|
42
|
+
export interface UsageHistoryResponse {
|
|
43
|
+
generatedAt: number;
|
|
44
|
+
entries: UsageHistoryEntry[];
|
|
45
|
+
}
|
|
46
|
+
/** POST /v1/usage/observed request body — one client's batched observed usage. */
|
|
47
|
+
export type ClientUsageReportRequest = ClientUsageReport;
|
|
48
|
+
/** POST /v1/usage/observed response body. */
|
|
49
|
+
export interface ClientUsageReportResponse {
|
|
50
|
+
ok: boolean;
|
|
51
|
+
}
|
|
52
|
+
/** GET /v1/usage/clients response body — per-client token burn aggregates. */
|
|
53
|
+
export interface ClientUsageSummaryResponse {
|
|
54
|
+
generatedAt: number;
|
|
55
|
+
clients: ClientUsageClientSummary[];
|
|
56
|
+
}
|
|
37
57
|
/** POST /v1/credential/:id/refresh response body. */
|
|
38
58
|
export interface CredentialRefreshResponse {
|
|
39
59
|
entry: AuthCredentialSnapshotEntry;
|
|
@@ -418,6 +418,61 @@ export declare const usageResponseSchema: import("arktype/internal/variants/obje
|
|
|
418
418
|
raw?: unknown;
|
|
419
419
|
}[];
|
|
420
420
|
}, {}>;
|
|
421
|
+
/** Broker `/v1/usage/history` response — recorded usage-limit snapshots, oldest first. */
|
|
422
|
+
export declare const usageHistoryResponseSchema: import("arktype/internal/variants/object.ts").ObjectType<{
|
|
423
|
+
generatedAt: number;
|
|
424
|
+
entries: {
|
|
425
|
+
recordedAt: number;
|
|
426
|
+
provider: string;
|
|
427
|
+
accountKey: string;
|
|
428
|
+
email?: string | undefined;
|
|
429
|
+
accountId?: string | undefined;
|
|
430
|
+
limitId: string;
|
|
431
|
+
label: string;
|
|
432
|
+
windowLabel?: string | undefined;
|
|
433
|
+
usedFraction?: number | undefined;
|
|
434
|
+
status?: "exhausted" | "ok" | "unknown" | "warning" | undefined;
|
|
435
|
+
resetsAt?: number | undefined;
|
|
436
|
+
}[];
|
|
437
|
+
}, {}>;
|
|
438
|
+
/** Broker `POST /v1/usage/observed` request — one client's batched observed usage. */
|
|
439
|
+
export declare const clientUsageReportRequestSchema: import("arktype/internal/variants/object.ts").ObjectType<{
|
|
440
|
+
installId: string;
|
|
441
|
+
hostname?: string | undefined;
|
|
442
|
+
entries: {
|
|
443
|
+
at: number;
|
|
444
|
+
provider: string;
|
|
445
|
+
model: string;
|
|
446
|
+
requests: number;
|
|
447
|
+
inputTokens: number;
|
|
448
|
+
outputTokens: number;
|
|
449
|
+
cacheReadTokens: number;
|
|
450
|
+
cacheWriteTokens: number;
|
|
451
|
+
costUsd: number;
|
|
452
|
+
}[];
|
|
453
|
+
}, {}>;
|
|
454
|
+
export declare const clientUsageReportResponseSchema: import("arktype/internal/variants/object.ts").ObjectType<{
|
|
455
|
+
ok: boolean;
|
|
456
|
+
}, {}>;
|
|
457
|
+
/** Broker `GET /v1/usage/clients` response — per-client token burn aggregates. */
|
|
458
|
+
export declare const clientUsageSummaryResponseSchema: import("arktype/internal/variants/object.ts").ObjectType<{
|
|
459
|
+
generatedAt: number;
|
|
460
|
+
clients: {
|
|
461
|
+
installId: string;
|
|
462
|
+
hostname?: string | undefined;
|
|
463
|
+
firstSeen: number;
|
|
464
|
+
lastSeen: number;
|
|
465
|
+
providers: {
|
|
466
|
+
provider: string;
|
|
467
|
+
requests: number;
|
|
468
|
+
inputTokens: number;
|
|
469
|
+
outputTokens: number;
|
|
470
|
+
cacheReadTokens: number;
|
|
471
|
+
cacheWriteTokens: number;
|
|
472
|
+
costUsd: number;
|
|
473
|
+
}[];
|
|
474
|
+
}[];
|
|
475
|
+
}, {}>;
|
|
421
476
|
export declare const credentialRefreshResponseSchema: import("arktype/internal/variants/object.ts").ObjectType<{
|
|
422
477
|
entry: {
|
|
423
478
|
id: number;
|
|
@@ -11,7 +11,7 @@ import { Database } from "bun:sqlite";
|
|
|
11
11
|
import type { ApiKeyResolver } from "./auth-retry.js";
|
|
12
12
|
import type { OAuthAuthInfo, OAuthController, OAuthCredentials, OAuthProviderId } from "./registry/oauth/types.js";
|
|
13
13
|
import type { Provider } from "./types.js";
|
|
14
|
-
import type { CredentialRankingStrategy, UsageCostHistoryEntry, UsageCostHistoryQuery, UsageHistoryEntry, UsageHistoryQuery, UsageLogger, UsageProvider, UsageReport } from "./usage.js";
|
|
14
|
+
import type { ClientUsageReport, ClientUsageSummary, CredentialRankingStrategy, ObservedUsageEntry, UsageCostHistoryEntry, UsageCostHistoryQuery, UsageHistoryEntry, UsageHistoryQuery, UsageLogger, UsageProvider, UsageReport } from "./usage.js";
|
|
15
15
|
import { type CodexResetConsumeCode, type CodexResetCredit } from "./usage/openai-codex-reset.js";
|
|
16
16
|
export type ApiKeyCredential = {
|
|
17
17
|
type: "api_key";
|
|
@@ -281,6 +281,16 @@ export interface AuthCredentialStore {
|
|
|
281
281
|
listUsageCosts?(query?: UsageCostHistoryQuery): UsageCostHistoryEntry[];
|
|
282
282
|
/** Read recorded usage-limit snapshots, oldest first. */
|
|
283
283
|
listUsageHistory?(query?: UsageHistoryQuery): UsageHistoryEntry[];
|
|
284
|
+
/**
|
|
285
|
+
* Client hook: forward locally observed request usage. Remote broker stores
|
|
286
|
+
* batch these to the broker so it can attribute token burn per install;
|
|
287
|
+
* local stores omit it and observation is skipped.
|
|
288
|
+
*/
|
|
289
|
+
recordObservedUsage?(entries: ObservedUsageEntry[]): void;
|
|
290
|
+
/** Broker host: persist one client's observed-usage report. */
|
|
291
|
+
recordClientUsage?(report: ClientUsageReport): void;
|
|
292
|
+
/** Broker host: aggregate recorded per-client usage since a timestamp. */
|
|
293
|
+
getClientUsageSummary?(sinceMs: number): ClientUsageSummary;
|
|
284
294
|
/**
|
|
285
295
|
* Optional store-supplied OAuth refresh. When present, `AuthStorage` uses
|
|
286
296
|
* it before the per-provider local refresh path. `RemoteAuthCredentialStore`
|
|
@@ -836,6 +846,28 @@ export declare class AuthStorage {
|
|
|
836
846
|
recordedAt?: number;
|
|
837
847
|
baseUrl?: string;
|
|
838
848
|
}): boolean;
|
|
849
|
+
/**
|
|
850
|
+
* Forward one completed request's usage to the store's observer hook.
|
|
851
|
+
* Broker-backed stores batch these into per-install reports so the broker
|
|
852
|
+
* can track actual token burn per client; local stores have no hook and
|
|
853
|
+
* the call is a no-op.
|
|
854
|
+
*/
|
|
855
|
+
recordObservedUsage(entry: {
|
|
856
|
+
provider: Provider;
|
|
857
|
+
model: string;
|
|
858
|
+
usage: {
|
|
859
|
+
input: number;
|
|
860
|
+
output: number;
|
|
861
|
+
cacheRead: number;
|
|
862
|
+
cacheWrite: number;
|
|
863
|
+
};
|
|
864
|
+
costUsd?: number;
|
|
865
|
+
at?: number;
|
|
866
|
+
}): void;
|
|
867
|
+
/** Broker host: persist one client's observed-usage report (per-install token burn). */
|
|
868
|
+
recordClientUsage(report: ClientUsageReport): boolean;
|
|
869
|
+
/** Broker host: aggregate recorded per-client usage since `sinceMs`. */
|
|
870
|
+
getClientUsageSummary(sinceMs: number): ClientUsageSummary;
|
|
839
871
|
ingestUsageHeaders(provider: Provider, headers: Record<string, string>, options?: {
|
|
840
872
|
sessionId?: string;
|
|
841
873
|
baseUrl?: string;
|
|
@@ -1200,6 +1232,8 @@ export declare class SqliteAuthCredentialStore implements AuthCredentialStore {
|
|
|
1200
1232
|
listUsageHistory(query?: UsageHistoryQuery): UsageHistoryEntry[];
|
|
1201
1233
|
recordUsageCosts(entries: UsageCostHistoryEntry[]): void;
|
|
1202
1234
|
listUsageCosts(query?: UsageCostHistoryQuery): UsageCostHistoryEntry[];
|
|
1235
|
+
recordClientUsage(report: ClientUsageReport): void;
|
|
1236
|
+
getClientUsageSummary(sinceMs: number): ClientUsageSummary;
|
|
1203
1237
|
/**
|
|
1204
1238
|
* Save OAuth credentials for a provider.
|
|
1205
1239
|
* Preserves unrelated identities and replaces only the matching credential.
|
|
@@ -57,6 +57,33 @@ export type ToolResultBlockParam = {
|
|
|
57
57
|
is_error?: boolean;
|
|
58
58
|
cache_control?: CacheControlEphemeral | null;
|
|
59
59
|
};
|
|
60
|
+
/** Anthropic-executed server tool call replayed inside an assistant turn. */
|
|
61
|
+
export type ServerToolUseBlockParam = {
|
|
62
|
+
type: "server_tool_use";
|
|
63
|
+
id: string;
|
|
64
|
+
name: string;
|
|
65
|
+
input?: Record<string, unknown> | null;
|
|
66
|
+
[key: string]: unknown;
|
|
67
|
+
};
|
|
68
|
+
/** Web-search server-tool call whose matching result is replayable by omp. */
|
|
69
|
+
export type WebSearchServerToolUseBlockParam = ServerToolUseBlockParam & {
|
|
70
|
+
name: "web_search";
|
|
71
|
+
};
|
|
72
|
+
/** Native web-search result replayed inside an assistant turn. */
|
|
73
|
+
export type WebSearchToolResultBlockParam = {
|
|
74
|
+
type: "web_search_tool_result";
|
|
75
|
+
tool_use_id: string;
|
|
76
|
+
content: unknown;
|
|
77
|
+
[key: string]: unknown;
|
|
78
|
+
};
|
|
79
|
+
/** True for the complete native web-search history variants omp can replay. */
|
|
80
|
+
export declare function isAnthropicWebSearchHistoryBlock(block: {
|
|
81
|
+
type: string;
|
|
82
|
+
name?: unknown;
|
|
83
|
+
id?: unknown;
|
|
84
|
+
tool_use_id?: unknown;
|
|
85
|
+
content?: unknown;
|
|
86
|
+
}): block is WebSearchServerToolUseBlockParam | WebSearchToolResultBlockParam;
|
|
60
87
|
export type ThinkingBlockParam = {
|
|
61
88
|
type: "thinking";
|
|
62
89
|
thinking: string;
|
|
@@ -81,7 +108,7 @@ export type FallbackBlockParam = {
|
|
|
81
108
|
model: string;
|
|
82
109
|
};
|
|
83
110
|
};
|
|
84
|
-
export type ContentBlockParam = TextBlockParam | ImageBlockParam | ToolUseBlockParam | ToolResultBlockParam | ThinkingBlockParam | RedactedThinkingBlockParam | FallbackBlockParam;
|
|
111
|
+
export type ContentBlockParam = TextBlockParam | ImageBlockParam | ToolUseBlockParam | ToolResultBlockParam | ServerToolUseBlockParam | WebSearchToolResultBlockParam | ThinkingBlockParam | RedactedThinkingBlockParam | FallbackBlockParam;
|
|
85
112
|
/**
|
|
86
113
|
* A single conversation turn.
|
|
87
114
|
*
|
|
@@ -257,7 +284,7 @@ export type ResponseContentBlock = {
|
|
|
257
284
|
id: string;
|
|
258
285
|
name: string;
|
|
259
286
|
input?: Record<string, unknown> | null;
|
|
260
|
-
} | {
|
|
287
|
+
} | ServerToolUseBlockParam | WebSearchToolResultBlockParam | {
|
|
261
288
|
type: "fallback";
|
|
262
289
|
from: {
|
|
263
290
|
model: string;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { FetchImpl, Message, Model, ProviderSessionState, ServiceTier, SimpleStreamOptions, StreamFunction, StreamOptions, Usage } from "../types.js";
|
|
2
2
|
import { type AnthropicFetchOptions, type AnthropicMessagesClientLike } from "./anthropic-client.js";
|
|
3
|
-
import type
|
|
3
|
+
import { type FallbackParam, type MessageParam, type TextBlockParam } from "./anthropic-wire.js";
|
|
4
4
|
export type AnthropicHeaderOptions = {
|
|
5
5
|
apiKey: string;
|
|
6
6
|
baseUrl?: string;
|
package/dist/types/types.d.ts
CHANGED
|
@@ -476,6 +476,25 @@ export interface AnthropicFallbackContent {
|
|
|
476
476
|
model: string;
|
|
477
477
|
};
|
|
478
478
|
}
|
|
479
|
+
/**
|
|
480
|
+
* Verbatim Anthropic web-search call/result retained for same-provider
|
|
481
|
+
* history replay. Other providers discard it in `transformMessages`.
|
|
482
|
+
*/
|
|
483
|
+
export interface AnthropicServerToolContent {
|
|
484
|
+
type: "anthropicServerTool";
|
|
485
|
+
block: {
|
|
486
|
+
type: "server_tool_use";
|
|
487
|
+
id: string;
|
|
488
|
+
name: "web_search";
|
|
489
|
+
input?: Record<string, unknown> | null;
|
|
490
|
+
[key: string]: unknown;
|
|
491
|
+
} | {
|
|
492
|
+
type: "web_search_tool_result";
|
|
493
|
+
tool_use_id: string;
|
|
494
|
+
content: unknown;
|
|
495
|
+
[key: string]: unknown;
|
|
496
|
+
};
|
|
497
|
+
}
|
|
479
498
|
export interface ImageContent {
|
|
480
499
|
type: "image";
|
|
481
500
|
data: string;
|
|
@@ -631,7 +650,7 @@ export interface ContextSnapshot {
|
|
|
631
650
|
}
|
|
632
651
|
export interface AssistantMessage {
|
|
633
652
|
role: "assistant";
|
|
634
|
-
content: (TextContent | ThinkingContent | RedactedThinkingContent | AnthropicFallbackContent | ImageContent | ToolCall)[];
|
|
653
|
+
content: (TextContent | ThinkingContent | RedactedThinkingContent | AnthropicFallbackContent | AnthropicServerToolContent | ImageContent | ToolCall)[];
|
|
635
654
|
api: Api;
|
|
636
655
|
provider: Provider;
|
|
637
656
|
model: string;
|
package/dist/types/usage.d.ts
CHANGED
|
@@ -156,6 +156,56 @@ export interface UsageCostHistoryQuery {
|
|
|
156
156
|
/** Inclusive lower bound on {@link UsageCostHistoryEntry.recordedAt} (epoch ms). */
|
|
157
157
|
sinceMs?: number;
|
|
158
158
|
}
|
|
159
|
+
/**
|
|
160
|
+
* Aggregated request usage a client observed for one (provider, model) pair.
|
|
161
|
+
* Clients fold every completed request into per-pair buckets and flush them to
|
|
162
|
+
* the auth broker on a short cadence, so the broker can attribute token burn
|
|
163
|
+
* to the install that produced it.
|
|
164
|
+
*/
|
|
165
|
+
export interface ObservedUsageEntry {
|
|
166
|
+
/** Epoch ms of the newest request folded into this bucket. */
|
|
167
|
+
at: number;
|
|
168
|
+
provider: Provider;
|
|
169
|
+
model: string;
|
|
170
|
+
/** Completed requests folded into this bucket. */
|
|
171
|
+
requests: number;
|
|
172
|
+
inputTokens: number;
|
|
173
|
+
outputTokens: number;
|
|
174
|
+
cacheReadTokens: number;
|
|
175
|
+
cacheWriteTokens: number;
|
|
176
|
+
/** Estimated USD cost of the folded requests (0 when unknown). */
|
|
177
|
+
costUsd: number;
|
|
178
|
+
}
|
|
179
|
+
/** One client's observed-usage report, keyed by its stable install id. */
|
|
180
|
+
export interface ClientUsageReport {
|
|
181
|
+
/** Stable per-machine install id — the client primary key. */
|
|
182
|
+
installId: string;
|
|
183
|
+
/** Human-readable machine name for display surfaces. */
|
|
184
|
+
hostname?: string;
|
|
185
|
+
entries: ObservedUsageEntry[];
|
|
186
|
+
}
|
|
187
|
+
/** Per-provider aggregate of one client's recorded usage. */
|
|
188
|
+
export interface ClientProviderUsage {
|
|
189
|
+
provider: string;
|
|
190
|
+
requests: number;
|
|
191
|
+
inputTokens: number;
|
|
192
|
+
outputTokens: number;
|
|
193
|
+
cacheReadTokens: number;
|
|
194
|
+
cacheWriteTokens: number;
|
|
195
|
+
costUsd: number;
|
|
196
|
+
}
|
|
197
|
+
/** One known client with its usage aggregates over the queried window. */
|
|
198
|
+
export interface ClientUsageClientSummary {
|
|
199
|
+
installId: string;
|
|
200
|
+
hostname?: string;
|
|
201
|
+
firstSeen: number;
|
|
202
|
+
lastSeen: number;
|
|
203
|
+
providers: ClientProviderUsage[];
|
|
204
|
+
}
|
|
205
|
+
/** Aggregated per-client usage recorded by the broker host. */
|
|
206
|
+
export interface ClientUsageSummary {
|
|
207
|
+
clients: ClientUsageClientSummary[];
|
|
208
|
+
}
|
|
159
209
|
export declare const usageUnitSchema: import("arktype/internal/variants/string.ts").StringType<"bytes" | "minutes" | "percent" | "requests" | "tokens" | "unknown" | "usd", {}>;
|
|
160
210
|
export declare const usageStatusSchema: import("arktype/internal/variants/string.ts").StringType<"exhausted" | "ok" | "unknown" | "warning", {}>;
|
|
161
211
|
export declare const usageWindowSchema: import("arktype/internal/variants/object.ts").ObjectType<{
|
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.1.
|
|
4
|
+
"version": "17.1.2",
|
|
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,9 +38,9 @@
|
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"@bufbuild/protobuf": "^2.12.1",
|
|
41
|
-
"@oh-my-pi/pi-catalog": "17.1.
|
|
42
|
-
"@oh-my-pi/pi-utils": "17.1.
|
|
43
|
-
"@oh-my-pi/pi-wire": "17.1.
|
|
41
|
+
"@oh-my-pi/pi-catalog": "17.1.2",
|
|
42
|
+
"@oh-my-pi/pi-utils": "17.1.2",
|
|
43
|
+
"@oh-my-pi/pi-wire": "17.1.2",
|
|
44
44
|
"arktype": "2.2.3",
|
|
45
45
|
"zod": "^4"
|
|
46
46
|
},
|
|
@@ -9,6 +9,9 @@ import { readSseEvents } from "@oh-my-pi/pi-utils";
|
|
|
9
9
|
import { type } from "arktype";
|
|
10
10
|
import type { AuthCredential } from "../auth-storage";
|
|
11
11
|
import type {
|
|
12
|
+
ClientUsageReportRequest,
|
|
13
|
+
ClientUsageReportResponse,
|
|
14
|
+
ClientUsageSummaryResponse,
|
|
12
15
|
CredentialBlockRequest,
|
|
13
16
|
CredentialBlockResponse,
|
|
14
17
|
CredentialBlocksDeleteResponse,
|
|
@@ -20,10 +23,13 @@ import type {
|
|
|
20
23
|
HealthzResponse,
|
|
21
24
|
SnapshotResponse,
|
|
22
25
|
SnapshotStreamEvent,
|
|
26
|
+
UsageHistoryResponse,
|
|
23
27
|
UsageResponse,
|
|
24
28
|
UsageStaleResponse,
|
|
25
29
|
} from "./types";
|
|
26
30
|
import {
|
|
31
|
+
clientUsageReportResponseSchema,
|
|
32
|
+
clientUsageSummaryResponseSchema,
|
|
27
33
|
credentialBlockResponseSchema,
|
|
28
34
|
credentialBlocksDeleteResponseSchema,
|
|
29
35
|
credentialDisableResponseSchema,
|
|
@@ -32,6 +38,7 @@ import {
|
|
|
32
38
|
healthzResponseSchema,
|
|
33
39
|
snapshotResponseSchema,
|
|
34
40
|
snapshotStreamEventSchema,
|
|
41
|
+
usageHistoryResponseSchema,
|
|
35
42
|
usageResponseSchema,
|
|
36
43
|
usageStaleResponseSchema,
|
|
37
44
|
} from "./wire-schemas";
|
|
@@ -244,6 +251,38 @@ export class AuthBrokerClient {
|
|
|
244
251
|
return this.#request<UsageResponse>("GET", "/v1/usage", { schema: usageResponseSchema, signal });
|
|
245
252
|
}
|
|
246
253
|
|
|
254
|
+
/** Recorded usage-limit snapshots from the broker host, oldest first. */
|
|
255
|
+
fetchUsageHistory(
|
|
256
|
+
query?: { sinceMs?: number; provider?: string },
|
|
257
|
+
signal?: AbortSignal,
|
|
258
|
+
): Promise<UsageHistoryResponse> {
|
|
259
|
+
const params = new URLSearchParams();
|
|
260
|
+
if (query?.sinceMs !== undefined) params.set("sinceMs", String(query.sinceMs));
|
|
261
|
+
if (query?.provider) params.set("provider", query.provider);
|
|
262
|
+
const path = `/v1/usage/history${params.size > 0 ? `?${params.toString()}` : ""}`;
|
|
263
|
+
return this.#request<UsageHistoryResponse>("GET", path, { schema: usageHistoryResponseSchema, signal });
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/** Report this client's batched observed request usage for per-install burn tracking. */
|
|
267
|
+
reportClientUsage(report: ClientUsageReportRequest, signal?: AbortSignal): Promise<ClientUsageReportResponse> {
|
|
268
|
+
return this.#request<ClientUsageReportResponse>("POST", "/v1/usage/observed", {
|
|
269
|
+
body: report,
|
|
270
|
+
schema: clientUsageReportResponseSchema,
|
|
271
|
+
signal,
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/** Per-client token burn aggregates recorded by the broker host. */
|
|
276
|
+
fetchClientUsageSummary(query?: { sinceMs?: number }, signal?: AbortSignal): Promise<ClientUsageSummaryResponse> {
|
|
277
|
+
const params = new URLSearchParams();
|
|
278
|
+
if (query?.sinceMs !== undefined) params.set("sinceMs", String(query.sinceMs));
|
|
279
|
+
const path = `/v1/usage/clients${params.size > 0 ? `?${params.toString()}` : ""}`;
|
|
280
|
+
return this.#request<ClientUsageSummaryResponse>("GET", path, {
|
|
281
|
+
schema: clientUsageSummaryResponseSchema,
|
|
282
|
+
signal,
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
|
|
247
286
|
notifyUsageStale(signal?: AbortSignal): Promise<UsageStaleResponse> {
|
|
248
287
|
return this.#request<UsageStaleResponse>("POST", "/v1/usage/stale", {
|
|
249
288
|
schema: usageStaleResponseSchema,
|
|
@@ -7,8 +7,9 @@
|
|
|
7
7
|
* usage reports cache TTL is 5 minutes per credential, so durability across
|
|
8
8
|
* runs isn't required.
|
|
9
9
|
*/
|
|
10
|
+
import * as os from "node:os";
|
|
10
11
|
import { scheduler } from "node:timers/promises";
|
|
11
|
-
import { logger } from "@oh-my-pi/pi-utils";
|
|
12
|
+
import { getInstallId, logger } from "@oh-my-pi/pi-utils";
|
|
12
13
|
import {
|
|
13
14
|
type AuthCredential,
|
|
14
15
|
type AuthCredentialSnapshotEntry,
|
|
@@ -21,8 +22,8 @@ import {
|
|
|
21
22
|
import * as AIError from "../error";
|
|
22
23
|
import type { OAuthCredentials } from "../registry/oauth/types";
|
|
23
24
|
import type { Provider } from "../types";
|
|
24
|
-
import type { UsageReport } from "../usage";
|
|
25
|
-
import { type AuthBrokerClient, AuthBrokerStreamUnsupportedError } from "./client";
|
|
25
|
+
import type { ObservedUsageEntry, UsageReport } from "../usage";
|
|
26
|
+
import { type AuthBrokerClient, AuthBrokerError, AuthBrokerStreamUnsupportedError } from "./client";
|
|
26
27
|
import type {
|
|
27
28
|
CredentialBlockSnapshot,
|
|
28
29
|
RefresherSchedule,
|
|
@@ -232,6 +233,8 @@ export interface RemoteAuthCredentialStoreOptions {
|
|
|
232
233
|
* routing policy, not broker authorization.
|
|
233
234
|
*/
|
|
234
235
|
accountPool?: AuthBrokerAccountPool;
|
|
236
|
+
/** Flush cadence for batched observed-usage reports. Default 10s. */
|
|
237
|
+
observedUsageFlushMs?: number;
|
|
235
238
|
}
|
|
236
239
|
|
|
237
240
|
export class RemoteAuthCredentialStore implements AuthCredentialStore {
|
|
@@ -259,10 +262,17 @@ export class RemoteAuthCredentialStore implements AuthCredentialStore {
|
|
|
259
262
|
#streamingActive = false;
|
|
260
263
|
/** Latched once the broker has answered 404 — never try the stream again. */
|
|
261
264
|
#streamingUnsupported = false;
|
|
265
|
+
/** Pending observed usage keyed by `provider\u0000model`, merged until flush. */
|
|
266
|
+
#observedUsage = new Map<string, ObservedUsageEntry>();
|
|
267
|
+
#observedUsageTimer: Timer | undefined;
|
|
268
|
+
readonly #observedUsageFlushMs: number;
|
|
269
|
+
/** Latched once the broker answered 404 — old broker, never report again. */
|
|
270
|
+
#observedUsageUnsupported = false;
|
|
262
271
|
|
|
263
272
|
constructor(opts: RemoteAuthCredentialStoreOptions) {
|
|
264
273
|
this.#client = opts.client;
|
|
265
274
|
this.#streamSnapshots = opts.streamSnapshots ?? true;
|
|
275
|
+
this.#observedUsageFlushMs = opts.observedUsageFlushMs ?? 10_000;
|
|
266
276
|
this.#accountPool = opts.accountPool
|
|
267
277
|
? new Map([...opts.accountPool].map(([provider, identities]) => [provider, new Set(identities)]))
|
|
268
278
|
: undefined;
|
|
@@ -1049,10 +1059,73 @@ export class RemoteAuthCredentialStore implements AuthCredentialStore {
|
|
|
1049
1059
|
return inflight;
|
|
1050
1060
|
}
|
|
1051
1061
|
|
|
1062
|
+
/**
|
|
1063
|
+
* Fold locally observed request usage into the pending report and schedule
|
|
1064
|
+
* a flush. One `POST /v1/usage/observed` at most per flush interval; on
|
|
1065
|
+
* failure the batch is retained and retried with the next flush. A 404
|
|
1066
|
+
* (pre-endpoint broker) disables reporting for the life of this store.
|
|
1067
|
+
*/
|
|
1068
|
+
recordObservedUsage(entries: ObservedUsageEntry[]): void {
|
|
1069
|
+
if (this.#closed || this.#observedUsageUnsupported) return;
|
|
1070
|
+
for (const entry of entries) {
|
|
1071
|
+
const key = `${entry.provider}\u0000${entry.model}`;
|
|
1072
|
+
const pending = this.#observedUsage.get(key);
|
|
1073
|
+
if (pending) {
|
|
1074
|
+
pending.at = Math.max(pending.at, entry.at);
|
|
1075
|
+
pending.requests += entry.requests;
|
|
1076
|
+
pending.inputTokens += entry.inputTokens;
|
|
1077
|
+
pending.outputTokens += entry.outputTokens;
|
|
1078
|
+
pending.cacheReadTokens += entry.cacheReadTokens;
|
|
1079
|
+
pending.cacheWriteTokens += entry.cacheWriteTokens;
|
|
1080
|
+
pending.costUsd += entry.costUsd;
|
|
1081
|
+
} else {
|
|
1082
|
+
this.#observedUsage.set(key, { ...entry });
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
if (this.#observedUsage.size > 0 && this.#observedUsageTimer === undefined) {
|
|
1086
|
+
this.#observedUsageTimer = setTimeout(() => {
|
|
1087
|
+
this.#observedUsageTimer = undefined;
|
|
1088
|
+
void this.#flushObservedUsage();
|
|
1089
|
+
}, this.#observedUsageFlushMs);
|
|
1090
|
+
this.#observedUsageTimer.unref?.();
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
async #flushObservedUsage(): Promise<void> {
|
|
1095
|
+
if (this.#observedUsage.size === 0 || this.#observedUsageUnsupported) return;
|
|
1096
|
+
const batch = [...this.#observedUsage.values()];
|
|
1097
|
+
this.#observedUsage.clear();
|
|
1098
|
+
try {
|
|
1099
|
+
await this.#client.reportClientUsage({
|
|
1100
|
+
installId: getInstallId(),
|
|
1101
|
+
hostname: os.hostname(),
|
|
1102
|
+
entries: batch,
|
|
1103
|
+
});
|
|
1104
|
+
} catch (error) {
|
|
1105
|
+
const status = error instanceof AuthBrokerError ? error.status : undefined;
|
|
1106
|
+
if (status === 404 || status === 501) {
|
|
1107
|
+
// Broker predates the endpoint (or store can't persist) — stop trying.
|
|
1108
|
+
this.#observedUsageUnsupported = true;
|
|
1109
|
+
logger.debug("auth-broker does not accept observed usage; reporting disabled", { status });
|
|
1110
|
+
return;
|
|
1111
|
+
}
|
|
1112
|
+
logger.debug("auth-broker observed usage flush failed; retrying next flush", { error: String(error) });
|
|
1113
|
+
// Merge the failed batch back under the (possibly refilled) buffer so
|
|
1114
|
+
// nothing is lost; bounded because entries are keyed per (provider, model).
|
|
1115
|
+
if (!this.#closed) this.recordObservedUsage(batch);
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1052
1119
|
close(): void {
|
|
1053
1120
|
if (this.#closed) return;
|
|
1054
1121
|
this.#closed = true;
|
|
1055
1122
|
this.#backgroundAbort.abort();
|
|
1123
|
+
if (this.#observedUsageTimer !== undefined) {
|
|
1124
|
+
clearTimeout(this.#observedUsageTimer);
|
|
1125
|
+
this.#observedUsageTimer = undefined;
|
|
1126
|
+
}
|
|
1127
|
+
// Best-effort final flush; failures are dropped (the process is exiting).
|
|
1128
|
+
if (this.#observedUsage.size > 0) void this.#flushObservedUsage();
|
|
1056
1129
|
this.#cache.clear();
|
|
1057
1130
|
this.#usageOverlays.clear();
|
|
1058
1131
|
}
|