@oh-my-pi/pi-ai 17.1.8 → 17.2.1

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 (65) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/dist/types/auth-broker/server.d.ts +5 -0
  3. package/dist/types/auth-broker/types.d.ts +4 -0
  4. package/dist/types/auth-storage.d.ts +38 -1
  5. package/dist/types/error/classes.d.ts +5 -1
  6. package/dist/types/error/rate-limit.d.ts +5 -4
  7. package/dist/types/providers/anthropic-client.d.ts +13 -1
  8. package/dist/types/providers/anthropic.d.ts +17 -11
  9. package/dist/types/providers/claude-code-fingerprint.d.ts +12 -8
  10. package/dist/types/providers/cowork-fetch.d.ts +3 -0
  11. package/dist/types/providers/cursor/exec-modern.d.ts +98 -0
  12. package/dist/types/providers/cursor-pi-args.d.ts +99 -0
  13. package/dist/types/providers/cursor.d.ts +53 -3
  14. package/dist/types/providers/error-message.d.ts +2 -4
  15. package/dist/types/providers/openai-codex-responses.d.ts +10 -0
  16. package/dist/types/providers/openai-responses-wire.d.ts +8 -0
  17. package/dist/types/providers/openai-shared.d.ts +13 -1
  18. package/dist/types/registry/exa.d.ts +8 -0
  19. package/dist/types/registry/registry.d.ts +7 -1
  20. package/dist/types/registry/xai.d.ts +4 -1
  21. package/dist/types/stream.d.ts +2 -0
  22. package/dist/types/types.d.ts +101 -1
  23. package/dist/types/usage/umans.d.ts +2 -0
  24. package/dist/types/utils/block-symbols.d.ts +11 -0
  25. package/dist/types/utils/harmony-leak.d.ts +15 -7
  26. package/dist/types/utils/proxy.d.ts +2 -0
  27. package/dist/types/utils/schema/normalize.d.ts +1 -0
  28. package/package.json +4 -4
  29. package/src/auth-broker/client.ts +5 -1
  30. package/src/auth-broker/server.ts +103 -9
  31. package/src/auth-broker/snapshot-cache.ts +12 -3
  32. package/src/auth-broker/types.ts +6 -0
  33. package/src/auth-storage.ts +466 -28
  34. package/src/error/classes.ts +98 -3
  35. package/src/error/flags.ts +6 -1
  36. package/src/error/rate-limit.ts +18 -12
  37. package/src/providers/amazon-bedrock.ts +3 -3
  38. package/src/providers/anthropic-client.ts +24 -2
  39. package/src/providers/anthropic.ts +80 -75
  40. package/src/providers/claude-code-fingerprint.ts +12 -11
  41. package/src/providers/cowork-fetch.ts +201 -0
  42. package/src/providers/cursor/exec-modern.ts +495 -0
  43. package/src/providers/cursor/proto/agent.proto +1007 -0
  44. package/src/providers/cursor-pi-args.ts +135 -0
  45. package/src/providers/cursor.ts +1138 -66
  46. package/src/providers/devin.ts +2 -1
  47. package/src/providers/error-message.ts +3 -1
  48. package/src/providers/openai-codex-responses.ts +106 -26
  49. package/src/providers/openai-completions.ts +19 -4
  50. package/src/providers/openai-responses-wire.ts +8 -0
  51. package/src/providers/openai-responses.ts +12 -1
  52. package/src/providers/openai-shared.ts +75 -12
  53. package/src/registry/api-key-validation.ts +18 -34
  54. package/src/registry/exa.ts +19 -0
  55. package/src/registry/novita.ts +6 -3
  56. package/src/registry/registry.ts +3 -1
  57. package/src/registry/xai.ts +17 -1
  58. package/src/stream.ts +13 -6
  59. package/src/types.ts +115 -0
  60. package/src/usage/umans.ts +192 -0
  61. package/src/utils/block-symbols.ts +12 -0
  62. package/src/utils/harmony-leak.ts +32 -0
  63. package/src/utils/idle-iterator.ts +2 -2
  64. package/src/utils/proxy.ts +6 -2
  65. package/src/utils/schema/normalize.ts +140 -38
@@ -3,10 +3,10 @@ import { type JsonValue } from "@bufbuild/protobuf";
3
3
  import type { McpToolDefinition } from "@oh-my-pi/pi-catalog/discovery/cursor-gen/agent_pb";
4
4
  import { type AgentServerMessage, type ConversationStateStructure } from "@oh-my-pi/pi-catalog/discovery/cursor-gen/agent_pb";
5
5
  import type { AssistantMessage, CursorExecHandlerResult, CursorExecHandlers, CursorExecPairing, CursorTodoSyncHandler, CursorToolResultHandler, Message, StreamFunction, StreamOptions, TextContent, ThinkingContent, Tool, ToolCall, ToolResultMessage } from "../types.js";
6
- import { kCursorExecResolved, kStreamingBlockIndex, kStreamingBlockKind, kStreamingLastParseLen, kStreamingPartialJson } from "../utils/block-symbols.js";
6
+ import { kCursorExecResolved, kStreamingBlockIndex, kStreamingBlockKind, kStreamingEnvelopeId, kStreamingLastParseLen, kStreamingPartialJson } from "../utils/block-symbols.js";
7
7
  import { AssistantMessageEventStream } from "../utils/event-stream.js";
8
8
  export declare const CURSOR_API_URL = "https://api2.cursor.sh";
9
- export declare const CURSOR_CLIENT_VERSION = "cli-2026.01.09-231024f";
9
+ export declare const CURSOR_CLIENT_VERSION = "cli-2026.07.23-e383d2b";
10
10
  export interface CursorOptions extends StreamOptions {
11
11
  customSystemPrompt?: string;
12
12
  conversationId?: string;
@@ -32,7 +32,8 @@ export type ToolCallState = ToolCall & {
32
32
  [kStreamingBlockIndex]: number;
33
33
  [kStreamingPartialJson]?: string;
34
34
  [kStreamingLastParseLen]?: number;
35
- [kStreamingBlockKind]: "mcp" | "todo" | "cursor-exec";
35
+ [kStreamingBlockKind]: "mcp" | "todo" | "cursor-exec" | "connect-scm";
36
+ [kStreamingEnvelopeId]?: string;
36
37
  [kCursorExecResolved]?: true;
37
38
  };
38
39
  export interface BlockState {
@@ -43,6 +44,17 @@ export interface BlockState {
43
44
  [kStreamingBlockIndex]: number;
44
45
  }) | null;
45
46
  currentToolCall: ToolCallState | null;
47
+ /**
48
+ * Open streamed tool-call blocks, keyed by the interaction envelope's
49
+ * `call_id`.
50
+ *
51
+ * Cursor interleaves calls: two `toolCallStarted` frames can arrive before
52
+ * either completes. A single "current" slot would let the second overwrite
53
+ * the first, orphaning a block that nothing then settles. Every keyed block
54
+ * stays reachable until its own completion, and `currentToolCall` remains
55
+ * only as the fallback for frames that carry no `call_id`.
56
+ */
57
+ openToolCalls: Map<string, ToolCallState>;
46
58
  /** MCP call IDs synthesized from exec frames before their redundant streamed block arrives. */
47
59
  resolvedMcpToolCallIds: Set<string>;
48
60
  firstTokenTime: number | undefined;
@@ -102,6 +114,44 @@ export declare function resolveExecHandler<TArgs, TResult>(args: TArgs, handler:
102
114
  * specific recovery hint.
103
115
  */
104
116
  export declare function emptyGrepPatternRejection(pattern: string | undefined, glob: string | undefined): string | null;
117
+ /**
118
+ * Retain a freshly opened streamed tool-call block.
119
+ *
120
+ * Keyed by the interaction envelope's `call_id`, which is the only key every
121
+ * `ToolCall*Update` for that call shares. The block's own `id` is deliberately
122
+ * not the key: MCP, Pi and connect-SCM blocks are filed under the id carried
123
+ * inside the call's `args`, because that is what the exec channel pairs its
124
+ * result under and what the transcript files the visible block under.
125
+ *
126
+ * `currentToolCall` is still set, as the fallback for frames that carry no
127
+ * `call_id` (proto3-optional, and unset on what older builds send).
128
+ */
129
+ /**
130
+ * Close every tool-call block still open when the stream ends.
131
+ *
132
+ * Not just the last one started: with interleaved calls several can be open at
133
+ * once, and an unclosed block leaves its live card animating and its call
134
+ * unpaired.
135
+ *
136
+ * Only blocks fed by a streamed argument buffer get reparsed. Todo,
137
+ * connect-SCM and MCP-settled frames arrive with complete `arguments` and
138
+ * never set the partial buffer; `parseStreamingJson(undefined)` returns `{}`,
139
+ * so reparsing unconditionally would erase the arguments of every such block
140
+ * caught open by a truncated stream.
141
+ *
142
+ * Server-owned blocks are also paired here. `connect-scm` and `todo` are
143
+ * stamped {@link kCursorExecResolved} the moment they open, so `agent-loop.ts`
144
+ * synthesizes no placeholder for them and only their `toolCallCompleted` frame
145
+ * pairs a result. A transport that closes before that frame would leave the
146
+ * call unpaired, and `buildSessionContext` strips a dangling call from every
147
+ * rebuilt transcript — the interaction disappears. An interrupted result is
148
+ * emitted instead.
149
+ *
150
+ * MCP blocks are excluded even when resolved: the exec dispatch that marked
151
+ * them owns their result, and `drainInFlightDispatches` awaits it before this
152
+ * runs, so pairing here would duplicate one against the same `toolCallId`.
153
+ */
154
+ export declare function flushOpenToolCalls(output: AssistantMessage, stream: AssistantMessageEventStream, state: BlockState): void;
105
155
  /**
106
156
  * Merge the decoded completion-frame `McpArgs` map into the args assembled
107
157
  * from streamed `args_text_delta` snapshots.
@@ -1,10 +1,8 @@
1
1
  import type { Api, Model } from "../types.js";
2
2
  export declare function createProviderErrorMessage(model: Model<Api>, err: unknown): {
3
3
  role: "assistant";
4
- content: {
5
- type: "text";
6
- text: string;
7
- }[];
4
+ content: never[];
5
+ errorMessage: string;
8
6
  api: Api;
9
7
  provider: string;
10
8
  model: string;
@@ -27,6 +27,14 @@ export interface OpenAICodexResponsesOptions extends StreamOptions {
27
27
  * keys are ignored; extras are never emitted as top-level metadata fields.
28
28
  */
29
29
  clientMetadata?: Record<string, string>;
30
+ /**
31
+ * Turn id of the initiating (parent) Codex turn for nested requests such as
32
+ * subagent spawns (codex-rs `parent_turn_id`, #35835). Emitted both as the
33
+ * flat `client_metadata.parent_turn_id` key and inside the
34
+ * `x-codex-turn-metadata` JSON blob; blank values are ignored. The key is
35
+ * reserved: `clientMetadata` extras cannot supply it.
36
+ */
37
+ parentTurnId?: string;
30
38
  /**
31
39
  * Invoked when the server streams a `response.metadata` event carrying
32
40
  * ChatGPT moderation metadata (`metadata.openai_chatgpt_moderation_metadata`)
@@ -44,6 +52,8 @@ export interface OpenAICodexCompatibilityMetadataOptions {
44
52
  startNewTurn?: boolean;
45
53
  turnStartedAtUnixMs?: number;
46
54
  clientMetadata?: Readonly<Record<string, string>>;
55
+ /** Parent Codex turn id for nested requests; see {@link OpenAICodexResponsesOptions.parentTurnId}. */
56
+ parentTurnId?: string;
47
57
  /** Add the direct installation header required by `/responses/compact`. */
48
58
  includeInstallationHeader?: boolean;
49
59
  }
@@ -2453,6 +2453,14 @@ export interface ResponseFunctionToolCall {
2453
2453
  * The namespace of the function to run.
2454
2454
  */
2455
2455
  namespace?: string;
2456
+ /**
2457
+ * Plaintext-argument marker for collaboration tool calls (codex-rs
2458
+ * `encrypted_function_args`, #35845): an empty array marks the arguments as
2459
+ * plaintext agent-message payloads rather than encrypted blobs. Preserved
2460
+ * verbatim across history replay so the backend keeps treating them as
2461
+ * plaintext; omitted entirely for ordinary function calls.
2462
+ */
2463
+ encrypted_function_args?: string[];
2456
2464
  /**
2457
2465
  * The status of the item. One of `in_progress`, `completed`, or `incomplete`.
2458
2466
  * Populated when items are returned via API.
@@ -397,7 +397,7 @@ export declare function repairOrphanResponsesToolOutputs(input: ResponseInput):
397
397
  * {@link repairOrphanResponsesToolOutputs}.
398
398
  */
399
399
  export declare function repairOrphanResponsesToolCalls(input: ResponseInput): ResponseInput;
400
- export declare function convertResponsesInputContent(content: string | Array<TextContent | ImageContent>, supportsImages: boolean, supportsImageDetailOriginal: boolean): ResponseInputContent[] | undefined;
400
+ export declare function convertResponsesInputContent(content: string | Array<TextContent | ImageContent>, supportsImages: boolean, supportsImageDetailOriginal: boolean, escapeControlTokens?: boolean): ResponseInputContent[] | undefined;
401
401
  export interface BuildResponsesInputOptions<TApi extends Api> {
402
402
  model: Model<TApi>;
403
403
  context: Context;
@@ -414,6 +414,18 @@ export interface BuildResponsesInputOptions<TApi extends Api> {
414
414
  /** Preserve assistant message item IDs from text signatures during fallback replay. */
415
415
  preserveAssistantMessageIds?: boolean;
416
416
  }
417
+ /**
418
+ * Escape reserved Harmony control tokens in the client-boundary text of
419
+ * replayed Responses input items — user/developer/system message text and
420
+ * tool-result output. Model-owned items (assistant output, reasoning, tool-call
421
+ * arguments) carry no client data and are returned untouched.
422
+ *
423
+ * Native history replay pushes stored `providerPayload` items straight onto the
424
+ * wire, bypassing {@link convertResponsesInputContent}; without this a stored
425
+ * `input_text` carrying `<|channel|>analysis` still reaches gpt-5.x raw (#6913).
426
+ * Callers gate on {@link isHarmonyDialectModel}. Items are copied, not mutated.
427
+ */
428
+ export declare function escapeReplayedClientText(items: ResponseInput): ResponseInput;
417
429
  export declare function buildResponsesInput<TApi extends Api>(options: BuildResponsesInputOptions<TApi>): ResponseInput;
418
430
  export declare function convertResponsesAssistantMessage<TApi extends Api>(assistantMsg: AssistantMessage, model: Model<TApi>, msgIndex: number, knownCallIds: Set<string>, includeThinkingSignatures?: boolean, customCallIds?: Set<string>, preserveMessageIds?: boolean, supportsCustomToolCalls?: boolean, customToolWireNameMap?: ReadonlyMap<string, string>, computerCallIds?: Set<string>): ResponseInput;
419
431
  /** Appends one tool result while keeping consecutive outputs ahead of its synthetic image messages. */
@@ -0,0 +1,8 @@
1
+ import type { OAuthLoginCallbacks } from "./oauth/types.js";
2
+ export declare const loginExa: (options: import("./oauth/index.js").OAuthController) => Promise<string>;
3
+ export declare const exaProvider: {
4
+ readonly id: "exa";
5
+ readonly name: "Exa";
6
+ readonly envKeys: "EXA_API_KEY";
7
+ readonly login: (cb: OAuthLoginCallbacks) => Promise<string>;
8
+ };
@@ -64,6 +64,11 @@ declare const ALL: ({
64
64
  readonly login: (cb: import("./oauth/index.js").OAuthLoginCallbacks) => Promise<string>;
65
65
  readonly callbackPort: 59653;
66
66
  readonly pasteCodeFlow: true;
67
+ } | {
68
+ readonly id: "exa";
69
+ readonly name: "Exa";
70
+ readonly envKeys: "EXA_API_KEY";
71
+ readonly login: (cb: import("./oauth/index.js").OAuthLoginCallbacks) => Promise<string>;
67
72
  } | {
68
73
  readonly id: "firepass";
69
74
  readonly name: "Fire Pass (Fireworks Kimi K2.6 Turbo subscription)";
@@ -282,7 +287,8 @@ declare const ALL: ({
282
287
  readonly login: (cb: import("./oauth/index.js").OAuthLoginCallbacks) => Promise<string>;
283
288
  } | {
284
289
  readonly id: "xai";
285
- readonly name: "xAI";
290
+ readonly name: "xAI API";
291
+ readonly login: (cb: import("./oauth/index.js").OAuthLoginCallbacks) => Promise<string>;
286
292
  } | {
287
293
  readonly id: "xai-oauth";
288
294
  readonly name: "xAI Grok OAuth (SuperGrok or X Premium+)";
@@ -1,4 +1,7 @@
1
+ import type { OAuthLoginCallbacks } from "./oauth/types.js";
2
+ export declare const loginXAI: (options: import("./oauth/index.js").OAuthController) => Promise<string>;
1
3
  export declare const xaiProvider: {
2
4
  readonly id: "xai";
3
- readonly name: "xAI";
5
+ readonly name: "xAI API";
6
+ readonly login: (cb: OAuthLoginCallbacks) => Promise<string>;
4
7
  };
@@ -5,6 +5,8 @@ import type { GoogleGeminiCliOptions } from "./providers/google-gemini-cli.js";
5
5
  import type { GoogleVertexOptions } from "./providers/google-vertex.js";
6
6
  import type { Api, AssistantMessage, Context, Model, OptionsForApi, SimpleStreamOptions, ToolChoice } from "./types.js";
7
7
  import { AssistantMessageEventStream } from "./utils/event-stream.js";
8
+ /** Strict official-Codex endpoint check; exact origin or a path boundary after {@link CODEX_BASE_URL}. */
9
+ export declare function isOfficialCodexApiUrl(baseUrl: string | undefined): boolean;
8
10
  export declare function configureProviderMaxInFlightRequests(limits: Record<string, number> | undefined): void;
9
11
  export declare const __providerInFlightForTesting: {
10
12
  setRoot(root: string | undefined): void;
@@ -1,6 +1,6 @@
1
1
  export * from "@oh-my-pi/pi-catalog/effort";
2
2
  export * from "@oh-my-pi/pi-catalog/types";
3
- import type { DeleteArgs, DeleteResult, DiagnosticsArgs, DiagnosticsResult, GrepArgs, GrepResult, LsArgs, LsResult, McpResult, ReadArgs, ReadResult, ShellArgs, ShellResult, WriteArgs, WriteResult } from "@oh-my-pi/pi-catalog/discovery/cursor-gen/agent_pb";
3
+ import type { DeleteArgs, DeleteResult, DiagnosticsArgs, DiagnosticsResult, GrepArgs, GrepResult, LsArgs, LsResult, McpResult, PiBashExecArgs, PiBashExecResult, PiEditExecArgs, PiEditExecResult, PiFindExecArgs, PiFindExecResult, PiGrepExecArgs, PiGrepExecResult, PiLsExecArgs, PiLsExecResult, PiReadExecArgs, PiReadExecResult, PiWriteExecArgs, PiWriteExecResult, ReadArgs, ReadResult, ShellArgs, ShellResult, WriteArgs, WriteResult } from "@oh-my-pi/pi-catalog/discovery/cursor-gen/agent_pb";
4
4
  import type { Effort } from "@oh-my-pi/pi-catalog/effort";
5
5
  import type { Api, FetchImpl, Model, Provider, ThinkingBudgets, Usage } from "@oh-my-pi/pi-catalog/types";
6
6
  import type { Type } from "arktype";
@@ -754,6 +754,14 @@ export interface CursorMcpCall {
754
754
  toolCallId: string;
755
755
  args: Record<string, unknown>;
756
756
  rawArgs: Record<string, Uint8Array>;
757
+ /**
758
+ * The frame asks only whether this call would be permitted — it must not
759
+ * run. The server sends it to resolve a smart-mode approval decision ahead
760
+ * of the real invocation, and answers with the dedicated `approved`
761
+ * variant, so executing here would fire a side-effecting tool the user has
762
+ * not yet been asked about (and fire it twice once the real call arrives).
763
+ */
764
+ approvalOnly?: boolean;
757
765
  }
758
766
  export interface CursorTodoSnapshotItem {
759
767
  content: string;
@@ -796,6 +804,51 @@ export interface CursorShellStreamCallbacks {
796
804
  onStdout(data: string): void;
797
805
  onStderr(data: string): void;
798
806
  }
807
+ /**
808
+ * A modern Pi exec frame plus the call id the dispatcher minted for it.
809
+ *
810
+ * Unlike the legacy exec args (`ReadArgs`, `ShellArgs`, ...), the Pi frames
811
+ * carry no `tool_call_id` field: on modern builds the id rides the streamed
812
+ * `ToolCall` envelope (`ToolCall.tool_call_id = 57`) instead of each variant's
813
+ * args. The exec channel has no access to that envelope, so the dispatcher
814
+ * mints an id and hands it to the handler, keeping the synthesized transcript
815
+ * block and its paired `toolResult` on the same key.
816
+ */
817
+ export interface CursorPiCall<TArgs> {
818
+ args: TArgs;
819
+ toolCallId: string;
820
+ }
821
+ /** One resource a host's MCP servers advertise. */
822
+ export interface CursorMcpResource {
823
+ uri: string;
824
+ name?: string;
825
+ description?: string;
826
+ mimeType?: string;
827
+ /** The server advertising it; Cursor addresses reads by this name. */
828
+ server: string;
829
+ }
830
+ /**
831
+ * The content of one resource read.
832
+ *
833
+ * `text` and `blob` are the wire's content oneof: exactly one is sent, with
834
+ * `text` winning when a host supplies both. A download instead sets
835
+ * `downloadPath` and no content at all — the model is told where the file
836
+ * landed rather than being handed its bytes.
837
+ */
838
+ export interface CursorMcpResourceContent {
839
+ uri: string;
840
+ name?: string;
841
+ description?: string;
842
+ mimeType?: string;
843
+ text?: string;
844
+ blob?: Uint8Array;
845
+ /**
846
+ * Where the host wrote the resource, workspace-relative, when the frame
847
+ * asked for a download. Set this INSTEAD of `text`/`blob`: the wire
848
+ * contract is that a download returns no content to the model.
849
+ */
850
+ downloadPath?: string;
851
+ }
799
852
  export interface CursorExecHandlers {
800
853
  read?: (args: ReadArgs) => Promise<CursorExecHandlerResult<ReadResult>>;
801
854
  ls?: (args: LsArgs) => Promise<CursorExecHandlerResult<LsResult>>;
@@ -806,6 +859,53 @@ export interface CursorExecHandlers {
806
859
  shellStream?: (args: ShellArgs, callbacks: CursorShellStreamCallbacks) => Promise<CursorExecHandlerResult<ShellResult>>;
807
860
  diagnostics?: (args: DiagnosticsArgs) => Promise<CursorExecHandlerResult<DiagnosticsResult>>;
808
861
  mcp?: (call: CursorMcpCall) => Promise<CursorExecHandlerResult<McpResult>>;
862
+ /**
863
+ * Answers "would this MCP call be permitted", without running it.
864
+ *
865
+ * A modern `mcpArgs` frame carrying `smart_mode_approval_only` asks for the
866
+ * permission decision alone, ahead of the real invocation. Executing the
867
+ * tool to answer it would fire a side effect the user never approved — and
868
+ * fire it twice once the real call arrives.
869
+ *
870
+ * `true` only when the host's policy resolves to a definite allow. A pending
871
+ * prompt is `false`: it can only be answered interactively at execution
872
+ * time, and there is no "ask me later" reply in this frame's result. When no
873
+ * handler is registered the provider refuses, since it cannot decide.
874
+ */
875
+ mcpApprovalPreflight?: (call: CursorMcpCall) => Promise<boolean>;
876
+ /**
877
+ * Modern Cursor CLI Pi tool frames (`ExecServerMessage` 45-51). They are a
878
+ * distinct frame family from the legacy `readArgs`/`shellArgs`/... set, not
879
+ * an alias: different args, different result oneofs, and no `tool_call_id`.
880
+ */
881
+ piRead?: (call: CursorPiCall<PiReadExecArgs>) => Promise<CursorExecHandlerResult<PiReadExecResult>>;
882
+ piBash?: (call: CursorPiCall<PiBashExecArgs>) => Promise<CursorExecHandlerResult<PiBashExecResult>>;
883
+ piEdit?: (call: CursorPiCall<PiEditExecArgs>) => Promise<CursorExecHandlerResult<PiEditExecResult>>;
884
+ piWrite?: (call: CursorPiCall<PiWriteExecArgs>) => Promise<CursorExecHandlerResult<PiWriteExecResult>>;
885
+ piGrep?: (call: CursorPiCall<PiGrepExecArgs>) => Promise<CursorExecHandlerResult<PiGrepExecResult>>;
886
+ piFind?: (call: CursorPiCall<PiFindExecArgs>) => Promise<CursorExecHandlerResult<PiFindExecResult>>;
887
+ piLs?: (call: CursorPiCall<PiLsExecArgs>) => Promise<CursorExecHandlerResult<PiLsExecResult>>;
888
+ /**
889
+ * The resources the host's MCP servers advertise, optionally filtered to one
890
+ * server. Without a handler the provider answers an empty catalog, which
891
+ * hides resources a host is in fact holding live connections to.
892
+ */
893
+ listMcpResources?: (args: {
894
+ server?: string;
895
+ }) => Promise<CursorMcpResource[]>;
896
+ /**
897
+ * Read one resource. `null` means the server or uri is genuinely unknown,
898
+ * which the provider answers as `not_found`; throwing surfaces as `error`.
899
+ */
900
+ readMcpResource?: (args: {
901
+ server: string;
902
+ uri: string;
903
+ /**
904
+ * When set, write the resource here (workspace-relative) and return
905
+ * `downloadPath` instead of content.
906
+ */
907
+ downloadPath?: string;
908
+ }) => Promise<CursorMcpResourceContent | null>;
809
909
  /** Mirror Cursor's server-owned todo list into local session state. */
810
910
  todoSync?: CursorTodoSyncHandler;
811
911
  onToolResult?: CursorToolResultHandler;
@@ -0,0 +1,2 @@
1
+ import type { UsageProvider } from "../usage.js";
2
+ export declare const umansUsageProvider: UsageProvider;
@@ -14,6 +14,17 @@ export declare function clearStreamingPartialJson(block: StreamingPartialJsonCar
14
14
  export declare const kStreamingBlockIndex: unique symbol;
15
15
  /** Stores the last parsed argument prefix length for throttled streaming JSON parsing. */
16
16
  export declare const kStreamingLastParseLen: unique symbol;
17
+ /**
18
+ * The Cursor interaction envelope's `call_id` for a streamed tool-call block.
19
+ *
20
+ * Tracked separately from the block's own `id` because they are NOT the same
21
+ * key: MCP and Pi blocks are filed under the id inside the call's `args`, which
22
+ * is what the exec channel pairs its result under, while every streamed
23
+ * `ToolCall*Update` correlates on the envelope's `call_id`. Matching
24
+ * completions against the block id would mis-route every call whose args carry
25
+ * their own id.
26
+ */
27
+ export declare const kStreamingEnvelopeId: unique symbol;
17
28
  /** Marks streamed tool-call arguments that already received an authoritative done payload. */
18
29
  export declare const kStreamingArgumentsDone: unique symbol;
19
30
  /** Classifies Cursor's in-flight tool-call kind without leaking provider-private state. */
@@ -1,13 +1,21 @@
1
+ import type { AssistantMessage, Model, ToolCall } from "../types.js";
1
2
  /**
2
- * GPT-5 Harmony-header leakage detection and recovery.
3
+ * Escape reserved Harmony control tokens in arbitrary text so it can be
4
+ * transported as data to a harmony-dialect model. Returns the input unchanged
5
+ * when it carries no reserved spelling.
3
6
  *
4
- * Background and policy: see `docs/ERRATA-GPT5-HARMONY.md`. This module
5
- * implements §3 of that document: detection by signal fusion, plus a
6
- * truncate-and-resume primitive for the `edit` tool when its input is in
7
- * hashline DSL form. Other tools and surfaces fall through to
8
- * abort-and-retry handled by the agent loop.
7
+ * Callers MUST gate on a harmony target and escape only the transport copy —
8
+ * the persisted transcript keeps the byte-for-byte original.
9
9
  */
10
- import type { AssistantMessage, Model, ToolCall } from "../types.js";
10
+ export declare function escapeHarmonyControlTokens(text: string): string;
11
+ /**
12
+ * Whether requests to `model` are served by a Harmony-dialect backend
13
+ * (gpt-5.x / gpt-oss), which rejects reserved control-token spellings appearing
14
+ * as data in the request. Resolves the wire model id (`requestModelId ?? id`)
15
+ * so deployment/catalog aliases — e.g. an Azure alias whose `requestModelId` is
16
+ * `gpt-5.4` — are detected even when the local id is opaque.
17
+ */
18
+ export declare function isHarmonyDialectModel(model: Model): boolean;
11
19
  declare const SIGNAL_ORDER: readonly ["M", "C", "G", "S", "B", "R", "T"];
12
20
  export type HarmonySignalClass = "H" | (typeof SIGNAL_ORDER)[number];
13
21
  export type HarmonySurface = "assistant_text" | "assistant_thinking" | "tool_arg";
@@ -29,6 +29,8 @@ export interface ConnectProxiedSocketOptions {
29
29
  signal?: AbortSignal;
30
30
  /** Maximum wall-clock time to establish the final TLS tunnel. Disabled when absent or non-positive. */
31
31
  timeoutMs?: number;
32
+ /** Target TLS profile. Cursor defaults to HTTP/2 when this is absent. */
33
+ tls?: tls.ConnectionOptions;
32
34
  }
33
35
  /**
34
36
  * Tunnel a socket connection through an HTTP CONNECT proxy.
@@ -26,6 +26,7 @@ export interface NormalizeSchemaOptions {
26
26
  inferTypeForBareEnum: boolean;
27
27
  foldOneOfIntoAnyOf: boolean;
28
28
  dropNonScalarEnum: boolean;
29
+ stringEnumsOnly?: boolean;
29
30
  rejectResidualIncompatibilities?: ReadonlyArray<ResidualSchemaIncompatibility>;
30
31
  validateAndFallback?: {
31
32
  fallback: unknown;
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.8",
4
+ "version": "17.2.1",
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.8",
42
- "@oh-my-pi/pi-utils": "17.1.8",
43
- "@oh-my-pi/pi-wire": "17.1.8",
41
+ "@oh-my-pi/pi-catalog": "17.2.1",
42
+ "@oh-my-pi/pi-utils": "17.2.1",
43
+ "@oh-my-pi/pi-wire": "17.2.1",
44
44
  "arktype": "2.2.3",
45
45
  "zod": "^4"
46
46
  },
@@ -28,6 +28,7 @@ import type {
28
28
  UsageResponse,
29
29
  UsageStaleResponse,
30
30
  } from "./types";
31
+ import { AUTH_BROKER_CAPABILITIES_HEADER, AUTH_BROKER_CAPABILITY_CODEX_METER_BLOCK_SCOPES } from "./types";
31
32
  import { getAuthBrokerWireSchemas } from "./wire-schema-resource";
32
33
 
33
34
  type AuthBrokerResponseSchemaName =
@@ -135,7 +136,9 @@ export class AuthBrokerClient {
135
136
  const query = new URLSearchParams();
136
137
  if (opts.waitMs !== undefined) query.set("wait", String(opts.waitMs));
137
138
  const path = `/v1/snapshot${query.size > 0 ? `?${query.toString()}` : ""}`;
138
- const headers: Record<string, string> = {};
139
+ const headers: Record<string, string> = {
140
+ [AUTH_BROKER_CAPABILITIES_HEADER]: AUTH_BROKER_CAPABILITY_CODEX_METER_BLOCK_SCOPES,
141
+ };
139
142
  if (opts.ifGenerationGt !== undefined) headers["If-None-Match"] = `"${opts.ifGenerationGt}"`;
140
143
  const timeoutMs =
141
144
  opts.waitMs !== undefined && opts.waitMs > 0 ? Math.max(this.#timeoutMs, opts.waitMs + 1000) : undefined;
@@ -176,6 +179,7 @@ export class AuthBrokerClient {
176
179
  const headers: Record<string, string> = {
177
180
  Accept: "text/event-stream",
178
181
  Authorization: `Bearer ${this.#token}`,
182
+ [AUTH_BROKER_CAPABILITIES_HEADER]: AUTH_BROKER_CAPABILITY_CODEX_METER_BLOCK_SCOPES,
179
183
  };
180
184
  if (opts.signal?.aborted) {
181
185
  throw new AuthBrokerError("Auth broker request aborted", { cause: opts.signal.reason });