@oh-my-pi/pi-ai 17.1.2 → 17.1.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.
Files changed (54) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/README.md +1 -1
  3. package/dist/types/auth-broker/client.d.ts +7 -1
  4. package/dist/types/auth-broker/remote-store.d.ts +4 -1
  5. package/dist/types/auth-broker/types.d.ts +6 -1
  6. package/dist/types/auth-broker/wire-schemas.d.ts +41 -0
  7. package/dist/types/auth-storage.d.ts +55 -0
  8. package/dist/types/providers/anthropic.d.ts +1 -7
  9. package/dist/types/providers/claude-code-fingerprint.d.ts +15 -0
  10. package/dist/types/providers/cursor.d.ts +30 -7
  11. package/dist/types/providers/openai-codex/request-transformer.d.ts +5 -4
  12. package/dist/types/providers/openai-codex-responses.d.ts +3 -0
  13. package/dist/types/registry/alibaba-token-plan.d.ts +11 -0
  14. package/dist/types/registry/oauth/anthropic-constants.d.ts +12 -0
  15. package/dist/types/registry/oauth/anthropic.d.ts +1 -0
  16. package/dist/types/registry/oauth/index.d.ts +1 -0
  17. package/dist/types/registry/oauth/types.d.ts +8 -0
  18. package/dist/types/types.d.ts +71 -1
  19. package/dist/types/usage/minimax-code.d.ts +1 -0
  20. package/dist/types/usage.d.ts +10 -0
  21. package/dist/types/utils/idle-iterator.d.ts +6 -4
  22. package/package.json +4 -4
  23. package/src/auth-broker/client.ts +24 -1
  24. package/src/auth-broker/remote-store.ts +12 -0
  25. package/src/auth-broker/server.ts +7 -0
  26. package/src/auth-broker/types.ts +7 -0
  27. package/src/auth-broker/wire-schemas.ts +23 -0
  28. package/src/auth-storage.ts +279 -29
  29. package/src/error/flags.ts +1 -1
  30. package/src/providers/__tests__/kimi-code-thinking.test.ts +40 -5
  31. package/src/providers/amazon-bedrock.ts +3 -4
  32. package/src/providers/anthropic.ts +98 -32
  33. package/src/providers/azure-openai-responses.ts +36 -5
  34. package/src/providers/claude-code-fingerprint.ts +19 -0
  35. package/src/providers/cursor.ts +525 -40
  36. package/src/providers/openai-codex/request-transformer.ts +27 -7
  37. package/src/providers/openai-codex-responses.ts +37 -14
  38. package/src/providers/openai-completions.ts +2 -1
  39. package/src/providers/openai-responses.ts +16 -9
  40. package/src/providers/openai-shared.ts +12 -1
  41. package/src/registry/alibaba-token-plan.ts +103 -21
  42. package/src/registry/oauth/anthropic-constants.ts +12 -0
  43. package/src/registry/oauth/anthropic.ts +3 -1
  44. package/src/registry/oauth/index.ts +1 -0
  45. package/src/registry/oauth/types.ts +8 -0
  46. package/src/stream.ts +7 -2
  47. package/src/types.ts +79 -1
  48. package/src/usage/alibaba-token-plan.ts +34 -6
  49. package/src/usage/claude.ts +7 -2
  50. package/src/usage/minimax-code.ts +280 -19
  51. package/src/usage/openai-codex.ts +61 -14
  52. package/src/usage.ts +10 -0
  53. package/src/utils/idle-iterator.ts +8 -6
  54. package/src/utils.ts +5 -5
@@ -350,6 +350,13 @@ export interface StreamOptions {
350
350
  * and then to a 120s default.
351
351
  */
352
352
  streamIdleTimeoutMs?: number;
353
+ /**
354
+ * Optional cap on Codex SSE pre-response attempts, including the initial
355
+ * request. WebSocket retries and outer agent retries have separate budgets.
356
+ * Finite values below `1` and non-finite values are clamped to one request;
357
+ * omission preserves the provider default.
358
+ */
359
+ codexSseMaxAttempts?: number;
353
360
  /**
354
361
  * Optional retry delay hook for tests and transports that need custom scheduling.
355
362
  */
@@ -400,7 +407,14 @@ export interface SimpleStreamOptions extends Omit<StreamOptions, "apiKey"> {
400
407
  thinkingBudgets?: ThinkingBudgets;
401
408
  /** Cursor exec handlers for local tool execution */
402
409
  cursorExecHandlers?: CursorExecHandlers;
403
- /** Hook to handle tool results from Cursor exec */
410
+ /**
411
+ * Optional rewrite of Cursor exec-channel tool results. May return a Promise.
412
+ *
413
+ * The Agent reserves the original result in its buffer before awaiting this
414
+ * hook, and the `message_end` drain waits for a still-pending rewrite, so an
415
+ * async transformer is honored even when the turn closes in the same chunk.
416
+ * A rejecting transformer is swallowed and the reserved payload stands in.
417
+ */
404
418
  cursorOnToolResult?: CursorToolResultHandler;
405
419
  /** Optional tool choice override for compatible providers */
406
420
  toolChoice?: ToolChoice;
@@ -715,7 +729,24 @@ export type CursorExecHandlerResult<T> = {
715
729
  result: T;
716
730
  toolResult?: ToolResultMessage;
717
731
  } | T | ToolResultMessage;
732
+ /**
733
+ * Optional rewrite of a Cursor exec-channel tool result.
734
+ * May return a Promise. Returning `undefined` keeps the original result.
735
+ *
736
+ * The Agent reserves the original result in its buffer before awaiting this
737
+ * hook, and the `message_end` drain waits for a still-pending rewrite, so an
738
+ * async transformer is honored even when the turn closes in the same chunk.
739
+ * A rejecting transformer is swallowed and the reserved payload stands in.
740
+ */
718
741
  export type CursorToolResultHandler = (result: ToolResultMessage) => ToolResultMessage | undefined | Promise<ToolResultMessage | undefined>;
742
+ /**
743
+ * Identifies the synthesized assistant block a Cursor exec call was filed
744
+ * under, so paths that produce no handler `toolResult` can still pair one.
745
+ */
746
+ export interface CursorExecPairing {
747
+ toolCallId: string;
748
+ toolName: string;
749
+ }
719
750
  export interface CursorMcpCall {
720
751
  name: string;
721
752
  providerIdentifier: string;
@@ -724,6 +755,43 @@ export interface CursorMcpCall {
724
755
  args: Record<string, unknown>;
725
756
  rawArgs: Record<string, Uint8Array>;
726
757
  }
758
+ export interface CursorTodoSnapshotItem {
759
+ content: string;
760
+ status: "pending" | "in_progress" | "completed" | "abandoned";
761
+ }
762
+ /**
763
+ * Authoritative todo list state settled by Cursor's server-side
764
+ * `update_todos` / `read_todos` tools.
765
+ */
766
+ export interface CursorTodoSnapshot {
767
+ todos: CursorTodoSnapshotItem[];
768
+ /** True when the server reported the update as a merge. Presentation only. */
769
+ merged: boolean;
770
+ }
771
+ /**
772
+ * Settles a native todo call in the host.
773
+ *
774
+ * Called for every completed native todo call, not just successful ones: the
775
+ * interactive todo card only resolves on a matching `tool_execution_end`, so a
776
+ * refused or failed call that stayed silent would animate forever.
777
+ *
778
+ * `snapshot` is the server-confirmed list, or `null` when there is nothing to
779
+ * mirror — a server error (`error` set), or a benign refusal with `error` null:
780
+ * a filtered, truncated, or empty read, or a snapshot the local model cannot
781
+ * represent (two rows sharing content). Local state MUST be left untouched
782
+ * unless a snapshot is supplied.
783
+ *
784
+ * `toolCallId` is the id of the streamed native call, which is also the key the
785
+ * interactive transcript filed the visible block under. The host MUST reuse it
786
+ * when emitting the synthetic completion, or that block never resolves.
787
+ *
788
+ * Returns the result to persist for that block — always, since every settle
789
+ * needs a paired result or `buildSessionContext` strips the block as dangling.
790
+ * Only the host knows the phase grouping the todo renderer replays from, so the
791
+ * provider persists this value verbatim. When no handler is registered at all,
792
+ * the provider falls back to its own summary-only result.
793
+ */
794
+ export type CursorTodoSyncHandler = (snapshot: CursorTodoSnapshot | null, toolCallId: string, error: string | null) => ToolResultMessage;
727
795
  export interface CursorShellStreamCallbacks {
728
796
  onStdout(data: string): void;
729
797
  onStderr(data: string): void;
@@ -738,6 +806,8 @@ export interface CursorExecHandlers {
738
806
  shellStream?: (args: ShellArgs, callbacks: CursorShellStreamCallbacks) => Promise<CursorExecHandlerResult<ShellResult>>;
739
807
  diagnostics?: (args: DiagnosticsArgs) => Promise<CursorExecHandlerResult<DiagnosticsResult>>;
740
808
  mcp?: (call: CursorMcpCall) => Promise<CursorExecHandlerResult<McpResult>>;
809
+ /** Mirror Cursor's server-owned todo list into local session state. */
810
+ todoSync?: CursorTodoSyncHandler;
741
811
  onToolResult?: CursorToolResultHandler;
742
812
  }
743
813
  /**
@@ -1,2 +1,3 @@
1
1
  import type { UsageProvider } from "../usage.js";
2
+ /** MiniMax Token Plan (international, `api.minimax.io`). */
2
3
  export declare const minimaxCodeUsageProvider: UsageProvider;
@@ -401,6 +401,16 @@ export interface CredentialRankingStrategy {
401
401
  * not block unrelated families on the same OAuth credential.
402
402
  */
403
403
  blockScope?(context?: CredentialRankingContext): string | undefined;
404
+ /**
405
+ * Scopes that apply to a request, most specific first. With a context, the
406
+ * request's own scope plus any legacy catch-all scope whose blocks still
407
+ * apply to everything. Without one — reconciliation runs with no request —
408
+ * every scope whose blocks must be healed.
409
+ *
410
+ * A provider that scopes backoff by model family must implement this, or a
411
+ * block written under one scope is invisible to requests and to healing.
412
+ */
413
+ blockScopes?(context?: CredentialRankingContext): string[];
404
414
  /** Fallback window durations (ms) when limits don't specify durationMs. */
405
415
  windowDefaults: {
406
416
  primaryMs: number;
@@ -39,11 +39,13 @@ export declare function getStreamFirstEventTimeoutMs(idleTimeoutMs?: number, fal
39
39
  * `"0"` disable) wins outright. Otherwise the resolved idle (caller-supplied
40
40
  * `idleTimeoutMs` — which itself already encompasses per-call
41
41
  * `streamIdleTimeoutMs` or `PI_OPENAI_STREAM_IDLE_TIMEOUT_MS` resolved
42
- * upstream) floors the first-event budget so slow local OpenAI-compatible
43
- * servers are not undercut by a shorter `PI_STREAM_FIRST_EVENT_TIMEOUT_MS`
44
- * or the global default during prompt processing.
42
+ * upstream) floors the first-event budget so slow OpenAI-compatible servers
43
+ * are not undercut by a shorter `PI_STREAM_FIRST_EVENT_TIMEOUT_MS` or the
44
+ * global default during prompt processing. A zero per-provider fallback
45
+ * disables the first-event watchdog unless an environment override is set.
45
46
  *
46
- * Returns `undefined` when an explicit env knob disables the watchdog.
47
+ * Returns `0` when an explicit env knob or per-provider fallback disables the
48
+ * watchdog, preserving the sentinel through iterator timeout resolution.
47
49
  */
48
50
  export declare function getOpenAIStreamFirstEventTimeoutMs(idleTimeoutMs?: number, fallbackMs?: number): number | undefined;
49
51
  /**
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.2",
4
+ "version": "17.1.4",
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.2",
42
- "@oh-my-pi/pi-utils": "17.1.2",
43
- "@oh-my-pi/pi-wire": "17.1.2",
41
+ "@oh-my-pi/pi-catalog": "17.1.4",
42
+ "@oh-my-pi/pi-utils": "17.1.4",
43
+ "@oh-my-pi/pi-wire": "17.1.4",
44
44
  "arktype": "2.2.3",
45
45
  "zod": "^4"
46
46
  },
@@ -7,7 +7,7 @@
7
7
  */
8
8
  import { readSseEvents } from "@oh-my-pi/pi-utils";
9
9
  import { type } from "arktype";
10
- import type { AuthCredential } from "../auth-storage";
10
+ import type { AuthCredential, DisabledCredentialSummary } from "../auth-storage";
11
11
  import type {
12
12
  ClientUsageReportRequest,
13
13
  ClientUsageReportResponse,
@@ -20,6 +20,7 @@ import type {
20
20
  CredentialRefreshResponse,
21
21
  CredentialUploadRequest,
22
22
  CredentialUploadResponse,
23
+ DisabledCredentialsResponse,
23
24
  HealthzResponse,
24
25
  SnapshotResponse,
25
26
  SnapshotStreamEvent,
@@ -35,6 +36,7 @@ import {
35
36
  credentialDisableResponseSchema,
36
37
  credentialRefreshResponseSchema,
37
38
  credentialUploadResponseSchema,
39
+ disabledCredentialsResponseSchema,
38
40
  healthzResponseSchema,
39
41
  snapshotResponseSchema,
40
42
  snapshotStreamEventSchema,
@@ -306,6 +308,27 @@ export class AuthBrokerClient {
306
308
  });
307
309
  }
308
310
 
311
+ /**
312
+ * Disabled-credential tombstones (identity + cause, no token material).
313
+ * Returns an empty list against brokers predating `GET
314
+ * /v1/credentials/disabled` (404).
315
+ */
316
+ async listDisabledCredentials(provider?: string, signal?: AbortSignal): Promise<DisabledCredentialSummary[]> {
317
+ const params = new URLSearchParams();
318
+ if (provider) params.set("provider", provider);
319
+ const path = `/v1/credentials/disabled${params.size > 0 ? `?${params.toString()}` : ""}`;
320
+ try {
321
+ const response = await this.#request<DisabledCredentialsResponse>("GET", path, {
322
+ schema: disabledCredentialsResponseSchema,
323
+ signal,
324
+ });
325
+ return response.disabled;
326
+ } catch (error) {
327
+ if (error instanceof AuthBrokerError && error.status === 404) return [];
328
+ throw error;
329
+ }
330
+ }
331
+
309
332
  async uploadCredential(
310
333
  provider: string,
311
334
  credential: AuthCredential,
@@ -14,6 +14,7 @@ import {
14
14
  type AuthCredential,
15
15
  type AuthCredentialSnapshotEntry,
16
16
  type AuthCredentialStore,
17
+ type DisabledCredentialSummary,
17
18
  type OAuthCredential,
18
19
  REMOTE_REFRESH_SENTINEL,
19
20
  type StoredAuthCredential,
@@ -471,6 +472,11 @@ export class RemoteAuthCredentialStore implements AuthCredentialStore {
471
472
  return out;
472
473
  }
473
474
 
475
+ /** Broker-backed disabled tombstones; empty against brokers predating the endpoint. */
476
+ listDisabledCredentials(provider?: string, signal?: AbortSignal): Promise<DisabledCredentialSummary[]> {
477
+ return this.#client.listDisabledCredentials(provider, signal);
478
+ }
479
+
474
480
  getCredentialBlock(credentialId: number, providerKey: string, blockScope: string): number | undefined {
475
481
  const nowMs = Date.now();
476
482
  this.cleanExpiredCredentialBlocks(nowMs);
@@ -533,6 +539,12 @@ export class RemoteAuthCredentialStore implements AuthCredentialStore {
533
539
  });
534
540
  }
535
541
 
542
+ deleteCredentialBlock(_credentialId: number, _providerKey: string, _blockScope: string): void {
543
+ // The broker protocol only supports deleting every block for a credential.
544
+ // Keep scoped blocks until expiry rather than risk deleting unrelated or
545
+ // newer broker state through that broader operation.
546
+ }
547
+
536
548
  deleteCredentialBlocks(credentialId: number): void {
537
549
  this.#deleteSnapshotBlocks(credentialId);
538
550
  for (const key of this.#credentialBlockReconcileAfter.keys()) {
@@ -22,6 +22,7 @@ import type {
22
22
  CredentialDisableResponse,
23
23
  CredentialRefreshResponse,
24
24
  CredentialUploadResponse,
25
+ DisabledCredentialsResponse,
25
26
  HealthzResponse,
26
27
  RefresherSchedule,
27
28
  SnapshotEntry,
@@ -659,6 +660,12 @@ export function startAuthBroker(opts: AuthBrokerServerOptions): AuthBrokerServer
659
660
  return json(500, { error: message });
660
661
  }
661
662
  }
663
+ if (req.method === "GET" && pathname === "/v1/credentials/disabled") {
664
+ const provider = url.searchParams.get("provider") ?? undefined;
665
+ const disabled = await opts.storage.listDisabledCredentials(provider, req.signal);
666
+ const body: DisabledCredentialsResponse = { generatedAt: Date.now(), disabled };
667
+ return json(200, body);
668
+ }
662
669
  const refreshMatch = req.method === "POST" ? pathname.match(REFRESH_ROUTE) : null;
663
670
  if (refreshMatch) {
664
671
  const id = Number.parseInt(refreshMatch[1], 10);
@@ -10,6 +10,7 @@ import type {
10
10
  AuthCredential,
11
11
  AuthCredentialSnapshot,
12
12
  AuthCredentialSnapshotEntry,
13
+ DisabledCredentialSummary,
13
14
  StoredCredentialBlock,
14
15
  } from "../auth-storage";
15
16
  import type { ClientUsageClientSummary, ClientUsageReport, UsageHistoryEntry, UsageReport } from "../usage";
@@ -86,6 +87,12 @@ export interface CredentialDisableResponse {
86
87
  ok: boolean;
87
88
  }
88
89
 
90
+ /** GET /v1/credentials/disabled response body — tombstones of auto-disabled rows. */
91
+ export interface DisabledCredentialsResponse {
92
+ generatedAt: number;
93
+ disabled: DisabledCredentialSummary[];
94
+ }
95
+
89
96
  /** POST /v1/credential/:id/block request body. */
90
97
  export type CredentialBlockRequest = CredentialBlockSnapshot;
91
98
 
@@ -34,6 +34,7 @@ export const oauthCredentialSchema = type({
34
34
  "accountId?": "string",
35
35
  "orgId?": "string",
36
36
  "orgName?": "string",
37
+ "authorizedAt?": "number",
37
38
  });
38
39
 
39
40
  /** OAuth credential as it appears in broker snapshots — refresh replaced with sentinel. */
@@ -49,6 +50,7 @@ export const remoteOauthCredentialSchema = type({
49
50
  "accountId?": "string",
50
51
  "orgId?": "string",
51
52
  "orgName?": "string",
53
+ "authorizedAt?": "number",
52
54
  });
53
55
 
54
56
  export const apiKeyCredentialSchema = type({
@@ -320,6 +322,27 @@ export const credentialDisableResponseSchema = type({
320
322
  ok: "boolean",
321
323
  });
322
324
 
325
+ /** One disabled-credential tombstone — identity + cause, never token material. */
326
+ export const disabledCredentialSummarySchema = type({
327
+ "+": "reject",
328
+ id: "number.integer",
329
+ provider: type("string").atLeastLength(1),
330
+ type: "'oauth' | 'api_key'",
331
+ "email?": "string",
332
+ "accountId?": "string",
333
+ "orgId?": "string",
334
+ "orgName?": "string",
335
+ cause: "string",
336
+ "disabledAtMs?": "number",
337
+ });
338
+
339
+ /** Broker `GET /v1/credentials/disabled` response. */
340
+ export const disabledCredentialsResponseSchema = type({
341
+ "+": "reject",
342
+ generatedAt: "number",
343
+ disabled: disabledCredentialSummarySchema.array(),
344
+ });
345
+
323
346
  // ─── Credential blocks ──────────────────────────────────────────────────────
324
347
 
325
348
  export const credentialBlockRequestSchema = credentialBlockSnapshotSchema;