@oh-my-pi/pi-ai 18.2.9 → 18.2.11
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 +8 -14
- package/dist/types/usage/claude-reset.d.ts +8 -3
- package/package.json +6 -6
- package/src/providers/anthropic.ts +19 -10
- package/src/usage/alibaba-token-plan.ts +14 -3
- package/src/usage/claude-reset.ts +24 -21
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [18.2.11] - 2026-09-23
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Fixed Claude Opus 5.5 not applying a mid-session switch to high-effort reasoning when the session started without an explicit effort setting.
|
|
10
|
+
- Fixed Alibaba Token Plan monthly quotas not appearing in usage reports or the status line.
|
|
11
|
+
|
|
5
12
|
## [18.2.9] - 2026-09-22
|
|
6
13
|
|
|
7
14
|
### Added
|
|
@@ -2274,17 +2281,4 @@
|
|
|
2274
2281
|
- Preserved Anthropic `stop_details` on assistant messages so refusal and sensitive classifier stops remain structurally visible to callers. ([#2290](https://github.com/can1357/oh-my-pi/issues/2290))
|
|
2275
2282
|
- Fixed OpenAI Responses, Azure OpenAI Responses, and OpenAI Completions streams hanging until the 120s idle watchdog errored the turn when a provider delivers the terminal frame but never sends `[DONE]` nor closes the connection. `processResponsesStream` now breaks out of the event loop on `response.completed`/`response.incomplete` (mirroring the Codex websocket/SSE terminal break), and the completions consumer breaks once `finish_reason` plus a usage payload arrived — or, for hosts that never send usage, ends the stream cleanly via a short post-finish grace window (`iterateWithTerminalGrace`) that aborts the transport to release the socket.
|
|
2276
2283
|
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
### Added
|
|
2280
|
-
|
|
2281
|
-
- Added optional `ImageContent.detail` (`"auto" | "low" | "high" | "original"`): an OpenAI resolution hint forwarded by the `openai-responses` serializers (default stays `auto`) and by `openai-completions` for the values Chat Completions supports. `"original"` preserves native resolution — required for snapcompact frames, whose pixel-font glyphs do not survive the default downscale. Providers without a detail knob ignore the field.
|
|
2282
|
-
|
|
2283
|
-
### Fixed
|
|
2284
|
-
|
|
2285
|
-
- Fixed OpenRouter DeepSeek V4 strict tool schemas nesting `anyOf` inside the nullable wrapper for optional unions, which produced a branch without `type` and triggered OpenRouter's `Invalid tool parameters schema : field anyOf: missing field type` 400. ([#2270](https://github.com/can1357/oh-my-pi/issues/2270))
|
|
2286
|
-
- Hardened strict tool-schema handling beyond the optional-union case: `enforceStrictSchema` now splices natively nested pure unions into the parent `anyOf` (only when the inner node carries no constraining siblings, since sibling keywords are conjunctive with `anyOf`), so source schemas with nested unions no longer produce type-less `anyOf` branches that strict upstream validators reject. ([#2270](https://github.com/can1357/oh-my-pi/issues/2270))
|
|
2287
|
-
- Made the openai-completions non-strict retry reachable for `"mixed"` strict mode (previously gated to `all_strict`, i.e. Cerebras only) and taught it to recognize upstream tool-schema validation 400s (`Invalid tool parameters schema …`, `Invalid schema for function …`). A matching rejection now retries the request with base (non-strict) schemas and persists `strictToolsDisabled` on the provider session, so later requests skip the doomed strict attempt instead of paying a 400 + retry round-trip each turn. ([#2270](https://github.com/can1357/oh-my-pi/issues/2270))
|
|
2288
|
-
- Cross-model `anthropic-messages → anthropic-messages` continuations now preserve prior assistant turns' reasoning chains end-to-end: every prior `thinking`/`redactedThinking` block survives (not just the latest surviving assistant), and third-party ↔ third-party replays keep their signatures intact so the reasoning chain stays signed for the next turn. Signatures are stripped (and any `redacted_thinking` sibling without a native landing spot is dropped) only when an official Anthropic endpoint is on either end of the replay — official Anthropic cryptographically binds reasoning signatures to its key+session+model, while compatible reasoning endpoints (Z.AI, DeepSeek, custom anthropic-messages providers configured via `models.yaml`) treat them as opaque continuation hints. Source-side official detection uses the canonical catalog provider id `"anthropic"` (assistant messages carry no `baseUrl`); target-side detection reuses the baked `compat.officialEndpoint` flag. Latest-turn byte-for-byte behavior (Anthropic's "thinking blocks in the latest assistant message cannot be modified" rule) and existing aborted/errored last-block sanitization are unchanged. ([#2257](https://github.com/can1357/oh-my-pi/issues/2257), [#2265](https://github.com/can1357/oh-my-pi/issues/2265))
|
|
2289
|
-
|
|
2290
|
-
Older entries are archived in [packages/ai/CHANGELOG.md@1f7329fc2c7c](https://github.com/can1357/oh-my-pi/blob/1f7329fc2c7c366b38731738e0db9c170f9bb348/packages/ai/CHANGELOG.md).
|
|
2284
|
+
Older entries are archived in [packages/ai/CHANGELOG.md@d58593a30902](https://github.com/can1357/oh-my-pi/blob/d58593a3090258473304608d68ffd1f620e6b695/packages/ai/CHANGELOG.md).
|
|
@@ -25,9 +25,14 @@ export interface ClaudeResetConsumeResult {
|
|
|
25
25
|
raw?: unknown;
|
|
26
26
|
}
|
|
27
27
|
/**
|
|
28
|
-
* Reuse program blocks when a normal usage response actually
|
|
29
|
-
*
|
|
30
|
-
* the
|
|
28
|
+
* Reuse program blocks when a normal usage response actually evaluated them.
|
|
29
|
+
* Anthropic lists every promo program as a key on the plain `/usage` body and
|
|
30
|
+
* leaves the value `null` until the matching probe query (`?cedar_ember=1`,
|
|
31
|
+
* `?at_wall=1`) asks for an evaluation, so a `null` block is "unknown", never
|
|
32
|
+
* "no resets". Only an object block is an answer; anything else returns `null`
|
|
33
|
+
* so the caller falls through to {@link listClaudeResetCredits}. A lone
|
|
34
|
+
* non-redeemable Cedar block is likewise inconclusive because Juniper requires
|
|
35
|
+
* the separate at-wall read.
|
|
31
36
|
*
|
|
32
37
|
* @internal
|
|
33
38
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oh-my-pi/pi-ai",
|
|
3
|
-
"version": "18.2.
|
|
3
|
+
"version": "18.2.11",
|
|
4
4
|
"description": "Unified LLM API with automatic model discovery and provider configuration",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -152,11 +152,11 @@
|
|
|
152
152
|
"fmt": "oxfmt --no-error-on-unmatched-pattern 'src/**/*.{ts,tsx}' '{test,bench,examples,scripts}/**/*.ts' '*.ts'"
|
|
153
153
|
},
|
|
154
154
|
"dependencies": {
|
|
155
|
-
"@oh-my-pi/omptype": "18.2.
|
|
156
|
-
"@oh-my-pi/pi-catalog": "18.2.
|
|
157
|
-
"@oh-my-pi/pi-natives": "18.2.
|
|
158
|
-
"@oh-my-pi/pi-utils": "18.2.
|
|
159
|
-
"@oh-my-pi/pi-wire": "18.2.
|
|
155
|
+
"@oh-my-pi/omptype": "18.2.11",
|
|
156
|
+
"@oh-my-pi/pi-catalog": "18.2.11",
|
|
157
|
+
"@oh-my-pi/pi-natives": "18.2.11",
|
|
158
|
+
"@oh-my-pi/pi-utils": "18.2.11",
|
|
159
|
+
"@oh-my-pi/pi-wire": "18.2.11"
|
|
160
160
|
},
|
|
161
161
|
"devDependencies": {
|
|
162
162
|
"@types/bun": "^1.3.14"
|
|
@@ -466,8 +466,11 @@ type AnthropicControlState = {
|
|
|
466
466
|
stableSystemBlocks: AnthropicSystemBlock[] | undefined;
|
|
467
467
|
systemFingerprint: string | undefined;
|
|
468
468
|
controlTransitions: AnthropicControlTransition[];
|
|
469
|
-
|
|
469
|
+
/** Whether the effort baseline was captured; `undefined` efforts are a valid baseline. */
|
|
470
|
+
effortBaselined: boolean;
|
|
471
|
+
/** Top-level `output_config.effort` of the baseline request; `undefined` = API default. */
|
|
470
472
|
baseEffortWire: AnthropicOutputEffort | undefined;
|
|
473
|
+
/** Effort in force at the conversation tail; `undefined` = API default. */
|
|
471
474
|
currentEffort: AnthropicOutputEffort | undefined;
|
|
472
475
|
};
|
|
473
476
|
|
|
@@ -504,7 +507,7 @@ function createAnthropicControlState(): AnthropicControlState {
|
|
|
504
507
|
stableSystemBlocks: undefined,
|
|
505
508
|
systemFingerprint: undefined,
|
|
506
509
|
controlTransitions: [],
|
|
507
|
-
|
|
510
|
+
effortBaselined: false,
|
|
508
511
|
baseEffortWire: undefined,
|
|
509
512
|
currentEffort: undefined,
|
|
510
513
|
};
|
|
@@ -3999,7 +4002,7 @@ function resetAnthropicControlState(state: AnthropicControlState): void {
|
|
|
3999
4002
|
state.stableSystemBlocks = undefined;
|
|
4000
4003
|
state.systemFingerprint = undefined;
|
|
4001
4004
|
state.controlTransitions = [];
|
|
4002
|
-
state.
|
|
4005
|
+
state.effortBaselined = false;
|
|
4003
4006
|
state.baseEffortWire = undefined;
|
|
4004
4007
|
state.currentEffort = undefined;
|
|
4005
4008
|
}
|
|
@@ -4202,6 +4205,13 @@ function planStableAnthropicTools(
|
|
|
4202
4205
|
* later changes as per-message effort. Anthropic applies a system message's
|
|
4203
4206
|
* `output_config.effort` from the next `user` turn on, so the control is
|
|
4204
4207
|
* anchored before the latest user message to take effect on this response.
|
|
4208
|
+
*
|
|
4209
|
+
* An omitted effort means the API's per-model default (`medium` on Opus 5.5,
|
|
4210
|
+
* `high` elsewhere), so it is tracked as its own state rather than assumed to
|
|
4211
|
+
* be any concrete level: every later explicit level is sent as a control. A
|
|
4212
|
+
* per-message control cannot express "back to the API default", so a request
|
|
4213
|
+
* that drops its effort mid-session keeps the level already in force instead
|
|
4214
|
+
* of rewriting the top-level value and invalidating the cache.
|
|
4205
4215
|
*/
|
|
4206
4216
|
function planStableAnthropicEffort(
|
|
4207
4217
|
current: AnthropicOutputEffort | undefined,
|
|
@@ -4210,18 +4220,17 @@ function planStableAnthropicEffort(
|
|
|
4210
4220
|
enabled: boolean,
|
|
4211
4221
|
): AnthropicOutputEffort | undefined {
|
|
4212
4222
|
if (!state || !enabled) return current;
|
|
4213
|
-
|
|
4214
|
-
|
|
4215
|
-
state.baseEffort = effective;
|
|
4223
|
+
if (!state.effortBaselined) {
|
|
4224
|
+
state.effortBaselined = true;
|
|
4216
4225
|
state.baseEffortWire = current;
|
|
4217
|
-
state.currentEffort =
|
|
4226
|
+
state.currentEffort = current;
|
|
4218
4227
|
return current;
|
|
4219
4228
|
}
|
|
4220
|
-
if (state.currentEffort !==
|
|
4229
|
+
if (current !== undefined && state.currentEffort !== current) {
|
|
4221
4230
|
const lastUserIndex = messages.findLastIndex(message => message.role === "user");
|
|
4222
4231
|
const messageCount = lastUserIndex >= 0 ? lastUserIndex : messages.length;
|
|
4223
|
-
recordAnthropicControlTransition(state, messages, messageCount, [],
|
|
4224
|
-
state.currentEffort =
|
|
4232
|
+
recordAnthropicControlTransition(state, messages, messageCount, [], current);
|
|
4233
|
+
state.currentEffort = current;
|
|
4225
4234
|
}
|
|
4226
4235
|
return state.baseEffortWire;
|
|
4227
4236
|
}
|
|
@@ -93,9 +93,9 @@ function usageStatus(usedFraction: number): UsageLimit["status"] {
|
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
function buildLimit(
|
|
96
|
-
id: "5h" | "7d",
|
|
96
|
+
id: "5h" | "7d" | "monthly",
|
|
97
97
|
label: string,
|
|
98
|
-
durationMs: number,
|
|
98
|
+
durationMs: number | undefined,
|
|
99
99
|
usedFraction: number | undefined,
|
|
100
100
|
resetsAt: number | undefined,
|
|
101
101
|
accountId: string | undefined,
|
|
@@ -105,7 +105,7 @@ function buildLimit(
|
|
|
105
105
|
id: `credits:${id}`,
|
|
106
106
|
label,
|
|
107
107
|
scope: { provider: PROVIDER, ...(accountId ? { accountId } : {}), windowId: id },
|
|
108
|
-
window: { id, label, durationMs, ...(resetsAt ? { resetsAt } : {}) },
|
|
108
|
+
window: { id, label, ...(durationMs !== undefined ? { durationMs } : {}), ...(resetsAt ? { resetsAt } : {}) },
|
|
109
109
|
amount: { used: usedFraction * 100, usedFraction, unit: "percent" },
|
|
110
110
|
status: usageStatus(usedFraction),
|
|
111
111
|
};
|
|
@@ -245,6 +245,17 @@ async function fetchAlibabaTokenPlanUsage(
|
|
|
245
245
|
parsePositiveTimestamp(responseData.per1WeekResetTime),
|
|
246
246
|
accountId,
|
|
247
247
|
),
|
|
248
|
+
// Monthly-only plans report just this bucket, and the console never
|
|
249
|
+
// states its span (calendar months differ), so the window carries a
|
|
250
|
+
// reset deadline without a duration.
|
|
251
|
+
buildLimit(
|
|
252
|
+
"monthly",
|
|
253
|
+
"Monthly Credits",
|
|
254
|
+
undefined,
|
|
255
|
+
parseUsedFraction(responseData.per1MonthPercentage),
|
|
256
|
+
parsePositiveTimestamp(responseData.per1MonthResetTime),
|
|
257
|
+
accountId,
|
|
258
|
+
),
|
|
248
259
|
].filter((limit): limit is UsageLimit => limit !== undefined);
|
|
249
260
|
if (limits.length === 0) return null;
|
|
250
261
|
return {
|
|
@@ -448,9 +448,14 @@ async function withResolvedOrganization(
|
|
|
448
448
|
}
|
|
449
449
|
|
|
450
450
|
/**
|
|
451
|
-
* Reuse program blocks when a normal usage response actually
|
|
452
|
-
*
|
|
453
|
-
* the
|
|
451
|
+
* Reuse program blocks when a normal usage response actually evaluated them.
|
|
452
|
+
* Anthropic lists every promo program as a key on the plain `/usage` body and
|
|
453
|
+
* leaves the value `null` until the matching probe query (`?cedar_ember=1`,
|
|
454
|
+
* `?at_wall=1`) asks for an evaluation, so a `null` block is "unknown", never
|
|
455
|
+
* "no resets". Only an object block is an answer; anything else returns `null`
|
|
456
|
+
* so the caller falls through to {@link listClaudeResetCredits}. A lone
|
|
457
|
+
* non-redeemable Cedar block is likewise inconclusive because Juniper requires
|
|
458
|
+
* the separate at-wall read.
|
|
454
459
|
*
|
|
455
460
|
* @internal
|
|
456
461
|
*/
|
|
@@ -459,26 +464,24 @@ export function parseClaudeResetCreditsFromUsagePayload(
|
|
|
459
464
|
orgId?: string,
|
|
460
465
|
baseUrl?: string,
|
|
461
466
|
): ClaudeResetCreditList | null {
|
|
462
|
-
if (!isRecord(payload)
|
|
463
|
-
const
|
|
464
|
-
|
|
467
|
+
if (!isRecord(payload)) return null;
|
|
468
|
+
const cedarBlock = payload[CEDAR_PROGRAM];
|
|
469
|
+
const juniperBlock = payload[JUNIPER_PROGRAM];
|
|
465
470
|
const normalizedOrgId = orgId?.trim();
|
|
466
471
|
const safeOrgId = normalizedOrgId && ORGANIZATION_ID.test(normalizedOrgId) ? normalizedOrgId : undefined;
|
|
467
|
-
|
|
468
|
-
if (
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
...(baseUrl ? { baseUrl } : {}),
|
|
481
|
-
};
|
|
472
|
+
let cedarList: ClaudeResetCreditList | undefined;
|
|
473
|
+
if (isRecord(cedarBlock)) {
|
|
474
|
+
const parsedCedar = parseCedarStatus(cedarBlock);
|
|
475
|
+
if (!parsedCedar.ok || !parsedCedar.status) return null;
|
|
476
|
+
cedarList = normalizeCedarList(parsedCedar.status, safeOrgId, baseUrl);
|
|
477
|
+
if (cedarList.availableCount > 0 || cedarList.nextCreditId) return cedarList;
|
|
478
|
+
}
|
|
479
|
+
if (isRecord(juniperBlock)) {
|
|
480
|
+
const parsedJuniper = parseJuniperStatus(juniperBlock);
|
|
481
|
+
if (!parsedJuniper.ok || !parsedJuniper.status) return null;
|
|
482
|
+
return normalizeJuniperList(parsedJuniper.status, safeOrgId, baseUrl);
|
|
483
|
+
}
|
|
484
|
+
return cedarList ?? null;
|
|
482
485
|
}
|
|
483
486
|
|
|
484
487
|
/**
|