@oh-my-pi/pi-ai 18.1.21 → 18.2.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 +16 -0
- package/THIRD-PARTY-NOTICES.txt +54 -25
- package/dist/types/providers/anthropic-signature.d.ts +25 -0
- package/dist/types/registry/types.d.ts +2 -0
- package/dist/types/types.d.ts +7 -1
- package/package.json +6 -6
- package/src/providers/anthropic-signature.ts +103 -0
- package/src/providers/anthropic.ts +10 -0
- package/src/providers/openai-completions.ts +2 -0
- package/src/providers/openai-shared.ts +2 -0
- package/src/registry/build.ts +1 -0
- package/src/registry/types.ts +2 -0
- package/src/stream.ts +6 -5
- package/src/types.ts +7 -1
- package/src/utils/http-inspector.ts +11 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [18.2.0] - 2026-09-15
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Assistant turns from Anthropic-compatible hosts (direct, or via OpenRouter's `reasoning_details`) carry `upstreamModel`, the serving model id recovered from the signed thinking block, so callers can detect a router substituting a different model than requested.
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- Fixed OpenCode Go window-limit 429s (`5-hour`/`Weekly`/`Monthly usage limit reached. Resets in …`) not pinning the exhausted credential to the server-stated reset; the window phrasing is now covered by a regression test over the rotation classifier. ([#12091](https://github.com/can1357/oh-my-pi/pull/12091) by [@H4vC](https://github.com/H4vC))
|
|
14
|
+
|
|
15
|
+
## [18.1.22] - 2026-09-14
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- 400-request debug dumps now redact provider-specific auth headers (`x-goog-api-key`, `x-amz-security-token`, and any header whose name carries a key/token/secret), not just a fixed allow-list, so a shared dump can no longer leak a live API key ([#12007](https://github.com/can1357/oh-my-pi/issues/12007)).
|
|
20
|
+
|
|
5
21
|
## [18.1.20] - 2026-09-13
|
|
6
22
|
|
|
7
23
|
### Fixed
|
package/THIRD-PARTY-NOTICES.txt
CHANGED
|
@@ -10843,7 +10843,7 @@ RUST RUNTIME DEPENDENCY LICENSES
|
|
|
10843
10843
|
|
|
10844
10844
|
Generated from Cargo.lock by cargo-about 0.8.2 in locked, offline, workspace, all-feature, all-target mode, then restricted using cargo metadata to normal and build edges reachable from the workspace (development-only edges are excluded). cargo-deny independently evaluates the complete all-target graph.
|
|
10845
10845
|
|
|
10846
|
-
Native Opus (
|
|
10846
|
+
Native Opus (opus/opusic-sys) and PCRE2 (pcre2/pcre2-sys) are covered by the exact crate license payloads below. inferno 0.12.8 is package-scoped as CDDL-1.0: its CDDL-covered source remains available in the locked crates.io source archive at https://crates.io/crates/inferno/0.12.8 and https://github.com/jonhoo/inferno. The full locked license text follows below.
|
|
10847
10847
|
|
|
10848
10848
|
-------------------------------------------------------------------------------
|
|
10849
10849
|
License: Apache License 2.0 (Apache-2.0)
|
|
@@ -12509,6 +12509,58 @@ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
|
12509
12509
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
12510
12510
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
12511
12511
|
|
|
12512
|
+
-------------------------------------------------------------------------------
|
|
12513
|
+
License: BSD 3-Clause "New" or "Revised" License (BSD-3-Clause)
|
|
12514
|
+
Used by:
|
|
12515
|
+
- opusic-sys 0.7.5 (https://github.com/DoumanAsh/opusic-sys)
|
|
12516
|
+
|
|
12517
|
+
Full license text:
|
|
12518
|
+
|
|
12519
|
+
Copyright 2001-2023 Xiph.Org, Skype Limited, Octasic,
|
|
12520
|
+
Jean-Marc Valin, Timothy B. Terriberry,
|
|
12521
|
+
CSIRO, Gregory Maxwell, Mark Borgerding,
|
|
12522
|
+
Erik de Castro Lopo, Mozilla, Amazon
|
|
12523
|
+
|
|
12524
|
+
Redistribution and use in source and binary forms, with or without
|
|
12525
|
+
modification, are permitted provided that the following conditions
|
|
12526
|
+
are met:
|
|
12527
|
+
|
|
12528
|
+
- Redistributions of source code must retain the above copyright
|
|
12529
|
+
notice, this list of conditions and the following disclaimer.
|
|
12530
|
+
|
|
12531
|
+
- Redistributions in binary form must reproduce the above copyright
|
|
12532
|
+
notice, this list of conditions and the following disclaimer in the
|
|
12533
|
+
documentation and/or other materials provided with the distribution.
|
|
12534
|
+
|
|
12535
|
+
- Neither the name of Internet Society, IETF or IETF Trust, nor the
|
|
12536
|
+
names of specific contributors, may be used to endorse or promote
|
|
12537
|
+
products derived from this software without specific prior written
|
|
12538
|
+
permission.
|
|
12539
|
+
|
|
12540
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
12541
|
+
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
12542
|
+
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
12543
|
+
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
|
|
12544
|
+
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|
12545
|
+
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
12546
|
+
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
12547
|
+
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
12548
|
+
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
12549
|
+
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
12550
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
12551
|
+
|
|
12552
|
+
Opus is subject to the royalty-free patent licenses which are
|
|
12553
|
+
specified at:
|
|
12554
|
+
|
|
12555
|
+
Xiph.Org Foundation:
|
|
12556
|
+
https://datatracker.ietf.org/ipr/1524/
|
|
12557
|
+
|
|
12558
|
+
Microsoft Corporation:
|
|
12559
|
+
https://datatracker.ietf.org/ipr/1914/
|
|
12560
|
+
|
|
12561
|
+
Broadcom Corporation:
|
|
12562
|
+
https://datatracker.ietf.org/ipr/1526/
|
|
12563
|
+
|
|
12512
12564
|
-------------------------------------------------------------------------------
|
|
12513
12565
|
License: BSD 3-Clause "New" or "Revised" License (BSD-3-Clause)
|
|
12514
12566
|
Used by:
|
|
@@ -13290,29 +13342,6 @@ jurisdiction of the Federal Courts of the Northern District of
|
|
|
13290
13342
|
California and the state courts of the State of California, with
|
|
13291
13343
|
venue lying in Santa Clara County, California.
|
|
13292
13344
|
|
|
13293
|
-
-------------------------------------------------------------------------------
|
|
13294
|
-
License: ISC License (ISC)
|
|
13295
|
-
Used by:
|
|
13296
|
-
- audiopus_sys 0.2.2 (https://github.com/lakelezz/audiopus_sys.git)
|
|
13297
|
-
|
|
13298
|
-
Full license text:
|
|
13299
|
-
|
|
13300
|
-
ISC License
|
|
13301
|
-
|
|
13302
|
-
Copyright (c) 2019, Lakelezz
|
|
13303
|
-
|
|
13304
|
-
Permission to use, copy, modify, and/or distribute this software for any
|
|
13305
|
-
purpose with or without fee is hereby granted, provided that the above
|
|
13306
|
-
copyright notice and this permission notice appear in all copies.
|
|
13307
|
-
|
|
13308
|
-
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
13309
|
-
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
13310
|
-
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
13311
|
-
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
13312
|
-
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
13313
|
-
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
13314
|
-
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
13315
|
-
|
|
13316
13345
|
-------------------------------------------------------------------------------
|
|
13317
13346
|
License: ISC License (ISC)
|
|
13318
13347
|
Used by:
|
|
@@ -18734,7 +18763,7 @@ DEALINGS IN THE SOFTWARE.
|
|
|
18734
18763
|
-------------------------------------------------------------------------------
|
|
18735
18764
|
License: MIT License (MIT)
|
|
18736
18765
|
Used by:
|
|
18737
|
-
- opus 0.
|
|
18766
|
+
- opus 0.4.0 (https://github.com/SpaceManiac/opus-rs)
|
|
18738
18767
|
|
|
18739
18768
|
Full license text:
|
|
18740
18769
|
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recovers the serving model id Anthropic embeds in thinking-block signatures.
|
|
3
|
+
*
|
|
4
|
+
* A signature is a base64 protobuf: outer `{1: version, 2: envelope}`, envelope
|
|
5
|
+
* `{1: header, 2..5: nonce/ciphertext/tag}`, header
|
|
6
|
+
* `{1: schema, 5: hash, 6: model, 8: "thinking", 11: org uuid, 15: profile, 21: unix ts}`.
|
|
7
|
+
* Field 6 names the model that actually produced the block — a concrete
|
|
8
|
+
* snapshot (`claude-haiku-4-5-20251001`) or an internal codename
|
|
9
|
+
* (`numbat-v6-…`) — which lets a caller notice a gateway serving something other
|
|
10
|
+
* than the requested model. The v4 outer format (`CAQS…`) ships a header
|
|
11
|
+
* without field 6; the decoder then yields nothing.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Model id embedded in an Anthropic thinking signature, or undefined when the
|
|
15
|
+
* signature is malformed, uses a format without a cleartext header, or names
|
|
16
|
+
* nothing resembling a model id.
|
|
17
|
+
*/
|
|
18
|
+
export declare function servedModelFromAnthropicSignature(signature: string): string | undefined;
|
|
19
|
+
/**
|
|
20
|
+
* Model id from an OpenRouter reasoning item — a `reasoning_details` entry on
|
|
21
|
+
* chat completions or a Responses `reasoning` output item — which forwards the
|
|
22
|
+
* upstream Anthropic signature as `{ format: "anthropic-claude-v1", signature }`.
|
|
23
|
+
* Undefined for other formats.
|
|
24
|
+
*/
|
|
25
|
+
export declare function servedModelFromOpenRouterReasoning(item: unknown): string | undefined;
|
|
@@ -60,6 +60,8 @@ export interface ProviderDefinition {
|
|
|
60
60
|
readonly envKeys?: KeyResolver;
|
|
61
61
|
/** Provider transport can authenticate without a resolved API-key string. */
|
|
62
62
|
readonly allowsMissingApiKey?: boolean;
|
|
63
|
+
/** APIs whose provider transport resolves credentials without a stored account. */
|
|
64
|
+
readonly nativeAuthApis?: readonly string[];
|
|
63
65
|
/** Provider-owned model normalization that must run before API-specific option mapping. */
|
|
64
66
|
readonly prepareModel?: ProviderModelPreparer;
|
|
65
67
|
/** Provider-owned request shaping applied before generic API dispatch. */
|
package/dist/types/types.d.ts
CHANGED
|
@@ -839,7 +839,13 @@ export interface AssistantMessage {
|
|
|
839
839
|
* providers that expose no such field.
|
|
840
840
|
*/
|
|
841
841
|
upstreamProvider?: string;
|
|
842
|
-
/**
|
|
842
|
+
/**
|
|
843
|
+
* Concrete model that produced this turn when it is knowable independently
|
|
844
|
+
* of the requested id: reported by a router that selected one, or recovered
|
|
845
|
+
* from a signed thinking block (Anthropic signatures name the serving
|
|
846
|
+
* model). Compared against `model` to notice a gateway serving something
|
|
847
|
+
* other than what was requested.
|
|
848
|
+
*/
|
|
843
849
|
upstreamModel?: string;
|
|
844
850
|
usage: Usage;
|
|
845
851
|
stopReason: StopReason;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oh-my-pi/pi-ai",
|
|
3
|
-
"version": "18.
|
|
3
|
+
"version": "18.2.0",
|
|
4
4
|
"description": "Unified LLM API with automatic model discovery and provider configuration",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -124,11 +124,11 @@
|
|
|
124
124
|
"fmt": "oxfmt --no-error-on-unmatched-pattern 'src/**/*.{ts,tsx}' '{test,bench,examples,scripts}/**/*.ts' '*.ts'"
|
|
125
125
|
},
|
|
126
126
|
"dependencies": {
|
|
127
|
-
"@oh-my-pi/omptype": "18.
|
|
128
|
-
"@oh-my-pi/pi-catalog": "18.
|
|
129
|
-
"@oh-my-pi/pi-natives": "18.
|
|
130
|
-
"@oh-my-pi/pi-utils": "18.
|
|
131
|
-
"@oh-my-pi/pi-wire": "18.
|
|
127
|
+
"@oh-my-pi/omptype": "18.2.0",
|
|
128
|
+
"@oh-my-pi/pi-catalog": "18.2.0",
|
|
129
|
+
"@oh-my-pi/pi-natives": "18.2.0",
|
|
130
|
+
"@oh-my-pi/pi-utils": "18.2.0",
|
|
131
|
+
"@oh-my-pi/pi-wire": "18.2.0"
|
|
132
132
|
},
|
|
133
133
|
"devDependencies": {
|
|
134
134
|
"@types/bun": "^1.3.14"
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recovers the serving model id Anthropic embeds in thinking-block signatures.
|
|
3
|
+
*
|
|
4
|
+
* A signature is a base64 protobuf: outer `{1: version, 2: envelope}`, envelope
|
|
5
|
+
* `{1: header, 2..5: nonce/ciphertext/tag}`, header
|
|
6
|
+
* `{1: schema, 5: hash, 6: model, 8: "thinking", 11: org uuid, 15: profile, 21: unix ts}`.
|
|
7
|
+
* Field 6 names the model that actually produced the block — a concrete
|
|
8
|
+
* snapshot (`claude-haiku-4-5-20251001`) or an internal codename
|
|
9
|
+
* (`numbat-v6-…`) — which lets a caller notice a gateway serving something other
|
|
10
|
+
* than the requested model. The v4 outer format (`CAQS…`) ships a header
|
|
11
|
+
* without field 6; the decoder then yields nothing.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const OUTER_ENVELOPE_FIELD = 2;
|
|
15
|
+
const ENVELOPE_HEADER_FIELD = 1;
|
|
16
|
+
const HEADER_MODEL_FIELD = 6;
|
|
17
|
+
const MAX_MODEL_ID_LENGTH = 128;
|
|
18
|
+
const MODEL_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:/-]*$/;
|
|
19
|
+
|
|
20
|
+
/** Returns the first length-delimited field `field` in `message`, or undefined. */
|
|
21
|
+
function lengthDelimitedField(message: Uint8Array, field: number): Uint8Array | undefined {
|
|
22
|
+
let offset = 0;
|
|
23
|
+
while (offset < message.length) {
|
|
24
|
+
let tag = 0;
|
|
25
|
+
let shift = 0;
|
|
26
|
+
let byte: number;
|
|
27
|
+
do {
|
|
28
|
+
if (offset >= message.length) return undefined;
|
|
29
|
+
byte = message[offset++];
|
|
30
|
+
tag |= (byte & 0x7f) << shift;
|
|
31
|
+
shift += 7;
|
|
32
|
+
} while (byte & 0x80);
|
|
33
|
+
const fieldNumber = tag >>> 3;
|
|
34
|
+
const wireType = tag & 7;
|
|
35
|
+
switch (wireType) {
|
|
36
|
+
case 0: {
|
|
37
|
+
do {
|
|
38
|
+
if (offset >= message.length) return undefined;
|
|
39
|
+
byte = message[offset++];
|
|
40
|
+
} while (byte & 0x80);
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
case 1:
|
|
44
|
+
offset += 8;
|
|
45
|
+
break;
|
|
46
|
+
case 2: {
|
|
47
|
+
let length = 0;
|
|
48
|
+
shift = 0;
|
|
49
|
+
do {
|
|
50
|
+
if (offset >= message.length) return undefined;
|
|
51
|
+
byte = message[offset++];
|
|
52
|
+
length |= (byte & 0x7f) << shift;
|
|
53
|
+
shift += 7;
|
|
54
|
+
} while (byte & 0x80);
|
|
55
|
+
if (offset + length > message.length) return undefined;
|
|
56
|
+
if (fieldNumber === field) return message.subarray(offset, offset + length);
|
|
57
|
+
offset += length;
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
case 5:
|
|
61
|
+
offset += 4;
|
|
62
|
+
break;
|
|
63
|
+
default:
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Model id embedded in an Anthropic thinking signature, or undefined when the
|
|
72
|
+
* signature is malformed, uses a format without a cleartext header, or names
|
|
73
|
+
* nothing resembling a model id.
|
|
74
|
+
*/
|
|
75
|
+
export function servedModelFromAnthropicSignature(signature: string): string | undefined {
|
|
76
|
+
if (signature.length < 8 || signature.length > 8192) return undefined;
|
|
77
|
+
let raw: Uint8Array;
|
|
78
|
+
try {
|
|
79
|
+
raw = Buffer.from(signature, "base64");
|
|
80
|
+
} catch {
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
const envelope = lengthDelimitedField(raw, OUTER_ENVELOPE_FIELD);
|
|
84
|
+
if (!envelope) return undefined;
|
|
85
|
+
const header = lengthDelimitedField(envelope, ENVELOPE_HEADER_FIELD);
|
|
86
|
+
if (!header) return undefined;
|
|
87
|
+
const modelBytes = lengthDelimitedField(header, HEADER_MODEL_FIELD);
|
|
88
|
+
if (!modelBytes || modelBytes.length === 0 || modelBytes.length > MAX_MODEL_ID_LENGTH) return undefined;
|
|
89
|
+
const model = Buffer.from(modelBytes).toString("utf8");
|
|
90
|
+
return MODEL_ID_PATTERN.test(model) ? model : undefined;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Model id from an OpenRouter reasoning item — a `reasoning_details` entry on
|
|
95
|
+
* chat completions or a Responses `reasoning` output item — which forwards the
|
|
96
|
+
* upstream Anthropic signature as `{ format: "anthropic-claude-v1", signature }`.
|
|
97
|
+
* Undefined for other formats.
|
|
98
|
+
*/
|
|
99
|
+
export function servedModelFromOpenRouterReasoning(item: unknown): string | undefined {
|
|
100
|
+
if (!item || typeof item !== "object" || !("format" in item) || !("signature" in item)) return undefined;
|
|
101
|
+
if (item.format !== "anthropic-claude-v1" || typeof item.signature !== "string") return undefined;
|
|
102
|
+
return servedModelFromAnthropicSignature(item.signature);
|
|
103
|
+
}
|
|
@@ -122,6 +122,7 @@ import {
|
|
|
122
122
|
resolveGitHubCopilotBaseUrl,
|
|
123
123
|
wrapFetchForCopilotFallback,
|
|
124
124
|
} from "./github-copilot-headers";
|
|
125
|
+
import { servedModelFromAnthropicSignature } from "./anthropic-signature";
|
|
125
126
|
import { getOpenAIPromptCacheKey } from "./openai-shared";
|
|
126
127
|
import { applyInferenceHeaders } from "./inference-headers";
|
|
127
128
|
import { redactSensitiveCredentials, transformMessages } from "./transform-messages";
|
|
@@ -2588,6 +2589,11 @@ const streamAnthropicOnce = (
|
|
|
2588
2589
|
if (unwrappedThinking !== undefined) {
|
|
2589
2590
|
block.thinking = unwrappedThinking;
|
|
2590
2591
|
block.thinkingSignature = undefined;
|
|
2592
|
+
} else if (!output.upstreamModel && block.thinkingSignature) {
|
|
2593
|
+
// The signature names the model that actually produced the
|
|
2594
|
+
// block; a gateway serving a different model than requested
|
|
2595
|
+
// cannot mint one that says otherwise.
|
|
2596
|
+
output.upstreamModel = servedModelFromAnthropicSignature(block.thinkingSignature);
|
|
2591
2597
|
}
|
|
2592
2598
|
stream.push({ type: "thinking_end", contentIndex, content: block.thinking, partial: output });
|
|
2593
2599
|
} else if (block.type === "anthropicServerTool" && block.block.type === "server_tool_use") {
|
|
@@ -3289,6 +3295,7 @@ const streamAnthropicOnce = (
|
|
|
3289
3295
|
output.content.length = 0;
|
|
3290
3296
|
output.model = model.id;
|
|
3291
3297
|
output.responseId = undefined;
|
|
3298
|
+
output.upstreamModel = undefined;
|
|
3292
3299
|
output.errorMessage = undefined;
|
|
3293
3300
|
output.providerPayload = undefined;
|
|
3294
3301
|
output.usage = createEmptyUsage(copilotDynamicHeaders?.premiumRequests);
|
|
@@ -3318,6 +3325,7 @@ const streamAnthropicOnce = (
|
|
|
3318
3325
|
output.content.length = 0;
|
|
3319
3326
|
output.model = model.id;
|
|
3320
3327
|
output.responseId = undefined;
|
|
3328
|
+
output.upstreamModel = undefined;
|
|
3321
3329
|
output.errorMessage = undefined;
|
|
3322
3330
|
output.inputTransformations = undefined;
|
|
3323
3331
|
output.providerPayload = undefined;
|
|
@@ -3351,6 +3359,7 @@ const streamAnthropicOnce = (
|
|
|
3351
3359
|
output.content.length = 0;
|
|
3352
3360
|
output.model = model.id;
|
|
3353
3361
|
output.responseId = undefined;
|
|
3362
|
+
output.upstreamModel = undefined;
|
|
3354
3363
|
output.errorMessage = undefined;
|
|
3355
3364
|
output.providerPayload = undefined;
|
|
3356
3365
|
output.usage = createEmptyUsage(copilotDynamicHeaders?.premiumRequests);
|
|
@@ -3378,6 +3387,7 @@ const streamAnthropicOnce = (
|
|
|
3378
3387
|
output.content.length = 0;
|
|
3379
3388
|
output.model = model.id;
|
|
3380
3389
|
output.responseId = undefined;
|
|
3390
|
+
output.upstreamModel = undefined;
|
|
3381
3391
|
output.errorMessage = undefined;
|
|
3382
3392
|
output.providerPayload = undefined;
|
|
3383
3393
|
output.usage = createEmptyUsage(copilotDynamicHeaders?.premiumRequests);
|
|
@@ -78,6 +78,7 @@ import {
|
|
|
78
78
|
rememberOpenAIReasoningEffortFallback,
|
|
79
79
|
resolveOpenAIReasoningEffortFallback,
|
|
80
80
|
} from "./openai-reasoning-fallback";
|
|
81
|
+
import { servedModelFromOpenRouterReasoning } from "./anthropic-signature";
|
|
81
82
|
import { resolveCopilotRequestIdentity, wrapFetchForCopilotFallback } from "./github-copilot-headers";
|
|
82
83
|
import {
|
|
83
84
|
applyChatCompletionsReasoningParams,
|
|
@@ -1379,6 +1380,7 @@ const streamOpenAICompletionsOnce = (
|
|
|
1379
1380
|
if (Array.isArray(reasoningDetails)) {
|
|
1380
1381
|
for (const detail of reasoningDetails) {
|
|
1381
1382
|
if (!detail || typeof detail !== "object") continue;
|
|
1383
|
+
if (!output.upstreamModel) output.upstreamModel = servedModelFromOpenRouterReasoning(detail);
|
|
1382
1384
|
const detailObject = detail as { type?: unknown; id?: unknown; data?: unknown };
|
|
1383
1385
|
if (detailObject.type === "reasoning.encrypted" && detailObject.id && detailObject.data) {
|
|
1384
1386
|
const matchingToolCall = output.content.find(
|
|
@@ -97,6 +97,7 @@ import {
|
|
|
97
97
|
hasCopilotVisionInput,
|
|
98
98
|
resolveGitHubCopilotBaseUrl,
|
|
99
99
|
} from "./github-copilot-headers";
|
|
100
|
+
import { servedModelFromOpenRouterReasoning } from "./anthropic-signature";
|
|
100
101
|
import type { ChatCompletionCreateParamsStreaming } from "./openai-chat-wire";
|
|
101
102
|
import type { InputItem } from "./openai-codex/request-transformer";
|
|
102
103
|
import type {
|
|
@@ -3250,6 +3251,7 @@ export async function processResponsesStream<TApi extends Api>(
|
|
|
3250
3251
|
if (reasoningBlock) {
|
|
3251
3252
|
reasoningBlock.thinking = finalizeReasoningThinking(item, reasoningBlock.thinking);
|
|
3252
3253
|
reasoningBlock.thinkingSignature = JSON.stringify(item);
|
|
3254
|
+
if (!output.upstreamModel) output.upstreamModel = servedModelFromOpenRouterReasoning(item);
|
|
3253
3255
|
stream.push({
|
|
3254
3256
|
type: "thinking_end",
|
|
3255
3257
|
contentIndex: contentIndexOf(reasoningBlock),
|
package/src/registry/build.ts
CHANGED
|
@@ -62,6 +62,7 @@ export function buildProviderDefinition(
|
|
|
62
62
|
...transport,
|
|
63
63
|
...(envKeys !== undefined ? { envKeys } : {}),
|
|
64
64
|
...(policy.allowsMissingApiKey !== undefined ? { allowsMissingApiKey: policy.allowsMissingApiKey } : {}),
|
|
65
|
+
...(policy.nativeAuthApis !== undefined ? { nativeAuthApis: policy.nativeAuthApis } : {}),
|
|
65
66
|
...(policy.available !== undefined ? { available: policy.available } : {}),
|
|
66
67
|
...(policy.showInLoginList !== undefined ? { showInLoginList: policy.showInLoginList } : {}),
|
|
67
68
|
...(policy.storeAs !== undefined ? { storeCredentialsAs: policy.storeAs } : {}),
|
package/src/registry/types.ts
CHANGED
|
@@ -69,6 +69,8 @@ export interface ProviderDefinition {
|
|
|
69
69
|
readonly envKeys?: KeyResolver;
|
|
70
70
|
/** Provider transport can authenticate without a resolved API-key string. */
|
|
71
71
|
readonly allowsMissingApiKey?: boolean;
|
|
72
|
+
/** APIs whose provider transport resolves credentials without a stored account. */
|
|
73
|
+
readonly nativeAuthApis?: readonly string[];
|
|
72
74
|
/** Provider-owned model normalization that must run before API-specific option mapping. */
|
|
73
75
|
readonly prepareModel?: ProviderModelPreparer;
|
|
74
76
|
/** Provider-owned request shaping applied before generic API dispatch. */
|
package/src/stream.ts
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
requireSupportedEffort,
|
|
14
14
|
resolveWireModelId,
|
|
15
15
|
} from "@oh-my-pi/pi-catalog/model-thinking";
|
|
16
|
-
import {
|
|
16
|
+
import { providerEntries } from "@oh-my-pi/pi-catalog/compat/providers";
|
|
17
17
|
import { CODEX_BASE_URL } from "@oh-my-pi/pi-catalog/wire/codex";
|
|
18
18
|
import { $env, $pickenv, getProviderInFlightRoot, isEnoent, logger } from "@oh-my-pi/pi-utils";
|
|
19
19
|
import { getCustomApi } from "./api-registry";
|
|
@@ -815,11 +815,12 @@ const LEGACY_ENV_KEYS: Record<string, KeyResolver> = {
|
|
|
815
815
|
};
|
|
816
816
|
|
|
817
817
|
/**
|
|
818
|
-
* Env fallbacks derived from the catalog
|
|
819
|
-
* provider env-var names.
|
|
820
|
-
* (Foundry/ADC/Bedrock
|
|
818
|
+
* Env fallbacks derived from the catalog provider entries (`env` in
|
|
819
|
+
* `providers/<id>.kdl`) — the single source for plain provider env-var names.
|
|
820
|
+
* Registry defs override with computed resolvers (Foundry/ADC/Bedrock
|
|
821
|
+
* probes); legacy non-provider keys merge last.
|
|
821
822
|
*/
|
|
822
|
-
const CATALOG_ENTRY_ENV_KEYS = (
|
|
823
|
+
const CATALOG_ENTRY_ENV_KEYS = Object.values(providerEntries()).flatMap(provider => {
|
|
823
824
|
const envVars = provider.envVars;
|
|
824
825
|
if (!envVars || envVars.length === 0) return [];
|
|
825
826
|
const resolver: KeyResolver = envVars.length === 1 ? envVars[0] : () => $pickenv(...envVars);
|
package/src/types.ts
CHANGED
|
@@ -1041,7 +1041,13 @@ export interface AssistantMessage {
|
|
|
1041
1041
|
* providers that expose no such field.
|
|
1042
1042
|
*/
|
|
1043
1043
|
upstreamProvider?: string;
|
|
1044
|
-
/**
|
|
1044
|
+
/**
|
|
1045
|
+
* Concrete model that produced this turn when it is knowable independently
|
|
1046
|
+
* of the requested id: reported by a router that selected one, or recovered
|
|
1047
|
+
* from a signed thinking block (Anthropic signatures name the serving
|
|
1048
|
+
* model). Compared against `model` to notice a gateway serving something
|
|
1049
|
+
* other than what was requested.
|
|
1050
|
+
*/
|
|
1045
1051
|
upstreamModel?: string;
|
|
1046
1052
|
usage: Usage;
|
|
1047
1053
|
stopReason: StopReason;
|
|
@@ -20,7 +20,16 @@ export type CapturedHttpErrorResponse = {
|
|
|
20
20
|
bodyJson?: unknown;
|
|
21
21
|
};
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
/**
|
|
24
|
+
* Matches any header name whose value carries a credential, so a persisted dump
|
|
25
|
+
* never leaks one. A substring match — not a hand-maintained allow-list — so
|
|
26
|
+
* provider-specific auth headers (`x-goog-api-key`, `x-amz-security-token`, …)
|
|
27
|
+
* are redacted without enumerating every provider's spelling; the fixed list it
|
|
28
|
+
* replaced silently leaked any auth header it did not name. Redacting a benign
|
|
29
|
+
* header that happens to match is harmless: dumps exist to diagnose the request
|
|
30
|
+
* body, not its transport headers.
|
|
31
|
+
*/
|
|
32
|
+
const SENSITIVE_HEADER_PATTERN = /key|token|secret|auth|credential|cookie/i;
|
|
24
33
|
|
|
25
34
|
/**
|
|
26
35
|
* Build the JSON persisted for a rejected request. Request fields stay at the
|
|
@@ -167,7 +176,7 @@ function redactHeaders(headers: Record<string, string> | undefined): Record<stri
|
|
|
167
176
|
|
|
168
177
|
const redacted: Record<string, string> = {};
|
|
169
178
|
for (const [key, value] of Object.entries(headers)) {
|
|
170
|
-
if (
|
|
179
|
+
if (SENSITIVE_HEADER_PATTERN.test(key)) {
|
|
171
180
|
redacted[key] = "[redacted]";
|
|
172
181
|
continue;
|
|
173
182
|
}
|