@oh-my-pi/pi-ai 17.2.6 → 17.2.8

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 (31) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/dist/types/auth-broker/client.d.ts +7 -0
  3. package/dist/types/auth-broker/server.d.ts +11 -0
  4. package/dist/types/auth-broker/wire-schema-resource.d.ts +1 -1
  5. package/dist/types/auth-broker/wire-schemas.d.ts +4 -2
  6. package/dist/types/index.d.ts +1 -1
  7. package/dist/types/providers/anthropic-messages-server-schema.d.ts +695 -255
  8. package/dist/types/providers/openai-chat-server-schema.d.ts +909 -293
  9. package/dist/types/providers/openai-responses-server-schema.d.ts +980 -353
  10. package/dist/types/types.d.ts +8 -5
  11. package/dist/types/usage.d.ts +152 -46
  12. package/dist/types/utils/schema/wire.d.ts +1 -1
  13. package/dist/types/utils/validation.d.ts +24 -0
  14. package/package.json +5 -5
  15. package/src/auth-broker/client.ts +2 -1
  16. package/src/auth-broker/server.ts +2 -1
  17. package/src/auth-broker/wire-schema-resource.ts +1 -6
  18. package/src/index.ts +1 -1
  19. package/src/providers/anthropic-messages-server-schema.ts +1 -1
  20. package/src/providers/anthropic-messages-server.ts +1 -1
  21. package/src/providers/cursor.ts +31 -3
  22. package/src/providers/google-gemini-cli.ts +1 -1
  23. package/src/providers/openai-chat-server-schema.ts +1 -1
  24. package/src/providers/openai-chat-server.ts +1 -1
  25. package/src/providers/openai-codex-responses.ts +1 -1
  26. package/src/providers/openai-responses-server-schema.ts +1 -1
  27. package/src/providers/openai-responses-server.ts +1 -1
  28. package/src/types.ts +9 -6
  29. package/src/usage.ts +1 -1
  30. package/src/utils/schema/wire.ts +1 -1
  31. package/src/utils/validation.ts +2 -1
@@ -1,9 +1,9 @@
1
1
  export * from "@oh-my-pi/pi-catalog/effort";
2
2
  export * from "@oh-my-pi/pi-catalog/types";
3
+ import type { Type } from "@oh-my-pi/omptype";
3
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-gen/agent_pb";
4
5
  import type { Effort } from "@oh-my-pi/pi-catalog/effort";
5
6
  import type { Api, FetchImpl, Model, Provider, ThinkingBudgets, Usage } from "@oh-my-pi/pi-catalog/types";
6
- import type { Type } from "arktype";
7
7
  import type { ZodType, z } from "zod/v4";
8
8
  import type { ApiKey } from "./auth-retry.js";
9
9
  import type { BedrockOptions } from "./providers/amazon-bedrock.js";
@@ -124,10 +124,13 @@ export declare function serviceTierFamily(model: ServiceTierModel): ServiceTierF
124
124
  export declare function resolveModelServiceTier(tiers: ServiceTierByFamily | null | undefined, model: Pick<Model, "provider" | "api" | "id">): ServiceTier | undefined;
125
125
  /**
126
126
  * True when the tier should be sent on the wire as the provider's service-tier
127
- * request field. OpenAI / OpenAI-Codex accept every {@link ServiceTier};
128
- * Google (Gemini API + Vertex) and OpenRouter accept `flex`/`priority`;
129
- * Fireworks Serverless realizes only its Priority serving path. Anthropic is
130
- * absent because it realizes `priority` via `speed: "fast"`.
127
+ * request field. `auto` is never forwarded — it is OpenAI's implicit default, so
128
+ * omitting `service_tier` is identical to requesting `auto`, and the Codex
129
+ * (ChatGPT OAuth) endpoint rejects an explicit `auto` outright. OpenAI /
130
+ * OpenAI-Codex accept every other {@link ServiceTier}; Google (Gemini API +
131
+ * Vertex) and OpenRouter accept `flex`/`priority`; Fireworks Serverless
132
+ * realizes only its Priority serving path. Anthropic is absent because it
133
+ * realizes `priority` via `speed: "fast"`.
131
134
  */
132
135
  export declare function shouldSendServiceTier(serviceTier: ServiceTier | null | undefined, target: Provider | ServiceTierModel | undefined): boolean;
133
136
  /**
@@ -206,125 +206,231 @@ export interface ClientUsageClientSummary {
206
206
  export interface ClientUsageSummary {
207
207
  clients: ClientUsageClientSummary[];
208
208
  }
209
- export declare const usageUnitSchema: import("arktype/internal/variants/string.ts").StringType<"bytes" | "minutes" | "percent" | "requests" | "tokens" | "unknown" | "usd", {}>;
210
- export declare const usageStatusSchema: import("arktype/internal/variants/string.ts").StringType<"exhausted" | "ok" | "unknown" | "warning", {}>;
211
- export declare const usageWindowSchema: import("arktype/internal/variants/object.ts").ObjectType<{
209
+ export declare const usageUnitSchema: import("@oh-my-pi/omptype").FluentType<"bytes" | "minutes" | "percent" | "requests" | "tokens" | "unknown" | "usd", "bytes" | "minutes" | "percent" | "requests" | "tokens" | "unknown" | "usd">;
210
+ export declare const usageStatusSchema: import("@oh-my-pi/omptype").FluentType<"exhausted" | "ok" | "unknown" | "warning", "exhausted" | "ok" | "unknown" | "warning">;
211
+ export declare const usageWindowSchema: import("@oh-my-pi/omptype").FluentType<{
212
+ durationMs?: number | undefined;
212
213
  id: string;
213
214
  label: string;
214
- durationMs?: number | undefined;
215
+ resetLabel?: string | undefined;
215
216
  resetsAt?: number | undefined;
217
+ }, {
218
+ durationMs?: number | undefined;
219
+ id: string;
220
+ label: string;
216
221
  resetLabel?: string | undefined;
217
- }, {}>;
218
- export declare const usageAmountSchema: import("arktype/internal/variants/object.ts").ObjectType<{
219
- used?: number | undefined;
222
+ resetsAt?: number | undefined;
223
+ }>;
224
+ export declare const usageAmountSchema: import("@oh-my-pi/omptype").FluentType<{
220
225
  limit?: number | undefined;
221
226
  remaining?: number | undefined;
227
+ remainingFraction?: number | undefined;
228
+ unit: "bytes" | "minutes" | "percent" | "requests" | "tokens" | "unknown" | "usd";
229
+ used?: number | undefined;
222
230
  usedFraction?: number | undefined;
231
+ }, {
232
+ limit?: number | undefined;
233
+ remaining?: number | undefined;
223
234
  remainingFraction?: number | undefined;
224
235
  unit: "bytes" | "minutes" | "percent" | "requests" | "tokens" | "unknown" | "usd";
225
- }, {}>;
226
- export declare const usageScopeSchema: import("arktype/internal/variants/object.ts").ObjectType<{
227
- provider: string;
236
+ used?: number | undefined;
237
+ usedFraction?: number | undefined;
238
+ }>;
239
+ export declare const usageScopeSchema: import("@oh-my-pi/omptype").FluentType<{
228
240
  accountId?: string | undefined;
229
- projectId?: string | undefined;
230
- orgId?: string | undefined;
231
241
  modelId?: string | undefined;
242
+ orgId?: string | undefined;
243
+ projectId?: string | undefined;
244
+ provider: string;
245
+ shared?: boolean | undefined;
232
246
  tier?: string | undefined;
233
247
  windowId?: string | undefined;
248
+ }, {
249
+ accountId?: string | undefined;
250
+ modelId?: string | undefined;
251
+ orgId?: string | undefined;
252
+ projectId?: string | undefined;
253
+ provider: string;
234
254
  shared?: boolean | undefined;
235
- }, {}>;
236
- export declare const usageLimitSchema: import("arktype/internal/variants/object.ts").ObjectType<{
255
+ tier?: string | undefined;
256
+ windowId?: string | undefined;
257
+ }>;
258
+ export declare const usageLimitSchema: import("@oh-my-pi/omptype").FluentType<{
259
+ amount: {
260
+ limit?: number | undefined;
261
+ remaining?: number | undefined;
262
+ remainingFraction?: number | undefined;
263
+ unit: "bytes" | "minutes" | "percent" | "requests" | "tokens" | "unknown" | "usd";
264
+ used?: number | undefined;
265
+ usedFraction?: number | undefined;
266
+ };
237
267
  id: string;
238
268
  label: string;
269
+ notes?: string[] | undefined;
239
270
  scope: {
240
- provider: string;
241
271
  accountId?: string | undefined;
242
- projectId?: string | undefined;
243
- orgId?: string | undefined;
244
272
  modelId?: string | undefined;
273
+ orgId?: string | undefined;
274
+ projectId?: string | undefined;
275
+ provider: string;
276
+ shared?: boolean | undefined;
245
277
  tier?: string | undefined;
246
278
  windowId?: string | undefined;
247
- shared?: boolean | undefined;
248
279
  };
280
+ status?: "exhausted" | "ok" | "unknown" | "warning" | undefined;
249
281
  window?: {
282
+ durationMs?: number | undefined;
250
283
  id: string;
251
284
  label: string;
252
- durationMs?: number | undefined;
253
- resetsAt?: number | undefined;
254
285
  resetLabel?: string | undefined;
286
+ resetsAt?: number | undefined;
255
287
  } | undefined;
288
+ }, {
256
289
  amount: {
257
- used?: number | undefined;
258
290
  limit?: number | undefined;
259
291
  remaining?: number | undefined;
260
- usedFraction?: number | undefined;
261
292
  remainingFraction?: number | undefined;
262
293
  unit: "bytes" | "minutes" | "percent" | "requests" | "tokens" | "unknown" | "usd";
294
+ used?: number | undefined;
295
+ usedFraction?: number | undefined;
263
296
  };
264
- status?: "exhausted" | "ok" | "unknown" | "warning" | undefined;
297
+ id: string;
298
+ label: string;
265
299
  notes?: string[] | undefined;
266
- }, {}>;
267
- export declare const usageResetCreditDetailSchema: import("arktype/internal/variants/object.ts").ObjectType<{
300
+ scope: {
301
+ accountId?: string | undefined;
302
+ modelId?: string | undefined;
303
+ orgId?: string | undefined;
304
+ projectId?: string | undefined;
305
+ provider: string;
306
+ shared?: boolean | undefined;
307
+ tier?: string | undefined;
308
+ windowId?: string | undefined;
309
+ };
310
+ status?: "exhausted" | "ok" | "unknown" | "warning" | undefined;
311
+ window?: {
312
+ durationMs?: number | undefined;
313
+ id: string;
314
+ label: string;
315
+ resetLabel?: string | undefined;
316
+ resetsAt?: number | undefined;
317
+ } | undefined;
318
+ }>;
319
+ export declare const usageResetCreditDetailSchema: import("@oh-my-pi/omptype").FluentType<{
320
+ expiresAt?: string | undefined;
268
321
  grantedAt?: string | undefined;
322
+ status?: string | undefined;
323
+ }, {
269
324
  expiresAt?: string | undefined;
325
+ grantedAt?: string | undefined;
270
326
  status?: string | undefined;
271
- }, {}>;
272
- export declare const usageResetCreditsSchema: import("arktype/internal/variants/object.ts").ObjectType<{
327
+ }>;
328
+ export declare const usageResetCreditsSchema: import("@oh-my-pi/omptype").FluentType<{
273
329
  availableCount: number;
274
330
  credits?: {
331
+ expiresAt?: string | undefined;
275
332
  grantedAt?: string | undefined;
333
+ status?: string | undefined;
334
+ }[] | undefined;
335
+ }, {
336
+ availableCount: number;
337
+ credits?: {
276
338
  expiresAt?: string | undefined;
339
+ grantedAt?: string | undefined;
277
340
  status?: string | undefined;
278
341
  }[] | undefined;
279
- }, {}>;
280
- export declare const usageReportSchema: import("arktype/internal/variants/object.ts").ObjectType<{
281
- provider: string;
342
+ }>;
343
+ export declare const usageReportSchema: import("@oh-my-pi/omptype").FluentType<{
282
344
  fetchedAt: number;
283
345
  limits: {
346
+ amount: {
347
+ limit?: number | undefined;
348
+ remaining?: number | undefined;
349
+ remainingFraction?: number | undefined;
350
+ unit: "bytes" | "minutes" | "percent" | "requests" | "tokens" | "unknown" | "usd";
351
+ used?: number | undefined;
352
+ usedFraction?: number | undefined;
353
+ };
284
354
  id: string;
285
355
  label: string;
356
+ notes?: string[] | undefined;
286
357
  scope: {
287
- provider: string;
288
358
  accountId?: string | undefined;
289
- projectId?: string | undefined;
290
- orgId?: string | undefined;
291
359
  modelId?: string | undefined;
360
+ orgId?: string | undefined;
361
+ projectId?: string | undefined;
362
+ provider: string;
363
+ shared?: boolean | undefined;
292
364
  tier?: string | undefined;
293
365
  windowId?: string | undefined;
294
- shared?: boolean | undefined;
295
366
  };
367
+ status?: "exhausted" | "ok" | "unknown" | "warning" | undefined;
296
368
  window?: {
369
+ durationMs?: number | undefined;
297
370
  id: string;
298
371
  label: string;
299
- durationMs?: number | undefined;
300
- resetsAt?: number | undefined;
301
372
  resetLabel?: string | undefined;
373
+ resetsAt?: number | undefined;
302
374
  } | undefined;
375
+ }[];
376
+ metadata?: Record<string, unknown> | undefined;
377
+ notes?: string[] | undefined;
378
+ provider: string;
379
+ raw?: unknown;
380
+ resetCredits?: {
381
+ availableCount: number;
382
+ credits?: {
383
+ expiresAt?: string | undefined;
384
+ grantedAt?: string | undefined;
385
+ status?: string | undefined;
386
+ }[] | undefined;
387
+ } | undefined;
388
+ }, {
389
+ fetchedAt: number;
390
+ limits: {
303
391
  amount: {
304
- used?: number | undefined;
305
392
  limit?: number | undefined;
306
393
  remaining?: number | undefined;
307
- usedFraction?: number | undefined;
308
394
  remainingFraction?: number | undefined;
309
395
  unit: "bytes" | "minutes" | "percent" | "requests" | "tokens" | "unknown" | "usd";
396
+ used?: number | undefined;
397
+ usedFraction?: number | undefined;
310
398
  };
311
- status?: "exhausted" | "ok" | "unknown" | "warning" | undefined;
399
+ id: string;
400
+ label: string;
312
401
  notes?: string[] | undefined;
402
+ scope: {
403
+ accountId?: string | undefined;
404
+ modelId?: string | undefined;
405
+ orgId?: string | undefined;
406
+ projectId?: string | undefined;
407
+ provider: string;
408
+ shared?: boolean | undefined;
409
+ tier?: string | undefined;
410
+ windowId?: string | undefined;
411
+ };
412
+ status?: "exhausted" | "ok" | "unknown" | "warning" | undefined;
413
+ window?: {
414
+ durationMs?: number | undefined;
415
+ id: string;
416
+ label: string;
417
+ resetLabel?: string | undefined;
418
+ resetsAt?: number | undefined;
419
+ } | undefined;
313
420
  }[];
421
+ metadata?: Record<string, unknown> | undefined;
422
+ notes?: string[] | undefined;
423
+ provider: string;
424
+ raw?: unknown;
314
425
  resetCredits?: {
315
426
  availableCount: number;
316
427
  credits?: {
317
- grantedAt?: string | undefined;
318
428
  expiresAt?: string | undefined;
429
+ grantedAt?: string | undefined;
319
430
  status?: string | undefined;
320
431
  }[] | undefined;
321
432
  } | undefined;
322
- notes?: string[] | undefined;
323
- metadata?: {
324
- [x: string]: unknown;
325
- } | undefined;
326
- raw?: unknown;
327
- }, {}>;
433
+ }>;
328
434
  /** Optional logger for usage fetchers. */
329
435
  export interface UsageLogger {
330
436
  debug(message: string, meta?: Record<string, unknown>): void;
@@ -10,7 +10,7 @@
10
10
  * All three are normalized at the boundary so providers and validators see the same
11
11
  * JSON Schema dialect.
12
12
  */
13
- import type { Type } from "arktype";
13
+ import type { Type } from "@oh-my-pi/omptype";
14
14
  import { type ZodType } from "zod/v4";
15
15
  import type { Tool } from "../../types.js";
16
16
  /**
@@ -1,3 +1,27 @@
1
+ /**
2
+ * Tool-call argument validation pipeline.
3
+ *
4
+ * Tools may declare their parameters as either Zod schemas (canonical) or
5
+ * plain JSON Schema (legacy / extensions). This module is the single
6
+ * entrypoint the agent calls before dispatching a tool — it:
7
+ *
8
+ * 1. Builds (or fetches from cache) a `ValidationContext` for the tool —
9
+ * the Zod schema if available plus the equivalent wire JSON Schema, or
10
+ * just the JSON Schema for non-Zod tools.
11
+ * 2. Normalizes LLM quirks (null / "null" → omit-or-default substitution)
12
+ * against the JSON Schema before validation.
13
+ * 3. Validates with the Zod or JSON-Schema validator.
14
+ * 4. On failure, walks the resulting issues and coerces common LLM type
15
+ * drift (JSON-stringified values, boolean/number/string scalar drift),
16
+ * drops unrecognized keys, and retries up to `MAX_COERCION_PASSES` times.
17
+ * 5. Throws a formatted error if reconciliation fails; otherwise returns
18
+ * the parsed arguments with original unknown root fields preserved (so
19
+ * hallucinated top-level keys still surface to the caller).
20
+ *
21
+ * The goal is to be conservative: every coercion is a structural rewrite that
22
+ * keeps the schema in charge of acceptance — we never invent values, only
23
+ * massage shapes the LLM almost got right.
24
+ */
1
25
  import type { Tool, ToolCall } from "../types.js";
2
26
  /**
3
27
  * Finds a tool by name and validates the tool call arguments against its schema.
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.2.6",
4
+ "version": "17.2.8",
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,10 +38,10 @@
38
38
  },
39
39
  "dependencies": {
40
40
  "@bufbuild/protobuf": "^2.12.1",
41
- "@oh-my-pi/pi-catalog": "17.2.6",
42
- "@oh-my-pi/pi-utils": "17.2.6",
43
- "@oh-my-pi/pi-wire": "17.2.6",
44
- "arktype": "2.2.3",
41
+ "@oh-my-pi/omptype": "17.2.8",
42
+ "@oh-my-pi/pi-catalog": "17.2.8",
43
+ "@oh-my-pi/pi-utils": "17.2.8",
44
+ "@oh-my-pi/pi-wire": "17.2.8",
45
45
  "zod": "^4"
46
46
  },
47
47
  "devDependencies": {
@@ -5,8 +5,9 @@
5
5
  * `omp auth-broker status` (liveness checks). All endpoints except
6
6
  * `/v1/healthz` require a bearer token.
7
7
  */
8
+
9
+ import { type } from "@oh-my-pi/omptype";
8
10
  import { readSseEvents } from "@oh-my-pi/pi-utils";
9
- import { type } from "arktype";
10
11
  import type { AuthCredential, DisabledCredentialSummary } from "../auth-storage";
11
12
  import type {
12
13
  ClientUsageReportRequest,
@@ -9,8 +9,9 @@
9
9
  * Transport security is delegated to the operator (Tailscale / Wireguard);
10
10
  * the server only checks a bearer token against an allow-list per request.
11
11
  */
12
+
13
+ import { type Type, type } from "@oh-my-pi/omptype";
12
14
  import { logger } from "@oh-my-pi/pi-utils";
13
- import { type Type, type } from "arktype";
14
15
  import type { AuthStorage, StoredCredentialBlock } from "../auth-storage";
15
16
  import { parseBind } from "../utils/parse-bind";
16
17
  import { AuthBrokerRefresher, type AuthBrokerRefresherSchedule } from "./refresher";
@@ -12,7 +12,7 @@
12
12
  * exception (standard type keeps extra keys): it preserves provider-specific extension fields so
13
13
  * they round-trip through the broker instead of being dropped (see below).
14
14
  */
15
- import { scope, type Type } from "arktype";
15
+ import { type Type, type } from "@oh-my-pi/omptype";
16
16
  import {
17
17
  type ApiKeyCredential,
18
18
  type AuthCredential,
@@ -84,11 +84,6 @@ export interface AuthBrokerWireSchemas {
84
84
  }
85
85
 
86
86
  function buildAuthBrokerWireSchemas(): AuthBrokerWireSchemas {
87
- // Wire schemas validate only a handful of times per process, so ArkType's
88
- // definition-time JIT codegen is startup tax. A local jitless scope skips
89
- // that codegen and uses interpreted traversal; correctness is unchanged.
90
- const { type } = scope({}, { jitless: true });
91
-
92
87
  // ─── Credential payloads ───────────────────────────────────────────────────
93
88
 
94
89
  /** Real OAuth credential (broker-side) — refresh token is the actual upstream value. */
package/src/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { type Type, type } from "arktype";
1
+ export { type Type, type } from "@oh-my-pi/omptype";
2
2
  export { type ZodType, z } from "zod/v4";
3
3
  export * from "./api-registry";
4
4
  export type * from "./auth-broker";
@@ -4,7 +4,7 @@
4
4
  * and options.
5
5
  */
6
6
 
7
- import { type } from "arktype";
7
+ import { type } from "@oh-my-pi/omptype";
8
8
  import type {
9
9
  ContentBlockParam,
10
10
  ImageBlockParam,
@@ -1,6 +1,6 @@
1
+ import { type } from "@oh-my-pi/omptype";
1
2
  import { Effort } from "@oh-my-pi/pi-catalog/effort";
2
3
  import { logger } from "@oh-my-pi/pi-utils";
3
- import { type } from "arktype";
4
4
  import { captureRequestHeaders, resolvePromptCacheKey } from "../auth-gateway/http";
5
5
  import * as AIError from "../error";
6
6
  import type {
@@ -144,6 +144,7 @@ import { isKimiK3ModelId } from "@oh-my-pi/pi-catalog/identity";
144
144
  import { calculateCost } from "@oh-my-pi/pi-catalog/models";
145
145
  import {
146
146
  $env,
147
+ logger,
147
148
  parseJsonWithRepair,
148
149
  parseStreamingJson,
149
150
  parseStreamingJsonThrottled,
@@ -233,6 +234,7 @@ const NOT_IMPLEMENTED = `Not implemented by this client`;
233
234
 
234
235
  const conversationStateCache = new Map<string, ConversationStateStructure>();
235
236
  const conversationBlobStores = new Map<string, Map<string, Uint8Array>>();
237
+ const warnedCursorKimiK3ReplayMessages = new Set<string>();
236
238
 
237
239
  export interface CursorOptions extends StreamOptions {
238
240
  customSystemPrompt?: string;
@@ -3902,6 +3904,15 @@ export function processInteractionUpdate(
3902
3904
  }
3903
3905
  } else if (updateCase === "turnEnded") {
3904
3906
  output.stopReason = "stop";
3907
+ if (
3908
+ isKimiK3ModelId(output.model) &&
3909
+ !output.content.some(item => item.type === "thinking" && item.thinking.length > 0)
3910
+ ) {
3911
+ logger.warn(
3912
+ "Cursor kimi-k3 turn completed without thinking blocks; persisted history will replay this turn without reasoning",
3913
+ { model: output.model, messageTimestamp: output.timestamp },
3914
+ );
3915
+ }
3905
3916
  } else if (updateCase === "tokenDelta") {
3906
3917
  const tokenDelta = update.message.value;
3907
3918
  usageState.sawTokenDelta = true;
@@ -4113,17 +4124,34 @@ function assertCursorKimiK3HistoryReplayable(
4113
4124
  ): void {
4114
4125
  if (!targetModelId || !isKimiK3ModelId(targetModelId)) return;
4115
4126
  const historyEnd = activeUserMessageIndex >= 0 ? activeUserMessageIndex : messages.length;
4127
+ const missingThinkingTurns: number[] = [];
4128
+ const newlyWarnedKeys: string[] = [];
4129
+ let assistantTurn = 0;
4116
4130
  for (let i = 0; i < historyEnd; i++) {
4117
4131
  const msg = messages[i];
4118
4132
  if (msg.role !== "assistant") continue;
4133
+ assistantTurn++;
4119
4134
  const isSameCursorModel = msg.api === "cursor-agent" && msg.provider === "cursor" && msg.model === targetModelId;
4120
- const hasThinking = msg.content.some(item => item.type === "thinking" && item.thinking.length > 0);
4121
- if (!isSameCursorModel || !hasThinking) {
4135
+ if (!isSameCursorModel) {
4136
+ // Foreign history genuinely cannot replay K3 thinking: another model's
4137
+ // turns carry no K3-signed reasoning to reconstruct.
4122
4138
  throw new AIError.ValidationError(
4123
- `Cursor ${targetModelId} requires complete same-model thinking history; start a new session instead of continuing history from ${msg.provider}/${msg.model}.`,
4139
+ `Cursor ${targetModelId} cannot continue history from a different model (${msg.provider}/${msg.model}); start a new session.`,
4124
4140
  );
4125
4141
  }
4142
+ const hasThinking = msg.content.some(item => item.type === "thinking" && item.thinking.length > 0);
4143
+ if (hasThinking) continue;
4144
+ const warningKey = `${msg.api}\0${msg.provider}\0${msg.model}\0${msg.timestamp}`;
4145
+ if (warnedCursorKimiK3ReplayMessages.has(warningKey)) continue;
4146
+ missingThinkingTurns.push(assistantTurn);
4147
+ newlyWarnedKeys.push(warningKey);
4126
4148
  }
4149
+ if (missingThinkingTurns.length === 0) return;
4150
+ for (const key of newlyWarnedKeys) warnedCursorKimiK3ReplayMessages.add(key);
4151
+ logger.warn(
4152
+ `Cursor kimi-k3 history contains same-model assistant turn(s) ${missingThinkingTurns.join(", ")} without thinking blocks; replaying those spans without reasoning may make generation less stable`,
4153
+ { model: targetModelId, assistantTurns: missingThinkingTurns },
4154
+ );
4127
4155
  }
4128
4156
 
4129
4157
  /**
@@ -5,6 +5,7 @@
5
5
  */
6
6
  import { createHash, randomBytes, randomUUID } from "node:crypto";
7
7
  import { scheduler } from "node:timers/promises";
8
+ import { type } from "@oh-my-pi/omptype";
8
9
  import { calculateCost } from "@oh-my-pi/pi-catalog/models";
9
10
  import {
10
11
  ANTIGRAVITY_SYSTEM_INSTRUCTION,
@@ -13,7 +14,6 @@ import {
13
14
  getGeminiCliHeaders,
14
15
  } from "@oh-my-pi/pi-catalog/wire/gemini-headers";
15
16
  import { extractHttpStatusFromError, fetchWithRetry, readSseJson } from "@oh-my-pi/pi-utils";
16
- import { type } from "arktype";
17
17
  import * as AIError from "../error";
18
18
  import type {
19
19
  Api,
@@ -7,7 +7,7 @@
7
7
  * `stream_options.include_obfuscation`) — does not trip 400s on shapes we simply ignore.
8
8
  */
9
9
 
10
- import { type } from "arktype";
10
+ import { type } from "@oh-my-pi/omptype";
11
11
  import type {
12
12
  ChatCompletionContentPart,
13
13
  ChatCompletionCreateParams,
@@ -1,5 +1,5 @@
1
1
  import { randomUUID } from "node:crypto";
2
- import { type } from "arktype";
2
+ import { type } from "@oh-my-pi/omptype";
3
3
  import { resolvePromptCacheKey } from "../auth-gateway/http";
4
4
  /**
5
5
  * Parsed inbound OpenAI chat-completions request, ready to feed into pi-ai
@@ -1,5 +1,6 @@
1
1
  import * as os from "node:os";
2
2
  import { scheduler } from "node:timers/promises";
3
+ import { type } from "@oh-my-pi/omptype";
3
4
  import { calculateCost } from "@oh-my-pi/pi-catalog/models";
4
5
  import {
5
6
  CODEX_BASE_URL,
@@ -19,7 +20,6 @@ import {
19
20
  readSseJson,
20
21
  structuredCloneJSON,
21
22
  } from "@oh-my-pi/pi-utils";
22
- import { type } from "arktype";
23
23
  import packageJson from "../../package.json" with { type: "json" };
24
24
  import * as AIError from "../error";
25
25
  import { getEnvApiKey, isOfficialCodexApiUrl } from "../stream";
@@ -8,7 +8,7 @@
8
8
  * is a worse outcome than dropping them on the floor.
9
9
  */
10
10
 
11
- import { type } from "arktype";
11
+ import { type } from "@oh-my-pi/omptype";
12
12
  import type {
13
13
  EasyInputMessage,
14
14
  ResponseCreateParams,
@@ -9,8 +9,8 @@
9
9
  * Inverse direction (source-of-truth for item shapes): ../../providers/openai-responses.ts
10
10
  */
11
11
 
12
+ import { type } from "@oh-my-pi/omptype";
12
13
  import { logger, structuredCloneJSON } from "@oh-my-pi/pi-utils";
13
- import { type } from "arktype";
14
14
  import { resolvePromptCacheKey } from "../auth-gateway/http";
15
15
  import type { AuthGatewayStreamControl, AuthGatewayParsedRequest as ParsedRequest } from "../auth-gateway/types";
16
16
  import * as AIError from "../error";
package/src/types.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  export * from "@oh-my-pi/pi-catalog/effort";
2
2
  export * from "@oh-my-pi/pi-catalog/types";
3
3
 
4
+ import type { Type } from "@oh-my-pi/omptype";
4
5
  import type {
5
6
  DeleteArgs,
6
7
  DeleteResult,
@@ -35,7 +36,6 @@ import type {
35
36
  import type { Effort } from "@oh-my-pi/pi-catalog/effort";
36
37
  import { isOpenAIModelId } from "@oh-my-pi/pi-catalog/identity/family";
37
38
  import type { Api, FetchImpl, KnownApi, Model, Provider, ThinkingBudgets, Usage } from "@oh-my-pi/pi-catalog/types";
38
- import type { Type } from "arktype";
39
39
  import type { ZodType, z } from "zod/v4";
40
40
  import type { ApiKey } from "./auth-retry";
41
41
  import type { BedrockOptions } from "./providers/amazon-bedrock";
@@ -214,16 +214,19 @@ export function resolveModelServiceTier(
214
214
 
215
215
  /**
216
216
  * True when the tier should be sent on the wire as the provider's service-tier
217
- * request field. OpenAI / OpenAI-Codex accept every {@link ServiceTier};
218
- * Google (Gemini API + Vertex) and OpenRouter accept `flex`/`priority`;
219
- * Fireworks Serverless realizes only its Priority serving path. Anthropic is
220
- * absent because it realizes `priority` via `speed: "fast"`.
217
+ * request field. `auto` is never forwarded — it is OpenAI's implicit default, so
218
+ * omitting `service_tier` is identical to requesting `auto`, and the Codex
219
+ * (ChatGPT OAuth) endpoint rejects an explicit `auto` outright. OpenAI /
220
+ * OpenAI-Codex accept every other {@link ServiceTier}; Google (Gemini API +
221
+ * Vertex) and OpenRouter accept `flex`/`priority`; Fireworks Serverless
222
+ * realizes only its Priority serving path. Anthropic is absent because it
223
+ * realizes `priority` via `speed: "fast"`.
221
224
  */
222
225
  export function shouldSendServiceTier(
223
226
  serviceTier: ServiceTier | null | undefined,
224
227
  target: Provider | ServiceTierModel | undefined,
225
228
  ): boolean {
226
- if (!serviceTier) return false;
229
+ if (!serviceTier || serviceTier === "auto") return false;
227
230
  const provider = typeof target === "string" ? target : target?.provider;
228
231
  if (provider === "openai" || provider === "openai-codex") return true;
229
232
  if (provider === "openrouter") {
package/src/usage.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  * Provides a normalized schema to represent multiple limit windows, model tiers,
5
5
  * and shared quotas across providers.
6
6
  */
7
- import { type } from "arktype";
7
+ import { type } from "@oh-my-pi/omptype";
8
8
  import type { FetchImpl, Provider } from "./types";
9
9
  export type UsageUnit = "percent" | "tokens" | "requests" | "usd" | "minutes" | "bytes" | "unknown";
10
10
 
@@ -11,7 +11,7 @@
11
11
  * JSON Schema dialect.
12
12
  */
13
13
 
14
- import type { Type } from "arktype";
14
+ import type { Type } from "@oh-my-pi/omptype";
15
15
  // We import the Zod *value* (z) for runtime APIs. Marker checks rely on the
16
16
  // `_zod` symbol that every Zod v4 schema instance carries.
17
17
  import { type ZodType, z } from "zod/v4";