@oh-my-pi/pi-ai 18.4.2 → 18.4.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.
Files changed (74) hide show
  1. package/CHANGELOG.md +30 -41
  2. package/dist/types/auth-broker/protocol.d.ts +12 -0
  3. package/dist/types/dialect/rendering.d.ts +4 -0
  4. package/dist/types/images/openai-hosted.d.ts +1 -1
  5. package/dist/types/images/shared.d.ts +5 -2
  6. package/dist/types/providers/anthropic-wire.d.ts +9 -1
  7. package/dist/types/providers/anthropic.d.ts +17 -0
  8. package/dist/types/providers/aws-sigv4.d.ts +5 -0
  9. package/dist/types/providers/bedrock-anthropic.d.ts +9 -0
  10. package/dist/types/providers/bedrock-request-metadata.d.ts +2 -0
  11. package/dist/types/providers/cursor/interaction-query.d.ts +10 -0
  12. package/dist/types/providers/openai-chat-wire.d.ts +2 -2
  13. package/dist/types/providers/openai-codex/request-transformer.d.ts +1 -1
  14. package/dist/types/providers/openai-responses-wire.d.ts +2 -2
  15. package/dist/types/providers/xai-base-url.d.ts +17 -0
  16. package/dist/types/types.d.ts +14 -3
  17. package/dist/types/usage/commandcode.d.ts +4 -0
  18. package/dist/types/usage/shared.d.ts +13 -1
  19. package/dist/types/utils/event-stream.d.ts +7 -0
  20. package/dist/types/utils/openai-http.d.ts +7 -1
  21. package/package.json +6 -6
  22. package/src/auth-broker/client.ts +1 -12
  23. package/src/auth-broker/protocol.ts +32 -0
  24. package/src/auth-broker/remote-store.ts +4 -40
  25. package/src/auth-broker/server.ts +1 -20
  26. package/src/auth-broker/snapshot-cache.ts +1 -9
  27. package/src/dialect/anthropic.ts +3 -25
  28. package/src/dialect/minimax.ts +3 -24
  29. package/src/dialect/rendering.ts +18 -0
  30. package/src/dialect/thinking.ts +24 -6
  31. package/src/dialect/xml.ts +3 -19
  32. package/src/images/openai-hosted.ts +5 -2
  33. package/src/images/openai-images.ts +10 -4
  34. package/src/images/shared.ts +9 -4
  35. package/src/providers/amazon-bedrock.ts +61 -22
  36. package/src/providers/anthropic-compaction.ts +10 -1
  37. package/src/providers/anthropic-wire.ts +12 -1
  38. package/src/providers/anthropic.ts +58 -15
  39. package/src/providers/aws-eventstream.ts +4 -3
  40. package/src/providers/aws-sigv4.ts +1 -1
  41. package/src/providers/azure-openai-responses.ts +3 -4
  42. package/src/providers/bedrock-anthropic.ts +30 -0
  43. package/src/providers/bedrock-request-metadata.ts +6 -0
  44. package/src/providers/connect-error-detail.ts +1 -5
  45. package/src/providers/cursor/interaction-query.ts +4 -2
  46. package/src/providers/cursor.ts +30 -26
  47. package/src/providers/google-gemini-cli.ts +9 -2
  48. package/src/providers/google-shared.ts +16 -6
  49. package/src/providers/openai-chat-wire.ts +2 -2
  50. package/src/providers/openai-codex/request-transformer.ts +1 -1
  51. package/src/providers/openai-codex-responses.ts +27 -16
  52. package/src/providers/openai-completions.ts +51 -36
  53. package/src/providers/openai-responses-wire.ts +2 -2
  54. package/src/providers/openai-responses.ts +3 -4
  55. package/src/providers/openai-shared.ts +130 -18
  56. package/src/providers/xai-base-url.ts +32 -0
  57. package/src/registry/engine/api-key.ts +8 -3
  58. package/src/types.ts +35 -4
  59. package/src/usage/claude.ts +4 -11
  60. package/src/usage/cline-pass.ts +2 -14
  61. package/src/usage/commandcode.ts +209 -0
  62. package/src/usage/cursor.ts +9 -1
  63. package/src/usage/openai-codex.ts +3 -5
  64. package/src/usage/registry.ts +3 -0
  65. package/src/usage/shared.ts +28 -1
  66. package/src/usage/synthetic.ts +4 -40
  67. package/src/usage/umans.ts +8 -36
  68. package/src/usage/zai.ts +10 -38
  69. package/src/utils/event-stream.ts +38 -2
  70. package/src/utils/http-inspector.ts +4 -8
  71. package/src/utils/openai-http.ts +10 -3
  72. package/src/utils/schema/json-schema-validator.ts +23 -26
  73. package/src/utils/schema/meta-validator.ts +4 -7
  74. package/src/utils/schema/wire.ts +17 -21
@@ -1,5 +1,5 @@
1
1
  import * as path from "node:path";
2
- import { getLogsDir, isBunTestRuntime } from "@oh-my-pi/pi-utils";
2
+ import { getLogsDir, isBunTestRuntime, isRecord } from "@oh-my-pi/pi-utils";
3
3
  import * as AIError from "../error/flags";
4
4
  import { formatErrorMessageWithRetryAfter } from "./retry-after.js";
5
5
 
@@ -252,13 +252,13 @@ function formatCapturedHttpError(captured: CapturedHttpErrorResponse | undefined
252
252
  }
253
253
 
254
254
  function parseCapturedErrorPayload(captured: CapturedHttpErrorResponse): Record<string, unknown> | undefined {
255
- if (isObject(captured.bodyJson)) {
255
+ if (isRecord(captured.bodyJson)) {
256
256
  return captured.bodyJson;
257
257
  }
258
258
  if (!captured.bodyText) return undefined;
259
259
  try {
260
260
  const parsed = JSON.parse(captured.bodyText);
261
- return isObject(parsed) ? parsed : undefined;
261
+ return isRecord(parsed) ? parsed : undefined;
262
262
  } catch {
263
263
  return undefined;
264
264
  }
@@ -266,14 +266,10 @@ function parseCapturedErrorPayload(captured: CapturedHttpErrorResponse): Record<
266
266
 
267
267
  function getObjectProperty(value: Record<string, unknown>, key: string): Record<string, unknown> | undefined {
268
268
  const property = value[key];
269
- return isObject(property) ? property : undefined;
269
+ return isRecord(property) ? property : undefined;
270
270
  }
271
271
 
272
272
  function getStringProperty(value: Record<string, unknown>, key: string): string | undefined {
273
273
  const property = value[key];
274
274
  return typeof property === "string" && property.trim().length > 0 ? property : undefined;
275
275
  }
276
-
277
- function isObject(value: unknown): value is Record<string, unknown> {
278
- return typeof value === "object" && value !== null && !Array.isArray(value);
279
- }
@@ -70,8 +70,14 @@ export interface OpenAIStreamRequestInit {
70
70
  fetch?: FetchImpl;
71
71
  /** Optional caller-specific gate composed with shared transport retry exclusions. */
72
72
  shouldRetryResponse?: (response: Response, bodyText: string) => boolean | Promise<boolean>;
73
- /** Raw wire-frame observer (`onSseEvent` debug pipeline). */
73
+ /**
74
+ * Raw wire-frame observer (`onSseEvent` debug pipeline). Leave it unset
75
+ * when no diagnostic listener exists: any observer turns on per-line raw
76
+ * capture for every frame.
77
+ */
74
78
  onSseEvent?: SseEventObserver;
79
+ /** Called when the stream ends on the OpenAI `[DONE]` sentinel; independent of {@link onSseEvent}. */
80
+ onDoneSentinel?: () => void;
75
81
  }
76
82
 
77
83
  export interface OpenAIStreamHandle<TEvent> {
@@ -117,7 +123,7 @@ export async function postOpenAIStream<TEvent>(init: OpenAIStreamRequestInit): P
117
123
  });
118
124
  }
119
125
  return {
120
- events: decodeStream<TEvent>(response.body, init.signal, init.onSseEvent),
126
+ events: decodeStream<TEvent>(response.body, init.signal, init.onSseEvent, init.onDoneSentinel),
121
127
  response,
122
128
  requestId: response.headers.get("x-request-id"),
123
129
  };
@@ -140,8 +146,9 @@ async function* decodeStream<TEvent>(
140
146
  body: ReadableStream<Uint8Array>,
141
147
  signal: AbortSignal | undefined,
142
148
  onSseEvent: SseEventObserver | undefined,
149
+ onDoneSentinel: (() => void) | undefined,
143
150
  ): AsyncGenerator<TEvent> {
144
- for await (const frame of readSseJsonOrText<TEvent>(body, signal, onSseEvent)) {
151
+ for await (const frame of readSseJsonOrText<TEvent>(body, signal, onSseEvent, onDoneSentinel)) {
145
152
  if (typeof frame === "string") {
146
153
  const inBand = AIError.createInBandProviderErrorFromText(frame);
147
154
  if (inBand) throw inBand;
@@ -12,7 +12,7 @@
12
12
  * Compared to AJV this is single-pass, synchronous, dependency-free, and
13
13
  * tolerates non-standard shapes (`nullable`) that LLM-emitted schemas carry.
14
14
  */
15
- import { logger } from "@oh-my-pi/pi-utils";
15
+ import { isRecord, logger } from "@oh-my-pi/pi-utils";
16
16
  import { areJsonValuesEqual } from "./equality";
17
17
 
18
18
  export interface JsonSchemaValidationIssue {
@@ -65,9 +65,6 @@ function getValueIdentity(ctx: ValidationContext, value: object): number {
65
65
  return id;
66
66
  }
67
67
 
68
- function isJsonObject(value: unknown): value is Record<string, unknown> {
69
- return typeof value === "object" && value !== null && !Array.isArray(value);
70
- }
71
68
  /**
72
69
  * Whether `value` matches every `const`/`enum` discriminator property the
73
70
  * branch declares (with at least one such property present and matching).
@@ -76,13 +73,13 @@ function isJsonObject(value: unknown): value is Record<string, unknown> {
76
73
  * repairs to them; without a unique tag, branch issues are guesses.
77
74
  */
78
75
  function isTagSelectedBranch(branch: unknown, value: unknown): boolean {
79
- if (!isJsonObject(branch) || !isJsonObject(value)) return false;
76
+ if (!isRecord(branch) || !isRecord(value)) return false;
80
77
  const props = branch.properties;
81
- if (!isJsonObject(props)) return false;
78
+ if (!isRecord(props)) return false;
82
79
  let matched = false;
83
80
  for (const key in props) {
84
81
  const propSchema = props[key];
85
- if (!isJsonObject(propSchema)) continue;
82
+ if (!isRecord(propSchema)) continue;
86
83
  const hasConst = Object.hasOwn(propSchema, "const");
87
84
  const enumValues = Array.isArray(propSchema.enum) ? propSchema.enum : undefined;
88
85
  if (!hasConst && !enumValues) continue;
@@ -138,7 +135,7 @@ function matchesJsonSchemaType(value: unknown, type: string): boolean {
138
135
  case "boolean":
139
136
  return typeof value === "boolean";
140
137
  case "object":
141
- return isJsonObject(value);
138
+ return isRecord(value);
142
139
  case "array":
143
140
  return Array.isArray(value);
144
141
  case "null":
@@ -178,7 +175,7 @@ function resolveLocalRef(root: unknown, ref: string): unknown | undefined {
178
175
  let current: unknown = root;
179
176
  for (const rawToken of ref.slice(2).split("/")) {
180
177
  const token = decodePointerToken(rawToken);
181
- if (!isJsonObject(current) && !Array.isArray(current)) return undefined;
178
+ if (!isRecord(current) && !Array.isArray(current)) return undefined;
182
179
  current = (current as Record<string, unknown>)[token];
183
180
  }
184
181
  return current;
@@ -186,7 +183,7 @@ function resolveLocalRef(root: unknown, ref: string): unknown | undefined {
186
183
 
187
184
  /** A presence-only constraint, as distinct from restrictions on a property's value. */
188
185
  function requiresOnlyPropertyPresence(schema: unknown, key: string, root: unknown, depth = 0): boolean {
189
- if (!isJsonObject(schema) || depth >= MAX_REF_DEPTH) return false;
186
+ if (!isRecord(schema) || depth >= MAX_REF_DEPTH) return false;
190
187
  for (const keyword of Object.keys(schema)) {
191
188
  switch (keyword) {
192
189
  case "required":
@@ -217,17 +214,17 @@ export function schemaDefinesProperty(schema: unknown, key: string): boolean {
217
214
  const root = schema;
218
215
  const visited = new WeakMap<object, number>();
219
216
  const visit = (node: unknown, context: "positive" | "negative" | "predicate" = "positive"): boolean => {
220
- if (!isJsonObject(node)) return false;
217
+ if (!isRecord(node)) return false;
221
218
  const contextBit = context === "positive" ? 1 : context === "negative" ? 2 : 4;
222
219
  const previousContexts = visited.get(node) ?? 0;
223
220
  if (previousContexts & contextBit) return false;
224
221
  visited.set(node, previousContexts | contextBit);
225
- if (isJsonObject(node.const) && Object.hasOwn(node.const, key)) return true;
226
- if (Array.isArray(node.enum) && node.enum.some(value => isJsonObject(value) && Object.hasOwn(value, key))) {
222
+ if (isRecord(node.const) && Object.hasOwn(node.const, key)) return true;
223
+ if (Array.isArray(node.enum) && node.enum.some(value => isRecord(value) && Object.hasOwn(value, key))) {
227
224
  return true;
228
225
  }
229
226
  const properties = node.properties;
230
- if (isJsonObject(properties) && Object.hasOwn(properties, key)) {
227
+ if (isRecord(properties) && Object.hasOwn(properties, key)) {
231
228
  return properties[key] !== false || context !== "positive";
232
229
  }
233
230
  const required = node.required;
@@ -240,7 +237,7 @@ export function schemaDefinesProperty(schema: unknown, key: string): boolean {
240
237
  return true;
241
238
  }
242
239
  const patternProperties = node.patternProperties;
243
- if (isJsonObject(patternProperties)) {
240
+ if (isRecord(patternProperties)) {
244
241
  for (const [pattern, patternSchema] of Object.entries(patternProperties)) {
245
242
  try {
246
243
  if (new RegExp(pattern).test(key)) {
@@ -251,8 +248,8 @@ export function schemaDefinesProperty(schema: unknown, key: string): boolean {
251
248
  }
252
249
  }
253
250
  }
254
- if (isJsonObject(node.unevaluatedProperties)) return true;
255
- if (isJsonObject(node.additionalProperties)) return true;
251
+ if (isRecord(node.unevaluatedProperties)) return true;
252
+ if (isRecord(node.additionalProperties)) return true;
256
253
  for (const keyword of ["anyOf", "oneOf", "allOf"] as const) {
257
254
  const branches = node[keyword];
258
255
  if (Array.isArray(branches) && branches.some(branch => visit(branch, context))) return true;
@@ -270,19 +267,19 @@ export function schemaDefinesProperty(schema: unknown, key: string): boolean {
270
267
  return true;
271
268
  }
272
269
  const dependentSchemas = node.dependentSchemas;
273
- if (isJsonObject(dependentSchemas)) {
270
+ if (isRecord(dependentSchemas)) {
274
271
  if (Object.hasOwn(dependentSchemas, key)) return true;
275
272
  if (Object.values(dependentSchemas).some(dependency => visit(dependency, context))) return true;
276
273
  }
277
274
  const dependentRequired = node.dependentRequired;
278
- if (isJsonObject(dependentRequired)) {
275
+ if (isRecord(dependentRequired)) {
279
276
  if (Object.hasOwn(dependentRequired, key)) return true;
280
277
  for (const dependencies of Object.values(dependentRequired)) {
281
278
  if (Array.isArray(dependencies) && dependencies.includes(key)) return true;
282
279
  }
283
280
  }
284
281
  const dependencies = node.dependencies;
285
- if (isJsonObject(dependencies)) {
282
+ if (isRecord(dependencies)) {
286
283
  if (Object.hasOwn(dependencies, key)) return true;
287
284
  for (const dependency of Object.values(dependencies)) {
288
285
  if (Array.isArray(dependency) && dependency.includes(key)) return true;
@@ -324,7 +321,7 @@ function validateSchemaNode(
324
321
  pushIssue(issues, path, "must not match false schema", { keyword: "false" });
325
322
  return false;
326
323
  }
327
- if (!isJsonObject(schema)) {
324
+ if (!isRecord(schema)) {
328
325
  pushIssue(issues, path, "schema must be an object or boolean", { keyword: "schema" });
329
326
  return false;
330
327
  }
@@ -469,7 +466,7 @@ function validateSchemaNode(
469
466
  );
470
467
  }
471
468
 
472
- if (isJsonObject(value)) {
469
+ if (isRecord(value)) {
473
470
  valid = validateObjectKeywords(schema, value, path, ctx, issues) && valid;
474
471
  }
475
472
  if (Array.isArray(value)) {
@@ -494,7 +491,7 @@ function validateObjectKeywords(
494
491
  issues: JsonSchemaValidationIssue[],
495
492
  ): boolean {
496
493
  let valid = true;
497
- const properties = isJsonObject(schema.properties) ? schema.properties : {};
494
+ const properties = isRecord(schema.properties) ? schema.properties : {};
498
495
  if (isRequiredSet(schema.required)) {
499
496
  for (const key of schema.required) {
500
497
  if (!(key in value)) {
@@ -516,7 +513,7 @@ function validateObjectKeywords(
516
513
  }
517
514
 
518
515
  const known = new Set(Object.keys(properties));
519
- if (isJsonObject(schema.patternProperties)) {
516
+ if (isRecord(schema.patternProperties)) {
520
517
  const patternProperties = schema.patternProperties;
521
518
  for (const pattern in patternProperties) {
522
519
  const patternSchema = patternProperties[pattern];
@@ -536,7 +533,7 @@ function validateObjectKeywords(
536
533
  }
537
534
  }
538
535
 
539
- if (isJsonObject(schema.dependentRequired)) {
536
+ if (isRecord(schema.dependentRequired)) {
540
537
  const dependentRequired = schema.dependentRequired;
541
538
  for (const key in dependentRequired) {
542
539
  const deps = dependentRequired[key];
@@ -554,7 +551,7 @@ function validateObjectKeywords(
554
551
  }
555
552
  }
556
553
 
557
- if (isJsonObject(schema.dependentSchemas)) {
554
+ if (isRecord(schema.dependentSchemas)) {
558
555
  const dependentSchemas = schema.dependentSchemas;
559
556
  for (const key in dependentSchemas) {
560
557
  if (!(key in value)) continue;
@@ -1,3 +1,4 @@
1
+ import { isRecord } from "@oh-my-pi/pi-utils";
1
2
  import { areJsonValuesEqual } from "./equality";
2
3
  import { epochNext, once } from "./stamps";
3
4
 
@@ -12,10 +13,6 @@ import { epochNext, once } from "./stamps";
12
13
 
13
14
  type Json = unknown;
14
15
 
15
- function isPlainObject(value: Json): value is Record<string, Json> {
16
- return typeof value === "object" && value !== null && !Array.isArray(value);
17
- }
18
-
19
16
  const TYPE_NAMES: Record<string, true> = {
20
17
  string: true,
21
18
  number: true,
@@ -55,7 +52,7 @@ function checkSchemaArray(value: Json, epoch: number): boolean {
55
52
  }
56
53
 
57
54
  function checkSchemaMap(value: Json, epoch: number): boolean {
58
- if (!isPlainObject(value)) return false;
55
+ if (!isRecord(value)) return false;
59
56
  for (const k in value) {
60
57
  if (!checkNode(value[k], epoch)) return false;
61
58
  }
@@ -66,7 +63,7 @@ function checkSchemaMap(value: Json, epoch: number): boolean {
66
63
  function checkNode(node: Json, epoch: number): boolean {
67
64
  // Boolean schemas (`true` / `false`) are valid JSON Schema.
68
65
  if (node === true || node === false) return true;
69
- if (!isPlainObject(node)) return false;
66
+ if (!isRecord(node)) return false;
70
67
  if (!once(node, epoch)) return true;
71
68
 
72
69
  if ("type" in node && !checkTypeKeyword(node.type)) return false;
@@ -115,7 +112,7 @@ function checkNode(node: Json, epoch: number): boolean {
115
112
  if ("dependentSchemas" in node && !checkSchemaMap(node.dependentSchemas, epoch)) return false;
116
113
  if ("dependentRequired" in node) {
117
114
  const value = node.dependentRequired;
118
- if (!isPlainObject(value)) return false;
115
+ if (!isRecord(value)) return false;
119
116
  for (const k in value) {
120
117
  const entry = value[k];
121
118
  if (!Array.isArray(entry) || !entry.every(item => typeof item === "string")) return false;
@@ -7,7 +7,7 @@
7
7
  */
8
8
 
9
9
  import type { Type } from "@oh-my-pi/omptype";
10
- import { structuredCloneJSON } from "@oh-my-pi/pi-utils";
10
+ import { isRecord, structuredCloneJSON } from "@oh-my-pi/pi-utils";
11
11
  import type { Tool, TSchema } from "../../types";
12
12
  import { upgradeJsonSchemaTo202012 } from "./draft";
13
13
  import { stamp } from "./stamps";
@@ -28,13 +28,13 @@ export function isArkSchema(value: unknown): value is Type {
28
28
 
29
29
  function isArkJsonAst(value: unknown): boolean {
30
30
  if (Array.isArray(value)) return value.some(isArkJsonAst);
31
- if (!isSchemaRecord(value)) return false;
31
+ if (!isRecord(value)) return false;
32
32
  if (typeof value.domain === "string" || Object.hasOwn(value, "unit")) return true;
33
33
  if (value.proto === "Array" && Object.hasOwn(value, "sequence")) return true;
34
34
  const required = value.required;
35
35
  return (
36
36
  Array.isArray(required) &&
37
- required.some(entry => isSchemaRecord(entry) && typeof entry.key === "string" && "value" in entry)
37
+ required.some(entry => isRecord(entry) && typeof entry.key === "string" && "value" in entry)
38
38
  );
39
39
  }
40
40
 
@@ -46,7 +46,7 @@ function parseArkObjectKey(key: string): { name: string; description?: string }
46
46
 
47
47
  function withArkKeyDescription(schema: unknown, description: string | undefined): unknown {
48
48
  if (!description) return schema;
49
- if (isSchemaRecord(schema)) {
49
+ if (isRecord(schema)) {
50
50
  if (typeof schema.description !== "string") schema.description = description;
51
51
  return schema;
52
52
  }
@@ -70,13 +70,13 @@ function arkJsonAstToWire(value: unknown): unknown {
70
70
  }
71
71
 
72
72
  if (Array.isArray(value)) {
73
- if (value.every(item => isSchemaRecord(item) && Object.hasOwn(item, "unit"))) {
73
+ if (value.every(item => isRecord(item) && Object.hasOwn(item, "unit"))) {
74
74
  return { enum: value.map(item => (item as { unit: unknown }).unit) };
75
75
  }
76
76
  return { anyOf: value.map(arkJsonAstToWire) };
77
77
  }
78
78
 
79
- if (!isSchemaRecord(value)) return {};
79
+ if (!isRecord(value)) return {};
80
80
 
81
81
  if (Object.hasOwn(value, "unit")) return { const: value.unit };
82
82
 
@@ -88,7 +88,7 @@ function arkJsonAstToWire(value: unknown): unknown {
88
88
  const properties: Record<string, unknown> = {};
89
89
  const required: string[] = [];
90
90
  const addEntry = (entry: unknown, isRequired: boolean): void => {
91
- if (!isSchemaRecord(entry) || typeof entry.key !== "string" || !("value" in entry)) return;
91
+ if (!isRecord(entry) || typeof entry.key !== "string" || !("value" in entry)) return;
92
92
  const key = parseArkObjectKey(entry.key);
93
93
  properties[key.name] = withArkKeyDescription(arkJsonAstToWire(entry.value), key.description);
94
94
  if (isRequired) required.push(key.name);
@@ -144,10 +144,6 @@ const SCHEMA_DEFINING_SIBLING_KEYS = new Set([
144
144
  "unevaluatedProperties",
145
145
  ]);
146
146
 
147
- function isSchemaRecord(value: unknown): value is Record<string, unknown> {
148
- return value !== null && typeof value === "object" && !Array.isArray(value);
149
- }
150
-
151
147
  function hasSchemaDefiningSibling(schema: Record<string, unknown>): boolean {
152
148
  for (const key in schema) {
153
149
  if (key !== "anyOf" && SCHEMA_DEFINING_SIBLING_KEYS.has(key)) return true;
@@ -188,7 +184,7 @@ function rewriteNullableScalarAnyOf(schema: Record<string, unknown>): void {
188
184
  let scalarType: string | undefined;
189
185
  let sawNull = false;
190
186
  for (const variant of variants) {
191
- if (!isSchemaRecord(variant)) return;
187
+ if (!isRecord(variant)) return;
192
188
  if (isNullVariant(variant)) {
193
189
  if (sawNull) return;
194
190
  sawNull = true;
@@ -206,7 +202,7 @@ function rewriteNullableScalarAnyOf(schema: Record<string, unknown>): void {
206
202
  }
207
203
 
208
204
  function isExclusiveRequiredBranch(branch: unknown): boolean {
209
- if (!isSchemaRecord(branch)) return false;
205
+ if (!isRecord(branch)) return false;
210
206
  if (Object.hasOwn(branch, "type")) return false;
211
207
  if (!Array.isArray(branch.required) || branch.required.length === 0) return false;
212
208
  if (!branch.required.every(name => typeof name === "string" && name.length > 0)) return false;
@@ -229,7 +225,7 @@ export function flattenExclusiveRequiredRootUnion(schema: Record<string, unknown
229
225
  const union = schema[unionKey];
230
226
  if (!Array.isArray(union) || union.length === 0) return schema;
231
227
  const typedObject = schema.type === "object" || (Array.isArray(schema.type) && schema.type.includes("object"));
232
- if (!typedObject && !isSchemaRecord(schema.properties)) return schema;
228
+ if (!typedObject && !isRecord(schema.properties)) return schema;
233
229
  if (!union.every(isExclusiveRequiredBranch)) return schema;
234
230
  const flattened = { ...schema };
235
231
  delete flattened[unionKey];
@@ -261,11 +257,11 @@ function normalizeArkPropertyComments(node: unknown): void {
261
257
  for (const child of node) normalizeArkPropertyComments(child);
262
258
  return;
263
259
  }
264
- if (!isSchemaRecord(node)) return;
260
+ if (!isRecord(node)) return;
265
261
  const obj = node as Record<string, unknown>;
266
262
 
267
263
  const properties = obj.properties;
268
- if (isSchemaRecord(properties)) {
264
+ if (isRecord(properties)) {
269
265
  const required = Array.isArray(obj.required) ? obj.required : undefined;
270
266
  if (required) {
271
267
  obj.required = required.map(key => (typeof key === "string" ? parseArkObjectKey(key).name : key));
@@ -289,7 +285,7 @@ function normalizeArkPropertyComments(node: unknown): void {
289
285
  for (const mapKey of SCHEMA_MAP_KEYS) {
290
286
  if (mapKey === "properties") continue;
291
287
  const map = obj[mapKey];
292
- if (isSchemaRecord(map)) {
288
+ if (isRecord(map)) {
293
289
  for (const key in map) normalizeArkPropertyComments(map[key]);
294
290
  }
295
291
  }
@@ -382,7 +378,7 @@ function collapseConstUnionAnyOf(obj: Record<string, unknown>): void {
382
378
  let branchDescription: string | undefined;
383
379
  let describedCount = 0;
384
380
  for (const variant of variants) {
385
- if (!isSchemaRecord(variant) || !Object.hasOwn(variant, "const")) return;
381
+ if (!isRecord(variant) || !Object.hasOwn(variant, "const")) return;
386
382
  for (const key in variant) {
387
383
  if (key !== "const" && key !== "description") return; // extra constraints — not a bare const
388
384
  }
@@ -553,7 +549,7 @@ function pruneArkUndefinedUnionBranches(node: unknown): void {
553
549
  const concrete = branches.filter(branch => !isUnconstrainedSchema(branch));
554
550
  if (concrete.length === branches.length || concrete.length === 0) continue;
555
551
  const only = concrete.length === 1 ? concrete[0] : undefined;
556
- if (only !== undefined && isSchemaRecord(only)) {
552
+ if (only !== undefined && isRecord(only)) {
557
553
  delete obj[unionKey];
558
554
  for (const key in only) {
559
555
  if (!(key in obj)) obj[key] = only[key];
@@ -651,14 +647,14 @@ function stripSchemaDescriptionsInPlace(node: unknown): void {
651
647
  for (const child of node) stripSchemaDescriptionsInPlace(child);
652
648
  return;
653
649
  }
654
- if (!isSchemaRecord(node)) return;
650
+ if (!isRecord(node)) return;
655
651
  delete node.description;
656
652
  for (const key of STRIP_SCHEMA_VALUE_KEYS) {
657
653
  if (Object.hasOwn(node, key)) stripSchemaDescriptionsInPlace(node[key]);
658
654
  }
659
655
  for (const mapKey of STRIP_SCHEMA_MAP_KEYS) {
660
656
  const map = node[mapKey];
661
- if (isSchemaRecord(map)) {
657
+ if (isRecord(map)) {
662
658
  for (const key in map) stripSchemaDescriptionsInPlace(map[key]);
663
659
  }
664
660
  }