@oh-my-pi/pi-ai 17.0.3 → 17.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [17.0.4] - 2026-07-18
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Fixed Kimi Code usage reports dropping the 5h window reset time (`omp usage` showed no "resets in …" for the 5h limit): the API returns `resetTime` on the limit `detail`, not on `window`, so the parsed row-level reset is now carried onto the window when the window itself has none.
|
|
10
|
+
- Made Kimi device-id persistence best-effort: a missing or unwritable `~/.omp/agent` directory no longer throws during Kimi header construction, which silently nulled every `kimi-code` usage probe on fresh installs.
|
|
11
|
+
- Coerced boolean tool-schema subschemas to MFJS object forms for native Moonshot/Kimi endpoints, preventing the task tool's `outputSchema` field from causing HTTP 400 responses ([#5952](https://github.com/can1357/oh-my-pi/issues/5952)).
|
|
12
|
+
|
|
5
13
|
## [17.0.3] - 2026-07-17
|
|
6
14
|
|
|
7
15
|
### Fixed
|
|
@@ -2,8 +2,11 @@ import { type DescriptionSpillFormat } from "./spill.js";
|
|
|
2
2
|
import { type JsonObject } from "./types.js";
|
|
3
3
|
export type ResidualSchemaIncompatibility = "type-array" | "type-null" | "nullable" | "combiners" | "not";
|
|
4
4
|
export interface NormalizeSchemaOptions {
|
|
5
|
-
/**
|
|
6
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Coerce boolean subschemas to object forms. `standard` preserves `false`
|
|
7
|
+
* with `not`; `permissive` uses `{}` when the provider cannot express it.
|
|
8
|
+
*/
|
|
9
|
+
coerceBooleanSubschemas?: "standard" | "permissive";
|
|
7
10
|
unsupportedFields: (key: string) => boolean;
|
|
8
11
|
normalizeFieldNames: boolean;
|
|
9
12
|
collapseNullFields: boolean;
|
|
@@ -62,6 +65,9 @@ export declare function normalizeSchemaForMCP(value: unknown): unknown;
|
|
|
62
65
|
* `default` and `description` are MFJS Meta Data fields and are preserved.
|
|
63
66
|
* - `additionalProperties` (boolean or schema) and `type: "null"` (incl.
|
|
64
67
|
* inside `anyOf`) are kept.
|
|
68
|
+
* - Boolean subschemas are object-coerced; MFJS has no exact `false` schema,
|
|
69
|
+
* so both values become the permissive empty schema while local tool
|
|
70
|
+
* validation remains authoritative.
|
|
65
71
|
*
|
|
66
72
|
* Out of scope (absent from the built-in tool surface, spec-ambiguous to
|
|
67
73
|
* rewrite blindly): `allOf` intersection merging, external/recursive `$ref`,
|
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.0.
|
|
4
|
+
"version": "17.0.4",
|
|
5
5
|
"description": "Unified LLM API with automatic model discovery and provider configuration",
|
|
6
6
|
"homepage": "https://omp.sh",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -38,9 +38,9 @@
|
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"@bufbuild/protobuf": "^2.12.1",
|
|
41
|
-
"@oh-my-pi/pi-catalog": "17.0.
|
|
42
|
-
"@oh-my-pi/pi-utils": "17.0.
|
|
43
|
-
"@oh-my-pi/pi-wire": "17.0.
|
|
41
|
+
"@oh-my-pi/pi-catalog": "17.0.4",
|
|
42
|
+
"@oh-my-pi/pi-utils": "17.0.4",
|
|
43
|
+
"@oh-my-pi/pi-wire": "17.0.4",
|
|
44
44
|
"arktype": "2.2.3",
|
|
45
45
|
"zod": "^4"
|
|
46
46
|
},
|
|
@@ -38,6 +38,7 @@ import {
|
|
|
38
38
|
adaptSchemaForStrict,
|
|
39
39
|
findStrictToolSchemaViolation,
|
|
40
40
|
NO_STRICT,
|
|
41
|
+
normalizeSchemaForMoonshot,
|
|
41
42
|
sanitizeSchemaForOpenAIResponses,
|
|
42
43
|
toolWireSchema,
|
|
43
44
|
} from "../utils/schema";
|
|
@@ -999,7 +1000,15 @@ export function convertTools(
|
|
|
999
1000
|
}
|
|
1000
1001
|
const strict = !NO_STRICT && strictMode && tool.strict !== false;
|
|
1001
1002
|
const baseParameters = toolWireSchema(tool);
|
|
1002
|
-
|
|
1003
|
+
// MFJS must run AFTER the Responses sanitizer: the sanitizer normalizes
|
|
1004
|
+
// `{}` → `true` (issue #1179), and Moonshot's validator rejects boolean
|
|
1005
|
+
// subschemas ("property schema … must be an object"), so the Moonshot
|
|
1006
|
+
// pass re-coerces them last.
|
|
1007
|
+
const sanitized = sanitizeSchemaForOpenAIResponses(baseParameters);
|
|
1008
|
+
const responseParameters =
|
|
1009
|
+
model.compat.toolSchemaFlavor === "moonshot-mfjs"
|
|
1010
|
+
? (normalizeSchemaForMoonshot(sanitized) as Record<string, unknown>)
|
|
1011
|
+
: sanitized;
|
|
1003
1012
|
const { schema: parameters, strict: effectiveStrict } = adaptSchemaForStrict(responseParameters, strict);
|
|
1004
1013
|
// Quarantine a tool whose emitted schema carries a provider-rejecting
|
|
1005
1014
|
// enum/const-vs-type contradiction: dropping just that tool keeps the rest
|
|
@@ -7,7 +7,7 @@ import * as fs from "node:fs";
|
|
|
7
7
|
import * as os from "node:os";
|
|
8
8
|
import * as path from "node:path";
|
|
9
9
|
import { scheduler } from "node:timers/promises";
|
|
10
|
-
import { $env, getAgentDir
|
|
10
|
+
import { $env, getAgentDir } from "@oh-my-pi/pi-utils";
|
|
11
11
|
import packageJson from "../../../package.json" with { type: "json" };
|
|
12
12
|
import * as AIError from "../../error";
|
|
13
13
|
import type { OAuthController, OAuthCredentials } from "./types";
|
|
@@ -57,21 +57,29 @@ function getDeviceModel(): string {
|
|
|
57
57
|
return formatDeviceModel(label, release, arch);
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
// Device id identifies this install to Kimi. Persistence is best-effort: a
|
|
61
|
+
// missing/unwritable agent dir must never break header construction (and with
|
|
62
|
+
// it every usage probe / request that spreads getKimiCommonHeaders()) — fall
|
|
63
|
+
// back to a per-process ephemeral id instead.
|
|
60
64
|
let getDeviceId = (): string => {
|
|
61
65
|
const deviceIdPath = path.join(getAgentDir(), DEVICE_ID_FILENAME);
|
|
62
66
|
try {
|
|
63
|
-
const existing = fs.readFileSync(deviceIdPath, "utf-8");
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
return trimmed;
|
|
67
|
+
const existing = fs.readFileSync(deviceIdPath, "utf-8").trim();
|
|
68
|
+
if (existing) {
|
|
69
|
+
getDeviceId = () => existing;
|
|
70
|
+
return existing;
|
|
68
71
|
}
|
|
69
|
-
} catch
|
|
70
|
-
|
|
72
|
+
} catch {
|
|
73
|
+
// Unreadable device-id file: regenerate below.
|
|
71
74
|
}
|
|
72
75
|
|
|
73
76
|
const deviceId = crypto.randomUUID().replace(/-/g, "");
|
|
74
|
-
|
|
77
|
+
try {
|
|
78
|
+
fs.mkdirSync(path.dirname(deviceIdPath), { recursive: true });
|
|
79
|
+
fs.writeFileSync(deviceIdPath, `${deviceId}\n`, { mode: 0o600 });
|
|
80
|
+
} catch {
|
|
81
|
+
// Persist failure → ephemeral id for this process.
|
|
82
|
+
}
|
|
75
83
|
getDeviceId = () => deviceId;
|
|
76
84
|
return deviceId;
|
|
77
85
|
};
|
package/src/usage/kimi.ts
CHANGED
|
@@ -144,15 +144,21 @@ function buildUsageStatus(amount: UsageAmount): UsageStatus {
|
|
|
144
144
|
}
|
|
145
145
|
|
|
146
146
|
function toUsageLimit(row: KimiUsageRow, provider: string, index: number, accountId?: string): UsageLimit {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
147
|
+
// Kimi puts `resetTime` on the limit `detail`, not on `window`, so a
|
|
148
|
+
// window built from `duration`/`timeUnit` alone carries no resetsAt.
|
|
149
|
+
// Fall back to the row-level reset so `omp usage` can render
|
|
150
|
+
// "resets in …" for the 5h window too.
|
|
151
|
+
const window: UsageWindow | undefined = row.window
|
|
152
|
+
? row.window.resetsAt !== undefined || row.resetsAt === undefined
|
|
153
|
+
? row.window
|
|
154
|
+
: { ...row.window, resetsAt: row.resetsAt }
|
|
155
|
+
: row.resetsAt
|
|
150
156
|
? {
|
|
151
157
|
id: "default",
|
|
152
158
|
label: "Usage window",
|
|
153
159
|
resetsAt: row.resetsAt,
|
|
154
160
|
}
|
|
155
|
-
: undefined
|
|
161
|
+
: undefined;
|
|
156
162
|
|
|
157
163
|
const amount = buildUsageAmount(row);
|
|
158
164
|
return {
|
|
@@ -29,8 +29,11 @@ import { decontaminateZodInstance } from "./zod-decontaminate";
|
|
|
29
29
|
export type ResidualSchemaIncompatibility = "type-array" | "type-null" | "nullable" | "combiners" | "not";
|
|
30
30
|
|
|
31
31
|
export interface NormalizeSchemaOptions {
|
|
32
|
-
/**
|
|
33
|
-
|
|
32
|
+
/**
|
|
33
|
+
* Coerce boolean subschemas to object forms. `standard` preserves `false`
|
|
34
|
+
* with `not`; `permissive` uses `{}` when the provider cannot express it.
|
|
35
|
+
*/
|
|
36
|
+
coerceBooleanSubschemas?: "standard" | "permissive";
|
|
34
37
|
unsupportedFields: (key: string) => boolean;
|
|
35
38
|
normalizeFieldNames: boolean;
|
|
36
39
|
collapseNullFields: boolean;
|
|
@@ -284,12 +287,12 @@ function normalizeSchemaNode(value: unknown, options: NormalizeSchemaWalkOptions
|
|
|
284
287
|
}
|
|
285
288
|
if (typeof value === "boolean") {
|
|
286
289
|
// A bare boolean is a JSON Schema subschema only in a subschema slot.
|
|
287
|
-
//
|
|
288
|
-
//
|
|
289
|
-
//
|
|
290
|
-
|
|
291
|
-
if (!
|
|
292
|
-
return value ? {} : { not: {} };
|
|
290
|
+
// Some provider wires have no boolean-schema representation: `true`
|
|
291
|
+
// becomes `{}`; `false` uses `not` when supported, or the permissive
|
|
292
|
+
// `{}` fallback when the provider cannot express an impossible schema.
|
|
293
|
+
const mode = options.coerceBooleanSubschemas;
|
|
294
|
+
if (!mode || !options.booleanIsSubschema) return value;
|
|
295
|
+
return value || mode === "permissive" ? {} : { not: {} };
|
|
293
296
|
}
|
|
294
297
|
if (!isJsonObject(value)) {
|
|
295
298
|
return value;
|
|
@@ -993,7 +996,7 @@ export function normalizeSchema(value: unknown, options: NormalizeSchemaOptions)
|
|
|
993
996
|
|
|
994
997
|
export function normalizeSchemaForGoogle(value: unknown): unknown {
|
|
995
998
|
return normalizeSchema(value, {
|
|
996
|
-
coerceBooleanSubschemas:
|
|
999
|
+
coerceBooleanSubschemas: "standard",
|
|
997
1000
|
unsupportedFields: isGoogleUnsupportedSchemaField,
|
|
998
1001
|
normalizeFieldNames: true,
|
|
999
1002
|
collapseNullFields: true,
|
|
@@ -1015,7 +1018,7 @@ export function normalizeSchemaForGoogle(value: unknown): unknown {
|
|
|
1015
1018
|
|
|
1016
1019
|
export function normalizeSchemaForCCA(value: unknown): unknown {
|
|
1017
1020
|
return normalizeSchema(value, {
|
|
1018
|
-
coerceBooleanSubschemas:
|
|
1021
|
+
coerceBooleanSubschemas: "standard",
|
|
1019
1022
|
unsupportedFields: isGoogleUnsupportedSchemaField,
|
|
1020
1023
|
normalizeFieldNames: true,
|
|
1021
1024
|
collapseNullFields: false,
|
|
@@ -1080,6 +1083,9 @@ export function normalizeSchemaForMCP(value: unknown): unknown {
|
|
|
1080
1083
|
* `default` and `description` are MFJS Meta Data fields and are preserved.
|
|
1081
1084
|
* - `additionalProperties` (boolean or schema) and `type: "null"` (incl.
|
|
1082
1085
|
* inside `anyOf`) are kept.
|
|
1086
|
+
* - Boolean subschemas are object-coerced; MFJS has no exact `false` schema,
|
|
1087
|
+
* so both values become the permissive empty schema while local tool
|
|
1088
|
+
* validation remains authoritative.
|
|
1083
1089
|
*
|
|
1084
1090
|
* Out of scope (absent from the built-in tool surface, spec-ambiguous to
|
|
1085
1091
|
* rewrite blindly): `allOf` intersection merging, external/recursive `$ref`,
|
|
@@ -1087,6 +1093,7 @@ export function normalizeSchemaForMCP(value: unknown): unknown {
|
|
|
1087
1093
|
*/
|
|
1088
1094
|
export function normalizeSchemaForMoonshot(value: unknown): unknown {
|
|
1089
1095
|
return normalizeSchema(value, {
|
|
1096
|
+
coerceBooleanSubschemas: "permissive",
|
|
1090
1097
|
unsupportedFields: isMoonshotUnsupportedSchemaField,
|
|
1091
1098
|
normalizeFieldNames: false,
|
|
1092
1099
|
collapseNullFields: false,
|