@oh-my-pi/pi-ai 17.4.2 → 18.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/dist/types/auth-broker/wire-schemas.d.ts +68 -4
- package/dist/types/index.d.ts +1 -0
- package/dist/types/utils/glyph-codec.d.ts +22 -0
- package/package.json +5 -5
- package/src/auth-broker/client.ts +37 -18
- package/src/auth-broker/server.ts +10 -5
- package/src/auth-broker/wire-schemas.ts +402 -40
- package/src/index.ts +1 -0
- package/src/providers/anthropic.ts +2 -0
- package/src/stream.ts +29 -3
- package/src/utils/glyph-codec.ts +454 -0
- package/src/utils/glyph-notice.md +3 -0
- package/dist/types/auth-broker/wire-schema-resource.d.ts +0 -53
- package/src/auth-broker/wire-schema-resource.ts +0 -487
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [18.0.0] - 2026-08-22
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Added reversible private-use glyph tokenization for Claude-compatible provider requests, including prompt notices, streamed response decoding, and safe handling of unresolved model-authored glyph tokens.
|
|
10
|
+
|
|
11
|
+
## [17.4.3] - 2026-08-21
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- Fixed completed Anthropic turns remaining busy when the provider sent `message_stop` but kept the SSE connection open, which stranded tool execution and queued steering until timeout.
|
|
16
|
+
|
|
5
17
|
## [17.4.2] - 2026-08-21
|
|
6
18
|
|
|
7
19
|
### Added
|
|
@@ -1,5 +1,69 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* ArkType schemas for the auth-broker wire protocol.
|
|
3
|
+
*
|
|
4
|
+
* Shared between the server (validates inbound request bodies) and the client
|
|
5
|
+
* (validates responses from the broker). Schemas mirror the TypeScript types
|
|
6
|
+
* in `./types.ts` 1:1; the types remain the source of truth for static typing,
|
|
7
|
+
* and `Type` is asserted-compatible with them where possible.
|
|
8
|
+
*
|
|
9
|
+
* Envelope and fixed-shape schemas use `"+": "reject"` so unknown keys are
|
|
10
|
+
* rejected — the previous implementation used a hand-rolled `hasOnlyFields`
|
|
11
|
+
* allowlist for the same effect. The OAuth credential schema is the deliberate
|
|
12
|
+
* exception (standard type keeps extra keys): it preserves provider-specific extension fields so
|
|
13
|
+
* they round-trip through the broker instead of being dropped (see below).
|
|
14
|
+
*/
|
|
15
|
+
import { type FluentType } from "@oh-my-pi/omptype";
|
|
16
|
+
import { type ApiKeyCredential, type AuthCredential, type AuthCredentialSnapshotEntry, type DisabledCredentialSummary, type OAuthCredential, type RemoteOAuthCredential, type SnapshotCredential } from "../auth-storage.js";
|
|
17
|
+
import type { ClientUsageReportRequest, ClientUsageReportResponse, ClientUsageSummaryResponse, CredentialBlockRequest, CredentialBlockResponse, CredentialBlockSnapshot, CredentialBlocksDeleteResponse, CredentialDisableResponse, CredentialRefreshResponse, CredentialUploadRequest, CredentialUploadResponse, DisabledCredentialsResponse, HealthzResponse, RefresherSchedule, SnapshotEntry, SnapshotResponse, SnapshotStreamEntryEvent, SnapshotStreamEvent, SnapshotStreamRemovedEvent, SnapshotStreamSnapshotEvent, UsageHistoryResponse, UsageResponse, UsageStaleResponse } from "./types.js";
|
|
18
|
+
/** Real OAuth credential (broker-side) — refresh token is the actual upstream value. */
|
|
19
|
+
export declare const oauthCredentialSchema: FluentType<OAuthCredential>;
|
|
20
|
+
/** OAuth credential as it appears in broker snapshots — refresh replaced with sentinel. */
|
|
21
|
+
export declare const remoteOauthCredentialSchema: FluentType<RemoteOAuthCredential>;
|
|
22
|
+
export declare const apiKeyCredentialSchema: FluentType<ApiKeyCredential>;
|
|
23
|
+
/** Discriminated union accepted on POST /v1/credential (writes). */
|
|
24
|
+
export declare const writableAuthCredentialSchema: FluentType<AuthCredential>;
|
|
25
|
+
/** Discriminated union returned in snapshots (refresh is sentinel for OAuth). */
|
|
26
|
+
export declare const snapshotCredentialSchema: FluentType<SnapshotCredential>;
|
|
27
|
+
export declare const credentialSnapshotEntrySchema: FluentType<AuthCredentialSnapshotEntry>;
|
|
28
|
+
export declare const credentialBlockSnapshotSchema: FluentType<CredentialBlockSnapshot>;
|
|
29
|
+
export declare const snapshotEntrySchema: FluentType<SnapshotEntry>;
|
|
30
|
+
export declare const refresherScheduleSchema: FluentType<RefresherSchedule>;
|
|
31
|
+
export declare const snapshotResponseSchema: FluentType<SnapshotResponse>;
|
|
32
|
+
/** First frame on connect — full snapshot embedded inline with a `kind` tag. */
|
|
33
|
+
export declare const snapshotStreamSnapshotEventSchema: FluentType<SnapshotStreamSnapshotEvent>;
|
|
34
|
+
/** Per-credential upsert/refresh delta. */
|
|
35
|
+
export declare const snapshotStreamEntryEventSchema: FluentType<SnapshotStreamEntryEvent>;
|
|
36
|
+
/** Per-credential delete delta. */
|
|
37
|
+
export declare const snapshotStreamRemovedEventSchema: FluentType<SnapshotStreamRemovedEvent>;
|
|
38
|
+
/** Discriminated union over every event frame the snapshot stream emits. */
|
|
39
|
+
export declare const snapshotStreamEventSchema: FluentType<SnapshotStreamEvent>;
|
|
40
|
+
export declare const healthzResponseSchema: FluentType<HealthzResponse>;
|
|
41
|
+
/**
|
|
42
|
+
* Broker `/v1/usage` response. Reports are full {@link UsageReport}s minus the
|
|
43
|
+
* heavy provider-specific `raw` field (the server strips it before send) — we
|
|
44
|
+
* keep `raw` optional in the underlying schema so a misconfigured broker that
|
|
45
|
+
* forgot to strip still validates.
|
|
46
|
+
*/
|
|
47
|
+
export declare const usageResponseSchema: FluentType<UsageResponse>;
|
|
48
|
+
/** Broker `/v1/usage/history` response — recorded usage-limit snapshots, oldest first. */
|
|
49
|
+
export declare const usageHistoryResponseSchema: FluentType<UsageHistoryResponse>;
|
|
50
|
+
/** Broker `POST /v1/usage/observed` request — one client's batched observed usage. */
|
|
51
|
+
export declare const clientUsageReportRequestSchema: FluentType<ClientUsageReportRequest>;
|
|
52
|
+
export declare const clientUsageReportResponseSchema: FluentType<ClientUsageReportResponse>;
|
|
53
|
+
/** Broker `GET /v1/usage/clients` response — per-client token burn aggregates. */
|
|
54
|
+
export declare const clientUsageSummaryResponseSchema: FluentType<ClientUsageSummaryResponse>;
|
|
55
|
+
export declare const credentialRefreshResponseSchema: FluentType<CredentialRefreshResponse>;
|
|
56
|
+
export declare const credentialDisableRequestSchema: FluentType<{
|
|
2
57
|
cause?: string;
|
|
3
|
-
}
|
|
4
|
-
|
|
5
|
-
|
|
58
|
+
}>;
|
|
59
|
+
export declare const credentialDisableResponseSchema: FluentType<CredentialDisableResponse>;
|
|
60
|
+
/** One disabled-credential tombstone — identity + cause, never token material. */
|
|
61
|
+
export declare const disabledCredentialSummarySchema: FluentType<DisabledCredentialSummary>;
|
|
62
|
+
/** Broker `GET /v1/credentials/disabled` response. */
|
|
63
|
+
export declare const disabledCredentialsResponseSchema: FluentType<DisabledCredentialsResponse>;
|
|
64
|
+
export declare const credentialBlockRequestSchema: FluentType<CredentialBlockRequest>;
|
|
65
|
+
export declare const credentialBlockResponseSchema: FluentType<CredentialBlockResponse>;
|
|
66
|
+
export declare const credentialBlocksDeleteResponseSchema: FluentType<CredentialBlocksDeleteResponse>;
|
|
67
|
+
export declare const usageStaleResponseSchema: FluentType<UsageStaleResponse>;
|
|
68
|
+
export declare const credentialUploadRequestSchema: FluentType<CredentialUploadRequest>;
|
|
69
|
+
export declare const credentialUploadResponseSchema: FluentType<CredentialUploadResponse>;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -44,6 +44,7 @@ export * from "./usage/xai-oauth.js";
|
|
|
44
44
|
export * from "./usage/zai.js";
|
|
45
45
|
export * from "./utils/anthropic-auth.js";
|
|
46
46
|
export * from "./utils/event-stream.js";
|
|
47
|
+
export * from "./utils/glyph-codec.js";
|
|
47
48
|
export * from "./utils/openrouter-headers.js";
|
|
48
49
|
export * from "./utils/retry.js";
|
|
49
50
|
export * from "./utils/schema/index.js";
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Context, CursorExecHandlers } from "../types.js";
|
|
2
|
+
import { AssistantMessageEventStream } from "./event-stream.js";
|
|
3
|
+
/** Per-request glyph codec applied at the provider wire boundary. */
|
|
4
|
+
export interface GlyphCodec {
|
|
5
|
+
/** Context with model-bound glyphs encoded and one conditional convention notice. */
|
|
6
|
+
context: Context;
|
|
7
|
+
/** Decodes glyph tokens in the stream's terminal assistant message. */
|
|
8
|
+
wrap(inner: AssistantMessageEventStream): AssistantMessageEventStream;
|
|
9
|
+
/** Decodes Cursor Pi calls and encodes their provider-bound results. */
|
|
10
|
+
wrapCursorExecHandlers(handlers: CursorExecHandlers): CursorExecHandlers;
|
|
11
|
+
/** Whether this request has encoded at least one glyph or literal token prefix. */
|
|
12
|
+
active: boolean;
|
|
13
|
+
}
|
|
14
|
+
/** Encode one string; returns the same reference when nothing changed. */
|
|
15
|
+
export declare function encodeGlyphText(text: string): string;
|
|
16
|
+
/** Decodes glyph tokens and unescapes `⟦E⟧` to `⟦`. */
|
|
17
|
+
export declare function decodeGlyphText(text: string): string;
|
|
18
|
+
/**
|
|
19
|
+
* Encode a context for the provider wire without mutating stored history.
|
|
20
|
+
* Reapplying it to the returned branded context is an inert identity pass.
|
|
21
|
+
*/
|
|
22
|
+
export declare function applyGlyphCodec(context: Context): GlyphCodec;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-ai",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "18.0.0",
|
|
5
5
|
"description": "Unified LLM API with automatic model discovery and provider configuration",
|
|
6
6
|
"homepage": "https://omp.sh",
|
|
7
7
|
"author": "Stencil Labs, Inc.",
|
|
@@ -37,10 +37,10 @@
|
|
|
37
37
|
"fmt": "biome format --write ."
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@oh-my-pi/omptype": "
|
|
41
|
-
"@oh-my-pi/pi-catalog": "
|
|
42
|
-
"@oh-my-pi/pi-utils": "
|
|
43
|
-
"@oh-my-pi/pi-wire": "
|
|
40
|
+
"@oh-my-pi/omptype": "18.0.0",
|
|
41
|
+
"@oh-my-pi/pi-catalog": "18.0.0",
|
|
42
|
+
"@oh-my-pi/pi-utils": "18.0.0",
|
|
43
|
+
"@oh-my-pi/pi-wire": "18.0.0"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@types/bun": "^1.3.14"
|
|
@@ -30,21 +30,40 @@ import type {
|
|
|
30
30
|
UsageStaleResponse,
|
|
31
31
|
} from "./types";
|
|
32
32
|
import { AUTH_BROKER_CAPABILITIES_HEADER, AUTH_BROKER_CAPABILITY_CODEX_METER_BLOCK_SCOPES } from "./types";
|
|
33
|
-
import {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
33
|
+
import {
|
|
34
|
+
clientUsageReportResponseSchema,
|
|
35
|
+
clientUsageSummaryResponseSchema,
|
|
36
|
+
credentialBlockResponseSchema,
|
|
37
|
+
credentialBlocksDeleteResponseSchema,
|
|
38
|
+
credentialDisableResponseSchema,
|
|
39
|
+
credentialRefreshResponseSchema,
|
|
40
|
+
credentialUploadResponseSchema,
|
|
41
|
+
disabledCredentialsResponseSchema,
|
|
42
|
+
healthzResponseSchema,
|
|
43
|
+
snapshotResponseSchema,
|
|
44
|
+
snapshotStreamEventSchema,
|
|
45
|
+
usageHistoryResponseSchema,
|
|
46
|
+
usageResponseSchema,
|
|
47
|
+
usageStaleResponseSchema,
|
|
48
|
+
} from "./wire-schemas";
|
|
49
|
+
|
|
50
|
+
/** Response schema per endpoint, keyed by the name `#request` callers pass. */
|
|
51
|
+
const RESPONSE_SCHEMAS = {
|
|
52
|
+
clientUsageReportResponseSchema,
|
|
53
|
+
clientUsageSummaryResponseSchema,
|
|
54
|
+
credentialBlockResponseSchema,
|
|
55
|
+
credentialBlocksDeleteResponseSchema,
|
|
56
|
+
credentialDisableResponseSchema,
|
|
57
|
+
credentialRefreshResponseSchema,
|
|
58
|
+
credentialUploadResponseSchema,
|
|
59
|
+
disabledCredentialsResponseSchema,
|
|
60
|
+
healthzResponseSchema,
|
|
61
|
+
usageHistoryResponseSchema,
|
|
62
|
+
usageResponseSchema,
|
|
63
|
+
usageStaleResponseSchema,
|
|
64
|
+
} as const;
|
|
65
|
+
|
|
66
|
+
type AuthBrokerResponseSchemaName = keyof typeof RESPONSE_SCHEMAS;
|
|
48
67
|
|
|
49
68
|
export interface AuthBrokerClientOptions {
|
|
50
69
|
/** Base URL (e.g. `https://broker.tailnet:8765`). Trailing slashes are trimmed. */
|
|
@@ -155,7 +174,7 @@ export class AuthBrokerClient {
|
|
|
155
174
|
}
|
|
156
175
|
const text = await response.text();
|
|
157
176
|
const raw = this.#parseJson(text, response.status);
|
|
158
|
-
const validated =
|
|
177
|
+
const validated = snapshotResponseSchema(raw);
|
|
159
178
|
if (validated instanceof type.errors) {
|
|
160
179
|
throw new AuthBrokerError("Auth broker response failed schema validation", {
|
|
161
180
|
status: response.status,
|
|
@@ -224,7 +243,7 @@ export class AuthBrokerClient {
|
|
|
224
243
|
cause: err,
|
|
225
244
|
});
|
|
226
245
|
}
|
|
227
|
-
const validated =
|
|
246
|
+
const validated = snapshotStreamEventSchema(parsed);
|
|
228
247
|
if (validated instanceof type.errors) {
|
|
229
248
|
throw new AuthBrokerError("Auth broker stream event failed schema validation", {
|
|
230
249
|
body: validated.summary,
|
|
@@ -391,7 +410,7 @@ export class AuthBrokerClient {
|
|
|
391
410
|
const response = await this.#fetchRaw(method, path, opts);
|
|
392
411
|
const text = await response.text();
|
|
393
412
|
const raw = this.#parseJson(text, response.status);
|
|
394
|
-
const validated =
|
|
413
|
+
const validated = RESPONSE_SCHEMAS[opts.schema](raw);
|
|
395
414
|
if (validated instanceof type.errors) {
|
|
396
415
|
throw new AuthBrokerError("Auth broker response failed schema validation", {
|
|
397
416
|
status: response.status,
|
|
@@ -41,7 +41,12 @@ import {
|
|
|
41
41
|
DEFAULT_SERVER_IDLE_TIMEOUT_S,
|
|
42
42
|
DEFAULT_STREAM_KEEPALIVE_MS,
|
|
43
43
|
} from "./types";
|
|
44
|
-
import {
|
|
44
|
+
import {
|
|
45
|
+
clientUsageReportRequestSchema,
|
|
46
|
+
credentialBlockRequestSchema,
|
|
47
|
+
credentialDisableRequestSchema,
|
|
48
|
+
credentialUploadRequestSchema,
|
|
49
|
+
} from "./wire-schemas";
|
|
45
50
|
|
|
46
51
|
const DEFAULT_EXTERNAL_CHANGE_POLL_MS = 250;
|
|
47
52
|
|
|
@@ -712,7 +717,7 @@ export function startAuthBroker(opts: AuthBrokerServerOptions): AuthBrokerServer
|
|
|
712
717
|
return json(200, { generatedAt: Date.now(), entries });
|
|
713
718
|
}
|
|
714
719
|
if (req.method === "POST" && pathname === "/v1/usage/observed") {
|
|
715
|
-
const parsed = await parseBody(req,
|
|
720
|
+
const parsed = await parseBody(req, clientUsageReportRequestSchema);
|
|
716
721
|
if (!parsed.ok) return parsed.response;
|
|
717
722
|
// Arktype's inferred union collides the `entries` field with
|
|
718
723
|
// Array.prototype.entries; the schema already validated the shape.
|
|
@@ -779,7 +784,7 @@ export function startAuthBroker(opts: AuthBrokerServerOptions): AuthBrokerServer
|
|
|
779
784
|
const disableMatch = req.method === "POST" ? pathname.match(DISABLE_ROUTE) : null;
|
|
780
785
|
if (disableMatch) {
|
|
781
786
|
const id = Number.parseInt(disableMatch[1], 10);
|
|
782
|
-
const parsed = await parseBody(req,
|
|
787
|
+
const parsed = await parseBody(req, credentialDisableRequestSchema, {
|
|
783
788
|
allowEmpty: true,
|
|
784
789
|
});
|
|
785
790
|
if (!parsed.ok) return parsed.response;
|
|
@@ -797,7 +802,7 @@ export function startAuthBroker(opts: AuthBrokerServerOptions): AuthBrokerServer
|
|
|
797
802
|
const blockMatch = req.method === "POST" ? pathname.match(BLOCK_ROUTE) : null;
|
|
798
803
|
if (blockMatch) {
|
|
799
804
|
const id = Number.parseInt(blockMatch[1], 10);
|
|
800
|
-
const parsed = await parseBody(req,
|
|
805
|
+
const parsed = await parseBody(req, credentialBlockRequestSchema);
|
|
801
806
|
if (!parsed.ok) return parsed.response;
|
|
802
807
|
const block: StoredCredentialBlock = {
|
|
803
808
|
credentialId: id,
|
|
@@ -847,7 +852,7 @@ export function startAuthBroker(opts: AuthBrokerServerOptions): AuthBrokerServer
|
|
|
847
852
|
}
|
|
848
853
|
}
|
|
849
854
|
if (req.method === "POST" && pathname === "/v1/credential") {
|
|
850
|
-
const parsed = await parseBody(req,
|
|
855
|
+
const parsed = await parseBody(req, credentialUploadRequestSchema);
|
|
851
856
|
if (!parsed.ok) return parsed.response;
|
|
852
857
|
const { provider, credential } = parsed.data;
|
|
853
858
|
try {
|