@oh-my-pi/pi-ai 17.3.7 → 17.4.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 +35 -0
- package/README.md +1 -1
- package/dist/types/auth/sqlite-credential-store.d.ts +2 -15
- package/dist/types/auth-storage.d.ts +4 -2
- package/dist/types/dialect/thinking-fence-strip.d.ts +39 -0
- package/dist/types/error/finalize.d.ts +2 -0
- package/dist/types/error/flags.d.ts +9 -0
- package/dist/types/providers/cursor/exec-modern.d.ts +1 -1
- package/dist/types/providers/cursor/interaction-query.d.ts +15 -0
- package/dist/types/providers/cursor-pi-args.d.ts +1 -1
- package/dist/types/providers/cursor.d.ts +14 -5
- package/dist/types/providers/openai-reasoning-fallback.d.ts +10 -2
- package/dist/types/providers/openai-shared.d.ts +32 -0
- package/dist/types/registry/oauth/opencode.d.ts +16 -9
- package/dist/types/types.d.ts +8 -1
- package/dist/types/usage.d.ts +10 -0
- package/dist/types/utils/schema/json-schema-validator.d.ts +5 -5
- package/package.json +5 -7
- package/src/auth/sqlite-credential-store.ts +11 -24
- package/src/auth-gateway/server.ts +29 -3
- package/src/auth-storage.ts +62 -10
- package/src/dialect/thinking-fence-strip.ts +92 -0
- package/src/error/finalize.ts +4 -0
- package/src/error/flags.ts +77 -4
- package/src/oneshot-retry.ts +4 -0
- package/src/providers/amazon-bedrock.ts +3 -2
- package/src/providers/anthropic.ts +6 -0
- package/src/providers/cursor/exec-modern.ts +2 -2
- package/src/providers/cursor/interaction-query.ts +212 -0
- package/src/providers/cursor/proto/agent.proto +25 -3
- package/src/providers/cursor-pi-args.ts +1 -1
- package/src/providers/cursor.ts +124 -27
- package/src/providers/devin.ts +10 -16
- package/src/providers/google-shared.ts +24 -7
- package/src/providers/openai-codex-responses.ts +2 -0
- package/src/providers/openai-completions.ts +8 -7
- package/src/providers/openai-reasoning-fallback.ts +86 -4
- package/src/providers/openai-responses.ts +5 -0
- package/src/providers/openai-shared.ts +120 -3
- package/src/registry/oauth/callback-server.ts +31 -0
- package/src/registry/oauth/opencode.ts +27 -13
- package/src/registry/oauth/perplexity.ts +6 -3
- package/src/registry/opencode-go.ts +1 -1
- package/src/registry/opencode-zen.ts +1 -1
- package/src/stream.ts +35 -10
- package/src/types.ts +8 -1
- package/src/usage/claude.ts +9 -2
- package/src/usage.ts +10 -0
- package/src/utils/openai-http.ts +30 -0
- package/src/utils/schema/json-schema-validator.ts +54 -17
- package/src/utils/validation.ts +212 -5
- package/src/providers/cursor/proto/buf.gen.yaml +0 -6
- package/src/providers/cursor/proto/buf.yaml +0 -17
- package/src/providers/devin/proto/buf.gen.yaml +0 -33
- package/src/providers/devin/proto/buf.yaml +0 -17
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,41 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [17.4.0] - 2026-08-20
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Added model metadata fields (`context_length`, `max_output_tokens`, `input_modalities`, etc.) to auth gateway model listing responses
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- Fixed tool-argument repair applying lossy transformations (such as stringifying objects or stripping unrecognized keys) when validating union schemas (`anyOf`/`oneOf`), preventing corrupted tool call and subagent payloads
|
|
14
|
+
- Fixed 400 errors when communicating with local OpenAI-compatible inference servers that reject `chat_template_kwargs.reasoning_effort` by improving reasoning effort parameter fallback and compatibility handling
|
|
15
|
+
- Fixed DeepSeek-family models on hosts like Fireworks losing reasoning whenever tools were offered: a redundant `tool_choice: "auto"` is now omitted so the provider keeps thinking enabled; forced and `"none"` selectors still take priority ([#1207](https://github.com/can1357/oh-my-pi/issues/1207))
|
|
16
|
+
|
|
17
|
+
## [17.3.8] - 2026-08-19
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- Fixed Gemini thought summaries occasionally leaking a raw `` ```thinking `` / `` ``````thinking `` fence delimiter into the reasoning block, so it no longer shows up as fence spam in the thinking display or persisted transcripts ([#8719](https://github.com/can1357/oh-my-pi/issues/8719)).
|
|
22
|
+
- Fixed the OpenCode Go login prompting for an "OpenCode Zen API key": the shared login flow now names the provider you selected, so connecting OpenCode Go asks for an OpenCode Go key (the `opencode.ai/auth` console is still shared, as documented upstream) ([#8738](https://github.com/can1357/oh-my-pi/issues/8738)).
|
|
23
|
+
- Fixed Anthropic-compatible endpoints with strict prompt validation (e.g. Z.AI GLM `api.z.ai/api/anthropic`, which rejects the whole request with `400 code 1213 "The prompt parameter was not received normally"`) failing sessions once a tool returned empty output on a vision-capable model: empty successful `tool_result` blocks now encode as `content: ""` instead of `content: []`, which both the official API and strict compatible endpoints accept.
|
|
24
|
+
- Fixed `retry.usageReservePct` (Reserve Margin) ignoring Claude Fable/Mythos weekly tier usage until it hit 100%, so a Fable model kept serving turns past the configured reserve; reserve health now honors the mapped tier row while credential-wide hard blocks still require confirmed exhaustion ([#8773](https://github.com/can1357/oh-my-pi/issues/8773)).
|
|
25
|
+
- Fixed `cursor-agent` streams stalling with "Provider stream stalled while waiting for the next event" when Cursor asked the client to approve a hosted WebFetch / web search (reproduced on `cursor-grok-4.6-xhigh` after "I'll fetch the page…"). Those `interaction_query` frames — including the newer WebFetch field 9 this proto did not name — were dropped, so the server waited forever and the idle watchdog aborted a live connection. Permission queries are now answered; hosted search/fetch is approved, unnamed permission fields get an `approved` reply on the same field number, and prompts this client cannot serve are rejected so the turn can continue.
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
|
|
29
|
+
- Fixed thinking effort selections being ignored for local Qwen 3.8+ models on llama.cpp and vLLM: the Qwen chat-completions dialects only toggled `enable_thinking`, so the chat template always reasoned at its `xhigh` default no matter which level was selected. The encoder now routes the requested effort onto the template's `reasoning_effort` kwarg (`chat_template_kwargs` for both Qwen dialects, plus the top-level field newer llama.cpp builds map natively).
|
|
30
|
+
- Fixed OpenAI Completions, Amazon Bedrock, and Cursor providers ignoring `onPayload` replacement payloads. The hook now transforms the actual request body sent upstream on these providers, matching the Anthropic/Gemini/OpenAI Responses replacement contract. `devin-agent` still does not fire the hook (its payload is a protobuf object).
|
|
31
|
+
- Fixed Codex requests failing outright when the signed-in ChatGPT account is not entitled to the requested model; the exact model denial is now classified as an account-policy error so credential rotation can reach an entitled sibling account
|
|
32
|
+
- Fixed Perplexity email-OTP login after its verification response renamed the encrypted session token from `token` to `challenge_token`.
|
|
33
|
+
- Cloud Code Assist Gemini 3.6/3.7 Flash requests at `minimal` now send `thinkingLevel: LOW` on the aliased `-low` SKU instead of `MINIMAL`, which the API rejects with HTTP 400.
|
|
34
|
+
- Answer Cursor `interaction_query` permission gates (hosted web search, Exa, unnamed field-9 WebFetch) so the Run RPC continues instead of sitting silent until the 300s idle watchdog.
|
|
35
|
+
- Fixed provider tool calls arriving with flattened array argument paths (e.g. Gemini's `questions[0].id`) being stripped and rejected by argument validation; well-formed flattened paths are now rebuilt into the nested arrays the tool schema expects ([#8886](https://github.com/can1357/oh-my-pi/issues/8886)).
|
|
36
|
+
- Fixed opencode-go (Console Go) rejecting Responses turns with `400 No tool output found for tool call …` (naming a random call of the batch on each retry) when a model streamed a trailing text/thinking block after its tool calls: `buildResponsesInput` emitted that block as an assistant `message` item wedged between the `function_call` batch and its `function_call_output` items. Such interleaved messages are now hoisted ahead of their call batch (canonical `message(s) → calls → outputs`), which the strict gateway validator accepts; content is unchanged ([#8789](https://github.com/can1357/oh-my-pi/issues/8789)).
|
|
37
|
+
- Fixed the OpenAI-wire transport sleeping on a LiteLLM concurrency-admission 429 (`rate_limit_type: max_parallel_requests`, `Retry-After: 60`) and retrying it up to 6 times (~300s) before session recovery saw the error. Because a 60s hint equals the transport's `maxDelayMs` cap, `fetchWithRetry` kept sleeping and retrying; the request now surfaces on the first attempt so `TurnRecovery`'s concurrency backoff/model fallback runs promptly. Genuine RPM/quota 429s (no such marker) still honor `Retry-After` ([#8854](https://github.com/can1357/oh-my-pi/issues/8854)).
|
|
38
|
+
- Fixed OAuth login (Codex `localhost:1455`, and any `localhost` callback flow) failing on hosts with IPv6 disabled at the kernel (`ipv6.disable=1`). The `::1` companion listener added in #8081 fails there with Bun's generic "Is port X in use?" message (oven-sh/bun#7187), which the in-use check misread as a real collision — tearing down the healthy IPv4 listener and surfacing a bogus "port 1455 is in use" error. The dual-bind path now detects the missing IPv6 loopback up front and serves IPv4 alone ([#8814](https://github.com/can1357/oh-my-pi/issues/8814)).
|
|
39
|
+
|
|
5
40
|
## [17.3.7] - 2026-08-17
|
|
6
41
|
|
|
7
42
|
### Changed
|
package/README.md
CHANGED
|
@@ -634,7 +634,7 @@ All providers accept the base `StreamOptions` (in addition to provider-specific
|
|
|
634
634
|
- `headers`: Extra request headers merged on top of model-defined headers
|
|
635
635
|
- `sessionId`: Provider-specific session identifier (prompt caching/routing)
|
|
636
636
|
- `signal`: Abort in-flight requests
|
|
637
|
-
- `onPayload`: Callback invoked with the provider request payload just before sending
|
|
637
|
+
- `onPayload`: Callback invoked with the provider request payload just before sending. Return a replacement payload object (sync or async) to send it instead of the original; return `undefined` to keep the original. The replacement is applied by every provider that fires the hook — all of them except `devin-agent`, whose payload is a protobuf object and does not fire the hook yet.
|
|
638
638
|
|
|
639
639
|
Example:
|
|
640
640
|
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* and remote stores share the same contract.
|
|
6
6
|
*/
|
|
7
7
|
import { Database } from "bun:sqlite";
|
|
8
|
+
import { isSqliteBusyError, isSqliteCorruptionError } from "@oh-my-pi/pi-utils";
|
|
8
9
|
import type { AuthCredential, AuthCredentialStore, CredentialRefreshLeaseFence, DisabledCredentialSummary, StoredAuthCredential, StoredCredentialBlock } from "../auth-storage.js";
|
|
9
10
|
import type { OAuthCredentials } from "../registry/oauth/types.js";
|
|
10
11
|
import type { ClientUsageReport, ClientUsageSummary, UsageHistoryEntry, UsageHistoryQuery } from "../usage.js";
|
|
@@ -14,20 +15,7 @@ type SerializedCredentialRecord = {
|
|
|
14
15
|
data: string;
|
|
15
16
|
identityKey: string | null;
|
|
16
17
|
};
|
|
17
|
-
|
|
18
|
-
* SQLite's busy result code family — base `SQLITE_BUSY` plus the extended
|
|
19
|
-
* variants `SQLITE_BUSY_RECOVERY` (concurrent WAL recovery), `SQLITE_BUSY_SNAPSHOT`,
|
|
20
|
-
* and `SQLITE_BUSY_TIMEOUT`. All warrant the same backoff-and-retry treatment.
|
|
21
|
-
*/
|
|
22
|
-
export declare function isSqliteBusyError(err: unknown): boolean;
|
|
23
|
-
/**
|
|
24
|
-
* SQLite's unrecoverable-corruption result codes — the `SQLITE_CORRUPT` family
|
|
25
|
-
* (base plus extended variants like `SQLITE_CORRUPT_VTAB` / `SQLITE_CORRUPT_INDEX`)
|
|
26
|
-
* and `SQLITE_NOTADB` (the file header is not a database). Unlike
|
|
27
|
-
* {@link isSqliteBusyError}, these never clear by retrying: the store must be
|
|
28
|
-
* repaired or replaced, so callers latch and stop touching it.
|
|
29
|
-
*/
|
|
30
|
-
export declare function isSqliteCorruptionError(err: unknown): boolean;
|
|
18
|
+
export { isSqliteBusyError, isSqliteCorruptionError };
|
|
31
19
|
export declare function serializeCredential(provider: string, credential: AuthCredential): SerializedCredentialRecord | null;
|
|
32
20
|
export declare function resolveCredentialIdentityKey(provider: string, credential: AuthCredential): string | null;
|
|
33
21
|
/**
|
|
@@ -112,4 +100,3 @@ export declare class SqliteAuthCredentialStore implements AuthCredentialStore {
|
|
|
112
100
|
acknowledgeLocalChanges(): void;
|
|
113
101
|
close(): void;
|
|
114
102
|
}
|
|
115
|
-
export {};
|
|
@@ -1142,8 +1142,10 @@ export declare class AuthStorage {
|
|
|
1142
1142
|
* - usage-limit / account-rate-limit error → {@link AuthStorage.markUsageLimitReached}
|
|
1143
1143
|
* (temporary block via its own backoff — default plus server usage-report
|
|
1144
1144
|
* reset; sticky left intact so the next resolve re-ranks around the block).
|
|
1145
|
-
* -
|
|
1146
|
-
*
|
|
1145
|
+
* - exact Codex model-entitlement denial → temporarily block only that
|
|
1146
|
+
* requested model after provider/model identity matches, then rotate.
|
|
1147
|
+
* - other account-scoped policy denial → temporarily block that account
|
|
1148
|
+
* without marking its credential suspect, then rotate through siblings.
|
|
1147
1149
|
* - otherwise (hard 401 / auth failure) → mark the credential suspect (or
|
|
1148
1150
|
* reload when no broker hook is wired) and block it, then drop matching
|
|
1149
1151
|
* sticky state.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strip self-referential reasoning-fence delimiters that a provider leaks
|
|
3
|
+
* *inside* a structured thinking part.
|
|
4
|
+
*
|
|
5
|
+
* The visible-channel healers ({@link ../utils/leaked-thinking-stream},
|
|
6
|
+
* {@link ./fenced-thinking}) split leaked ` ```thinking ` fences out of the
|
|
7
|
+
* *text* stream. They never run over parts a provider already flags as
|
|
8
|
+
* thinking, so when a model (observed on Gemini thought summaries — issue
|
|
9
|
+
* #8719) emits a bare ` ```thinking ` / ` ``````thinking ` opener line between
|
|
10
|
+
* summaries, that delimiter reaches display and persistence verbatim as fence
|
|
11
|
+
* spam inside the reasoning block.
|
|
12
|
+
*
|
|
13
|
+
* This stripper removes only a *standalone* reasoning-fence opener line — a line
|
|
14
|
+
* that is nothing but a run of ≥3 backticks immediately followed by the info
|
|
15
|
+
* string `thinking` or `reasoning`. Such a line is always redundant inside an
|
|
16
|
+
* already-structured thinking block and never carries content. Inline mentions
|
|
17
|
+
* (prose on the same line), language-tagged code fences (` ```rs `), and bare
|
|
18
|
+
* closers (` ``` `) are left untouched so legitimate fenced code inside the
|
|
19
|
+
* reasoning survives.
|
|
20
|
+
*
|
|
21
|
+
* Streaming-safe: deltas may split a line anywhere. A trailing partial line is
|
|
22
|
+
* held only while it remains a viable opener prefix; the moment it cannot be an
|
|
23
|
+
* opener it is flushed and the rest of the line passes through character-level.
|
|
24
|
+
* Correctness never depends on the prefix heuristic — every held line is
|
|
25
|
+
* classified strictly on its newline (or on {@link ThinkingFenceStripper.flush})
|
|
26
|
+
* before it is dropped.
|
|
27
|
+
*/
|
|
28
|
+
/**
|
|
29
|
+
* Stateful, line-oriented stripper for leaked reasoning-fence openers in one
|
|
30
|
+
* structured thinking block. One instance per thinking block; feed every
|
|
31
|
+
* thinking delta through {@link push} and drain the tail with {@link flush}.
|
|
32
|
+
*/
|
|
33
|
+
export declare class ThinkingFenceStripper {
|
|
34
|
+
#private;
|
|
35
|
+
/** Consume one thinking delta; returns the sanitized text to emit (may be empty). */
|
|
36
|
+
push(chunk: string): string;
|
|
37
|
+
/** Drain any held partial line at block end; returns text to emit (may be empty). */
|
|
38
|
+
flush(): string;
|
|
39
|
+
}
|
|
@@ -7,6 +7,8 @@ export interface FinalizeOptions {
|
|
|
7
7
|
api?: Api;
|
|
8
8
|
/** Provider id; forwarded to the message formatter for copilot rewrites. */
|
|
9
9
|
provider?: string;
|
|
10
|
+
/** Requested model id; paired with provider for model-entitlement classification. */
|
|
11
|
+
model?: string;
|
|
10
12
|
/** Caller signal, for providers that don't run an abort tracker. */
|
|
11
13
|
signal?: AbortSignal;
|
|
12
14
|
/** Abort tracker, preferred over `signal`: distinguishes caller vs. local aborts. */
|
|
@@ -58,6 +58,13 @@ export declare function classify(error: unknown, api?: Api): number;
|
|
|
58
58
|
export declare function isUsageLimit(error: unknown, api?: Api): boolean;
|
|
59
59
|
/** Whether an upstream rejection is an account-scoped policy denial worth retrying with a sibling credential. */
|
|
60
60
|
export declare function isAccountPolicyError(error: unknown, api?: Api): boolean;
|
|
61
|
+
/**
|
|
62
|
+
* Model id from Codex's exact ChatGPT-account entitlement denial. Generic
|
|
63
|
+
* unsupported-model invalid requests deliberately do not match.
|
|
64
|
+
*/
|
|
65
|
+
export declare function codexChatGPTAccountPolicyModel(error: unknown, depth?: number): string | undefined;
|
|
66
|
+
/** Whether the exact Codex entitlement denial applies to this provider and requested model. */
|
|
67
|
+
export declare function isCodexChatGPTAccountPolicyError(error: unknown, provider: string, modelId: string | undefined): boolean;
|
|
61
68
|
/**
|
|
62
69
|
* Strict-tool rejection: grammar too large, schema too complex, or structured
|
|
63
70
|
* outputs unsupported by the model/endpoint.
|
|
@@ -78,6 +85,8 @@ export declare function isFastModeUnsupported(error: unknown): boolean;
|
|
|
78
85
|
export declare function isCopilotTransientModelError(error: unknown): boolean;
|
|
79
86
|
export declare function classifyMessage(message: {
|
|
80
87
|
api?: Api;
|
|
88
|
+
provider?: string;
|
|
89
|
+
model?: string;
|
|
81
90
|
errorId?: number;
|
|
82
91
|
errorMessage?: string;
|
|
83
92
|
errorStatus?: number;
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* `ExecClientMessage` carrying a result with an unset oneof is a fake success
|
|
10
10
|
* the server reads as "the tool ran and produced nothing".
|
|
11
11
|
*/
|
|
12
|
-
import { type ExecuteHookRequest, type ExecuteHookResult, type McpStateExecResult, type McpToolDefinition, type PiBashExecResult, type PiEditExecResult, type PiFindExecResult, type PiGrepExecResult, type PiLsExecResult, type PiReadExecResult, type PiTruncation, type PiWriteExecResult } from "@oh-my-pi/pi-catalog/discovery/cursor-
|
|
12
|
+
import { type ExecuteHookRequest, type ExecuteHookResult, type McpStateExecResult, type McpToolDefinition, type PiBashExecResult, type PiEditExecResult, type PiFindExecResult, type PiGrepExecResult, type PiLsExecResult, type PiReadExecResult, type PiTruncation, type PiWriteExecResult } from "@oh-my-pi/pi-catalog/discovery/cursor-proto";
|
|
13
13
|
import type { ToolResultMessage } from "../../types.js";
|
|
14
14
|
/**
|
|
15
15
|
* The pure arg translation lives in `../cursor-pi-args` so the legacy pi shim
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type http2 from "node:http2";
|
|
2
|
+
import { type InteractionQuery } from "@oh-my-pi/pi-catalog/discovery/cursor-proto";
|
|
3
|
+
/**
|
|
4
|
+
* Answer a Cursor `interaction_query` so the Run RPC can continue.
|
|
5
|
+
*
|
|
6
|
+
* Hosted web search / Exa / unnamed permission gates (field 9 = WebFetch on
|
|
7
|
+
* current Cursor builds) block the turn until the client writes an
|
|
8
|
+
* `interaction_response`. Dropping the frame leaves the HTTP/2 stream alive
|
|
9
|
+
* on heartbeats that are not semantic progress; the lazy idle watchdog then
|
|
10
|
+
* aborts with "Provider stream stalled while waiting for the next event".
|
|
11
|
+
*
|
|
12
|
+
* Unsupported interactive queries are rejected so the server is not stranded.
|
|
13
|
+
* VM setup is left unanswered rather than reporting a fake success.
|
|
14
|
+
*/
|
|
15
|
+
export declare function handleInteractionQuery(query: InteractionQuery, h2Request: http2.ClientHttp2Stream): void;
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*
|
|
10
10
|
* Kept apart from `cursor/exec-modern.ts` on purpose: these are pure
|
|
11
11
|
* string/path functions with no protobuf coupling, while that module pulls in
|
|
12
|
-
*
|
|
12
|
+
* the generated cursor protobuf graph. The legacy shim is
|
|
13
13
|
* compiled into the bundled virtual module registry, so importing it from a
|
|
14
14
|
* nested path would drag the whole exec implementation in with it — and
|
|
15
15
|
* `./providers/*` is a single-segment wildcard export that cannot serve a
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import http2 from "node:http2";
|
|
2
|
-
import {
|
|
3
|
-
import type
|
|
4
|
-
import { type
|
|
5
|
-
import type { AssistantMessage, CursorExecHandlerResult, CursorExecHandlers, CursorExecPairing, CursorTodoSyncHandler, CursorToolResultHandler, Message, StreamFunction, StreamOptions, TextContent, ThinkingContent, Tool, ToolCall, ToolResultMessage } from "../types.js";
|
|
2
|
+
import type { McpToolDefinition } from "@oh-my-pi/pi-catalog/discovery/cursor-proto";
|
|
3
|
+
import { type AgentServerMessage, type ConversationStateStructure } from "@oh-my-pi/pi-catalog/discovery/cursor-proto";
|
|
4
|
+
import { type JsonValue } from "@oh-my-pi/pi-catalog/discovery/protobuf";
|
|
5
|
+
import type { AssistantMessage, Context, CursorExecHandlerResult, CursorExecHandlers, CursorExecPairing, CursorTodoSyncHandler, CursorToolResultHandler, Message, Model, StreamFunction, StreamOptions, TextContent, ThinkingContent, Tool, ToolCall, ToolResultMessage } from "../types.js";
|
|
6
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";
|
|
@@ -32,7 +32,7 @@ export type ToolCallState = ToolCall & {
|
|
|
32
32
|
[kStreamingBlockIndex]: number;
|
|
33
33
|
[kStreamingPartialJson]?: string;
|
|
34
34
|
[kStreamingLastParseLen]?: number;
|
|
35
|
-
[kStreamingBlockKind]: "mcp" | "todo" | "cursor-exec" | "connect-scm";
|
|
35
|
+
[kStreamingBlockKind]: "mcp" | "todo" | "cursor-exec" | "connect-scm" | "web-fetch";
|
|
36
36
|
[kStreamingEnvelopeId]?: string;
|
|
37
37
|
[kCursorExecResolved]?: true;
|
|
38
38
|
};
|
|
@@ -219,3 +219,12 @@ export declare function buildCursorHistoryForTest(messages: Message[], activeUse
|
|
|
219
219
|
turnUserMessagesJson: JsonValue[];
|
|
220
220
|
turnStepMessagesJson: JsonValue[][];
|
|
221
221
|
};
|
|
222
|
+
export declare function buildGrpcRequest(model: Model<"cursor-agent">, context: Context, options: CursorOptions | undefined, state: {
|
|
223
|
+
conversationId: string;
|
|
224
|
+
blobStore: Map<string, Uint8Array>;
|
|
225
|
+
conversationState?: ConversationStateStructure;
|
|
226
|
+
}): Promise<{
|
|
227
|
+
requestBytes: Uint8Array;
|
|
228
|
+
blobStore: Map<string, Uint8Array>;
|
|
229
|
+
conversationState: ConversationStateStructure;
|
|
230
|
+
}>;
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import type { CapturedHttpErrorResponse } from "../utils/http-inspector.js";
|
|
2
|
-
/**
|
|
3
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Fallback marker: the server rejected the `chat_template_kwargs.reasoning_effort`
|
|
4
|
+
* spelling itself (strict kwargs whitelists — Ninfer-style servers), not the
|
|
5
|
+
* effort value. Apply strips the kwarg and hoists the value onto the top-level
|
|
6
|
+
* `reasoning_effort` field when that spelling is absent.
|
|
7
|
+
* @internal
|
|
8
|
+
*/
|
|
9
|
+
export declare const STRIP_TEMPLATE_KWARG_REASONING_EFFORT: unique symbol;
|
|
10
|
+
/** @internal */
|
|
11
|
+
export type OpenAIReasoningEffortFallback = string | null | typeof STRIP_TEMPLATE_KWARG_REASONING_EFFORT;
|
|
4
12
|
/** @internal */
|
|
5
13
|
export interface OpenAIReasoningEffortFallbackState {
|
|
6
14
|
reasoningEffortFallbacks: Map<string, OpenAIReasoningEffortFallback>;
|
|
@@ -231,6 +231,7 @@ export type OpenAICompletionsParams = Omit<ChatCompletionCreateParamsStreaming,
|
|
|
231
231
|
chat_template_kwargs?: {
|
|
232
232
|
enable_thinking?: boolean;
|
|
233
233
|
preserve_thinking?: boolean;
|
|
234
|
+
reasoning_effort?: string;
|
|
234
235
|
};
|
|
235
236
|
reasoning?: {
|
|
236
237
|
effort?: string;
|
|
@@ -314,6 +315,20 @@ export interface OpenAICompatPolicy {
|
|
|
314
315
|
export declare function mapOpenAIReasoningEffort(model: Pick<Model, "thinking">, compat: {
|
|
315
316
|
reasoningEffortMap?: Partial<Record<Effort, string>>;
|
|
316
317
|
} | undefined, effort: string): string;
|
|
318
|
+
/**
|
|
319
|
+
* Whether a redundant `tool_choice: "auto"` should be dropped to keep
|
|
320
|
+
* reasoning alive. Hosts with `disableReasoningOnToolChoice` (DeepSeek family
|
|
321
|
+
* on e.g. Fireworks) silently turn reasoning off whenever any `tool_choice`
|
|
322
|
+
* is present. "auto" is already the provider default, so omitting it is
|
|
323
|
+
* wire-neutral for tool selection; forced and "none" choices are semantic and
|
|
324
|
+
* still win over reasoning (#1207).
|
|
325
|
+
*/
|
|
326
|
+
export declare function shouldDropAutoToolChoiceForReasoning(model: Pick<Model, "reasoning">, compat: {
|
|
327
|
+
disableReasoningOnToolChoice: boolean;
|
|
328
|
+
}, toolChoice: unknown, options: {
|
|
329
|
+
reasoning?: string;
|
|
330
|
+
disableReasoning?: boolean;
|
|
331
|
+
} | undefined): boolean;
|
|
317
332
|
export declare function resolveOpenAICompatPolicy<TApi extends Api>(model: Model<TApi>, options: ResolveOpenAICompatPolicyOptions): OpenAICompatPolicy;
|
|
318
333
|
export declare function applyChatCompletionsCompatPolicy(params: OpenAICompletionsParams, policy: OpenAICompatPolicy): void;
|
|
319
334
|
export declare function applyChatCompletionsReasoningParams(params: OpenAICompletionsParams, model: Model<"openai-completions">, compat: ResolvedOpenAICompat, options: (ChatCompletionsReasoningOptions & {
|
|
@@ -403,6 +418,23 @@ export declare function repairOrphanResponsesToolOutputs(input: ResponseInput):
|
|
|
403
418
|
* {@link repairOrphanResponsesToolOutputs}.
|
|
404
419
|
*/
|
|
405
420
|
export declare function repairOrphanResponsesToolCalls(input: ResponseInput): ResponseInput;
|
|
421
|
+
/**
|
|
422
|
+
* Relocate assistant `message` items wedged inside a tool-call → tool-output
|
|
423
|
+
* batch to before the batch, yielding canonical `message(s) → calls → outputs`
|
|
424
|
+
* order. Idempotent; returns the same array reference when nothing moves.
|
|
425
|
+
*
|
|
426
|
+
* OpenAI's Responses API pairs tool outputs by `call_id` and tolerates any item
|
|
427
|
+
* order, but stricter gateways (notably opencode-go's "Console Go") reject a
|
|
428
|
+
* shape where an assistant message interrupts a `function_call` →
|
|
429
|
+
* `function_call_output` run, 400ing with `No tool output found for tool call …`
|
|
430
|
+
* (naming a random call of the batch on each retry). This arises whenever a
|
|
431
|
+
* model streams a trailing text / demoted-thinking block *after* its tool calls:
|
|
432
|
+
* the block-encode path preserves stream order, emitting the message between the
|
|
433
|
+
* calls and the outputs appended afterward. Moving the already-model-owned
|
|
434
|
+
* message ahead of its call batch keeps content identical while satisfying the
|
|
435
|
+
* strict validator. See #8789.
|
|
436
|
+
*/
|
|
437
|
+
export declare function hoistInterleavedResponsesToolBatchMessages<T extends object>(items: readonly T[]): T[];
|
|
406
438
|
export declare function convertResponsesInputContent(content: string | Array<TextContent | ImageContent>, supportsImages: boolean, supportsImageDetailOriginal: boolean, escapeControlTokens?: boolean): ResponseInputContent[] | undefined;
|
|
407
439
|
export interface BuildResponsesInputOptions<TApi extends Api> {
|
|
408
440
|
model: Model<TApi>;
|
|
@@ -1,18 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* OpenCode Zen
|
|
2
|
+
* OpenCode login flow, shared by the OpenCode Zen and OpenCode Go providers.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Both are subscription services whose API keys are issued from the same
|
|
5
|
+
* OpenCode Zen console at https://opencode.ai/auth — OpenCode Go keys are
|
|
6
|
+
* minted there after subscribing to Go (see https://opencode.ai/docs/go).
|
|
7
|
+
* This is not OAuth; it's a simple paste-the-API-key flow:
|
|
7
8
|
* 1. Open browser to https://opencode.ai/auth
|
|
8
|
-
* 2. User logs in and copies
|
|
9
|
+
* 2. User logs in (and subscribes to Go, for OpenCode Go) and copies the key
|
|
9
10
|
* 3. User pastes the API key back into the CLI
|
|
10
11
|
*/
|
|
11
12
|
import type { OAuthController } from "./types.js";
|
|
12
13
|
/**
|
|
13
|
-
*
|
|
14
|
+
* Log in to an OpenCode subscription provider.
|
|
14
15
|
*
|
|
15
|
-
* Opens browser to
|
|
16
|
-
*
|
|
16
|
+
* Opens the browser to the OpenCode Zen console, prompts the user to paste
|
|
17
|
+
* their API key, and returns it directly (not OAuthCredentials — this isn't
|
|
18
|
+
* OAuth).
|
|
19
|
+
*
|
|
20
|
+
* @param providerName Display name of the provider being connected
|
|
21
|
+
* ("OpenCode Zen" or "OpenCode Go"). Used verbatim in the paste prompt so
|
|
22
|
+
* the CLI reflects the provider the user actually selected instead of always
|
|
23
|
+
* asking for a Zen key.
|
|
17
24
|
*/
|
|
18
|
-
export declare function loginOpenCode(options: OAuthController): Promise<string>;
|
|
25
|
+
export declare function loginOpenCode(options: OAuthController, providerName?: string): Promise<string>;
|
package/dist/types/types.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export * from "@oh-my-pi/pi-catalog/effort";
|
|
2
2
|
export * from "@oh-my-pi/pi-catalog/types";
|
|
3
3
|
import type { Type } from "@oh-my-pi/omptype";
|
|
4
|
-
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-
|
|
4
|
+
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-proto";
|
|
5
5
|
import type { Effort } from "@oh-my-pi/pi-catalog/effort";
|
|
6
6
|
import type { Api, FetchImpl, Model, Provider, ThinkingBudgets, Usage } from "@oh-my-pi/pi-catalog/types";
|
|
7
7
|
import type { ApiKey } from "./auth-retry.js";
|
|
@@ -701,6 +701,13 @@ export interface ContextSnapshot {
|
|
|
701
701
|
nonMessageTokens: number;
|
|
702
702
|
/** Estimated prompt tokens removed by local history rewrites after this provider snapshot was recorded. */
|
|
703
703
|
historyRewriteTokensRemoved?: number;
|
|
704
|
+
/**
|
|
705
|
+
* Compaction epoch current when this snapshot's provider request was recorded.
|
|
706
|
+
* A later compaction bumps the session epoch, so an anchor whose epoch is
|
|
707
|
+
* older than the current in-flight snapshot describes pre-compaction history
|
|
708
|
+
* and must not override the rebased estimate.
|
|
709
|
+
*/
|
|
710
|
+
compactionEpoch?: number;
|
|
704
711
|
lastMessageTimestamp?: number;
|
|
705
712
|
}
|
|
706
713
|
export interface AssistantMessage {
|
package/dist/types/usage.d.ts
CHANGED
|
@@ -482,6 +482,16 @@ export interface CredentialRankingStrategy {
|
|
|
482
482
|
* account-wide quotas can omit this and use all limits.
|
|
483
483
|
*/
|
|
484
484
|
scopeLimits?(report: UsageReport, context?: CredentialRankingContext): UsageLimit[];
|
|
485
|
+
/**
|
|
486
|
+
* Restrict limits for the opt-in, non-destructive usage-reserve health
|
|
487
|
+
* check ({@link AuthStorage.getModelUsageHealth}). Distinct from
|
|
488
|
+
* {@link scopeLimits}, which gates credential-wide hard blocks: a provider
|
|
489
|
+
* whose model/tier counters are trusted only at confirmed exhaustion for
|
|
490
|
+
* hard-blocking can still expose them here so the reserve margin protects
|
|
491
|
+
* the mapped quota before it hits the cap. Falls back to {@link scopeLimits}
|
|
492
|
+
* when omitted.
|
|
493
|
+
*/
|
|
494
|
+
scopeLimitsForReserve?(report: UsageReport, context?: CredentialRankingContext): UsageLimit[];
|
|
485
495
|
/**
|
|
486
496
|
* Return a provider-local backoff scope for the requested model. Providers
|
|
487
497
|
* with backend-specific quotas use this so one exhausted model family does
|
|
@@ -4,11 +4,11 @@ export interface JsonSchemaValidationIssue {
|
|
|
4
4
|
expectedTypes?: string[];
|
|
5
5
|
keyword?: string;
|
|
6
6
|
/**
|
|
7
|
-
* Marks issues
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
7
|
+
* Marks issues surfaced from a failed `anyOf` / `oneOf` branch (at any
|
|
8
|
+
* depth). Such a diagnosis is one candidate branch's guess, not
|
|
9
|
+
* authoritative: the tool-argument coercion layer keeps lossy repairs
|
|
10
|
+
* (container stringification, unrecognized-key deletion, singleton-array
|
|
11
|
+
* wrapping) off for these while still applying lossless ones.
|
|
12
12
|
*/
|
|
13
13
|
fromUnionBranch?: boolean;
|
|
14
14
|
}
|
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.
|
|
4
|
+
"version": "17.4.0",
|
|
5
5
|
"description": "Unified LLM API with automatic model discovery and provider configuration",
|
|
6
6
|
"homepage": "https://omp.sh",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -37,14 +37,12 @@
|
|
|
37
37
|
"fmt": "biome format --write ."
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@
|
|
41
|
-
"@oh-my-pi/
|
|
42
|
-
"@oh-my-pi/pi-
|
|
43
|
-
"@oh-my-pi/pi-
|
|
44
|
-
"@oh-my-pi/pi-wire": "17.3.7"
|
|
40
|
+
"@oh-my-pi/omptype": "17.4.0",
|
|
41
|
+
"@oh-my-pi/pi-catalog": "17.4.0",
|
|
42
|
+
"@oh-my-pi/pi-utils": "17.4.0",
|
|
43
|
+
"@oh-my-pi/pi-wire": "17.4.0"
|
|
45
44
|
},
|
|
46
45
|
"devDependencies": {
|
|
47
|
-
"@bufbuild/protoc-gen-es": "^2.12.1",
|
|
48
46
|
"@types/bun": "^1.3.14"
|
|
49
47
|
},
|
|
50
48
|
"engines": {
|
|
@@ -8,7 +8,13 @@ import { Database, type Statement } from "bun:sqlite";
|
|
|
8
8
|
import * as fs from "node:fs/promises";
|
|
9
9
|
import * as path from "node:path";
|
|
10
10
|
import { parseAlibabaTokenPlanCredential } from "@oh-my-pi/pi-catalog/wire/alibaba-token-plan";
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
getAgentDbPath,
|
|
13
|
+
getDbBusyTimeoutMs,
|
|
14
|
+
isSqliteBusyError,
|
|
15
|
+
isSqliteCorruptionError,
|
|
16
|
+
logger,
|
|
17
|
+
} from "@oh-my-pi/pi-utils";
|
|
12
18
|
import type {
|
|
13
19
|
AuthCredential,
|
|
14
20
|
AuthCredentialStore,
|
|
@@ -87,29 +93,10 @@ const LEGACY_CODEX_BLOCK_PROVIDER_KEY = "openai-codex:oauth";
|
|
|
87
93
|
const LEGACY_CODEX_BLOCK_SCOPE = "shared";
|
|
88
94
|
const CODEX_METER_BLOCK_SCOPES = ["chat", "spark"] as const;
|
|
89
95
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
*/
|
|
95
|
-
export function isSqliteBusyError(err: unknown): boolean {
|
|
96
|
-
if (err === null || typeof err !== "object") return false;
|
|
97
|
-
const code = (err as { code?: unknown }).code;
|
|
98
|
-
return typeof code === "string" && code.startsWith("SQLITE_BUSY");
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* SQLite's unrecoverable-corruption result codes — the `SQLITE_CORRUPT` family
|
|
103
|
-
* (base plus extended variants like `SQLITE_CORRUPT_VTAB` / `SQLITE_CORRUPT_INDEX`)
|
|
104
|
-
* and `SQLITE_NOTADB` (the file header is not a database). Unlike
|
|
105
|
-
* {@link isSqliteBusyError}, these never clear by retrying: the store must be
|
|
106
|
-
* repaired or replaced, so callers latch and stop touching it.
|
|
107
|
-
*/
|
|
108
|
-
export function isSqliteCorruptionError(err: unknown): boolean {
|
|
109
|
-
if (err === null || typeof err !== "object" || !("code" in err)) return false;
|
|
110
|
-
const code = err.code;
|
|
111
|
-
return typeof code === "string" && (code.startsWith("SQLITE_CORRUPT") || code === "SQLITE_NOTADB");
|
|
112
|
-
}
|
|
96
|
+
// SQLite error classifiers live in pi-utils so the credential store and the
|
|
97
|
+
// model cache share one implementation; re-exported here to preserve the
|
|
98
|
+
// pre-existing `@oh-my-pi/pi-ai/auth-storage` surface.
|
|
99
|
+
export { isSqliteBusyError, isSqliteCorruptionError };
|
|
113
100
|
|
|
114
101
|
function normalizeStoredAccountId(accountId: string | null | undefined): string | null {
|
|
115
102
|
const normalized = accountId?.trim();
|
|
@@ -730,19 +730,45 @@ async function handleCredentialsCheck(storage: AuthStorage, signal: AbortSignal)
|
|
|
730
730
|
return json(200, { generatedAt: Date.now(), credentials });
|
|
731
731
|
}
|
|
732
732
|
|
|
733
|
+
/**
|
|
734
|
+
* Row shape for `GET /v1/models`. Beyond the OpenAI-standard `id`/`object`/
|
|
735
|
+
* `owned_by`, rows advertise the catalog metadata OpenAI-compatible clients
|
|
736
|
+
* (omp's own proxy discovery, Zed's openai_compatible provider, ...) read to
|
|
737
|
+
* size and capability-gate discovered models: `context_length`,
|
|
738
|
+
* `max_output_tokens`, `input_modalities`, and `supports_tools` (only emitted
|
|
739
|
+
* when the catalog explicitly reports `false`; absent means usable).
|
|
740
|
+
*/
|
|
741
|
+
interface ModelListRow {
|
|
742
|
+
id: string;
|
|
743
|
+
object: "model";
|
|
744
|
+
owned_by: string;
|
|
745
|
+
api: Api;
|
|
746
|
+
display_name: string;
|
|
747
|
+
context_length?: number;
|
|
748
|
+
max_output_tokens?: number;
|
|
749
|
+
input_modalities: ("text" | "image")[];
|
|
750
|
+
supports_tools?: boolean;
|
|
751
|
+
}
|
|
752
|
+
|
|
733
753
|
function handleModelsList(opts: AuthGatewayBootOptions): Response {
|
|
734
754
|
const seen = new Set<string>();
|
|
735
|
-
const data:
|
|
755
|
+
const data: ModelListRow[] = [];
|
|
736
756
|
for (const model of opts.listModels?.() ?? []) {
|
|
737
757
|
const id = `${model.provider}/${model.id}`;
|
|
738
758
|
if (seen.has(id)) continue;
|
|
739
759
|
seen.add(id);
|
|
740
|
-
|
|
760
|
+
const row: ModelListRow = {
|
|
741
761
|
id,
|
|
742
762
|
object: "model",
|
|
743
763
|
owned_by: model.provider,
|
|
744
764
|
api: model.api,
|
|
745
|
-
|
|
765
|
+
display_name: model.name,
|
|
766
|
+
input_modalities: model.input,
|
|
767
|
+
};
|
|
768
|
+
if (model.contextWindow != null) row.context_length = model.contextWindow;
|
|
769
|
+
if (model.maxTokens != null) row.max_output_tokens = model.maxTokens;
|
|
770
|
+
if (model.supportsTools === false) row.supports_tools = false;
|
|
771
|
+
data.push(row);
|
|
746
772
|
}
|
|
747
773
|
return json(200, { object: "list", data });
|
|
748
774
|
}
|