@oh-my-pi/pi-ai 18.2.0 → 18.2.1
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 +35 -0
- package/dist/types/auth-broker/remote-store.d.ts +17 -0
- package/dist/types/auth-gateway/index.d.ts +1 -0
- package/dist/types/auth-gateway/session-state.d.ts +65 -0
- package/dist/types/auth-storage.d.ts +16 -0
- package/dist/types/error/body-error.d.ts +15 -0
- package/dist/types/error/flags.d.ts +16 -0
- package/dist/types/error/index.d.ts +1 -0
- package/dist/types/oneshot-retry.d.ts +6 -0
- package/dist/types/providers/openai-codex/request-transformer.d.ts +27 -0
- package/dist/types/providers/openai-shared.d.ts +20 -3
- package/dist/types/registry/oauth/perplexity.d.ts +1 -7
- package/dist/types/registry/oauth/types.d.ts +8 -0
- package/dist/types/stream.d.ts +2 -0
- package/dist/types/types.d.ts +3 -1
- package/dist/types/usage.d.ts +8 -0
- package/dist/types/utils/block-symbols.d.ts +36 -0
- package/dist/types/utils/openai-http.d.ts +2 -0
- package/dist/types/utils/retry-after.d.ts +2 -0
- package/dist/types/utils/schema/wire.d.ts +4 -5
- package/dist/types/utils.d.ts +9 -0
- package/package.json +6 -6
- package/src/auth-broker/remote-store.ts +73 -8
- package/src/auth-broker/wire-schemas.ts +1 -0
- package/src/auth-gateway/index.ts +1 -0
- package/src/auth-gateway/server.ts +48 -11
- package/src/auth-gateway/session-state.ts +114 -0
- package/src/auth-storage.ts +144 -13
- package/src/error/body-error.ts +310 -0
- package/src/error/flags.ts +63 -13
- package/src/error/index.ts +1 -0
- package/src/error/retryable.ts +2 -0
- package/src/oneshot-retry.ts +13 -3
- package/src/providers/anthropic-messages-server.ts +24 -3
- package/src/providers/anthropic.ts +101 -15
- package/src/providers/cursor.ts +7 -1
- package/src/providers/devin.ts +82 -28
- package/src/providers/openai-chat-server.ts +4 -0
- package/src/providers/openai-codex/request-transformer.ts +36 -0
- package/src/providers/openai-codex-responses.ts +35 -12
- package/src/providers/openai-completions.ts +43 -12
- package/src/providers/openai-reasoning-fallback.ts +6 -6
- package/src/providers/openai-responses-server.ts +2 -1
- package/src/providers/openai-responses.ts +25 -4
- package/src/providers/openai-shared.ts +199 -51
- package/src/registry/oauth/perplexity.ts +94 -28
- package/src/registry/oauth/types.ts +9 -0
- package/src/stream.ts +23 -2
- package/src/types.ts +3 -0
- package/src/usage/claude.ts +33 -0
- package/src/usage/google-antigravity.ts +8 -2
- package/src/usage.ts +3 -0
- package/src/utils/block-symbols.ts +57 -0
- package/src/utils/openai-http.ts +39 -3
- package/src/utils/retry-after.ts +12 -0
- package/src/utils/schema/normalize.ts +3 -3
- package/src/utils/schema/stamps.ts +33 -45
- package/src/utils/schema/wire.ts +9 -7
- package/src/utils.ts +67 -22
package/src/utils/schema/wire.ts
CHANGED
|
@@ -7,6 +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
11
|
import type { Tool, TSchema } from "../../types";
|
|
11
12
|
import { upgradeJsonSchemaTo202012 } from "./draft";
|
|
12
13
|
import { stamp } from "./stamps";
|
|
@@ -107,7 +108,7 @@ function arkJsonAstToWire(value: unknown): unknown {
|
|
|
107
108
|
return {};
|
|
108
109
|
}
|
|
109
110
|
|
|
110
|
-
/**
|
|
111
|
+
/** `stamp` cache keys; the entries themselves live in a weak side table keyed by schema identity. */
|
|
111
112
|
const kJsonWireSchema = Symbol("pi.schema.json.wire");
|
|
112
113
|
const kArkWireSchema = Symbol("pi.schema.ark.wire");
|
|
113
114
|
const kStrippedSchema = Symbol("pi.schema.descriptions.stripped");
|
|
@@ -602,7 +603,9 @@ export function toolWireSchema(tool: Tool): Record<string, unknown> {
|
|
|
602
603
|
const params: TSchema = tool.parameters;
|
|
603
604
|
if (isArkSchema(params)) return arkToWireSchema(params);
|
|
604
605
|
return stamp(params as Record<string, unknown>, kJsonWireSchema, p => {
|
|
605
|
-
|
|
606
|
+
// Legacy schemas may carry non-cloneable metadata (helper functions); the JSON
|
|
607
|
+
// fallback drops it the same way the wire serializer always has.
|
|
608
|
+
const raw = isArkJsonAst(p) ? arkJsonAstToWire(p) : structuredCloneJSON(p);
|
|
606
609
|
const upgraded = upgradeJsonSchemaTo202012(raw) as Record<string, unknown>;
|
|
607
610
|
return postProcessJsonSchema(upgraded);
|
|
608
611
|
});
|
|
@@ -663,10 +666,9 @@ function stripSchemaDescriptionsInPlace(node: unknown): void {
|
|
|
663
666
|
|
|
664
667
|
/**
|
|
665
668
|
* Return a deep clone of `schema` with every `description` annotation removed.
|
|
666
|
-
* The result is memoized
|
|
667
|
-
*
|
|
668
|
-
*
|
|
669
|
-
* system-prompt/UI rendering.
|
|
669
|
+
* The result is memoized against the input (`stamp`) so repeated provider
|
|
670
|
+
* requests reuse the same stripped object; the input is never mutated, so the
|
|
671
|
+
* memoized `toolWireSchema` result stays intact for system-prompt/UI rendering.
|
|
670
672
|
*/
|
|
671
673
|
export function stripSchemaDescriptions(schema: Record<string, unknown>): Record<string, unknown> {
|
|
672
674
|
return stamp(schema, kStrippedSchema, source => {
|
|
@@ -682,7 +684,7 @@ export function stripSchemaDescriptions(schema: Record<string, unknown>): Record
|
|
|
682
684
|
* Used when the full tool catalog is rendered into the system prompt instead, so
|
|
683
685
|
* the descriptions ride the wire once (in the prompt) rather than duplicated on
|
|
684
686
|
* every tool definition. Parameters are resolved to wire JSON Schema and cloned,
|
|
685
|
-
* leaving the original tool objects and the
|
|
687
|
+
* leaving the original tool objects and the memoized schema cache untouched.
|
|
686
688
|
*/
|
|
687
689
|
export function stripToolDescriptions(tools: readonly Tool[]): Tool[] {
|
|
688
690
|
return tools.map(tool => ({
|
package/src/utils.ts
CHANGED
|
@@ -45,8 +45,10 @@ export function normalizeResponsesToolCallId(
|
|
|
45
45
|
id: string,
|
|
46
46
|
itemPrefix: ResponsesToolItemIdPrefix = "fc",
|
|
47
47
|
): { callId: string; itemId: string } {
|
|
48
|
-
const
|
|
49
|
-
if (
|
|
48
|
+
const sep = id.search(/[\n|]/);
|
|
49
|
+
if (sep > 0) {
|
|
50
|
+
const callId = id.slice(0, sep);
|
|
51
|
+
const itemId = id.slice(sep + 1);
|
|
50
52
|
return { callId, itemId: normalizeResponsesItemId(itemId, itemPrefix) };
|
|
51
53
|
}
|
|
52
54
|
const hash = Bun.hash(id).toString(36);
|
|
@@ -324,6 +326,15 @@ export function stripUnpairedOpenAIResponsesComputerReasoningIdsForReplay(items:
|
|
|
324
326
|
* Returns `undefined` for hidden-empty turns that only contain reasoning and an
|
|
325
327
|
* empty assistant message, allowing callers to rebuild visible transcript
|
|
326
328
|
* history instead of replaying stale native state.
|
|
329
|
+
*
|
|
330
|
+
* When the turn does carry replayable output, whitespace-only assistant
|
|
331
|
+
* messages are dropped rather than replayed. gpt-5.6 Codex ends a turn whose
|
|
332
|
+
* text all landed in the `commentary` phase with an empty `final_answer`
|
|
333
|
+
* message; replaying that item verbatim seeds the next turn with an empty slot
|
|
334
|
+
* the model then fills - `""` → `"\n\n"` → stray words → non-Latin residue -
|
|
335
|
+
* and each contaminated item is replayed in turn, so the drift compounds until
|
|
336
|
+
* the visible answer collapses. Codex CLI replays the empty item too
|
|
337
|
+
* (openai/codex#32389); this is the layer where omp can refuse to.
|
|
327
338
|
*/
|
|
328
339
|
export function sanitizeOpenAIResponsesAssistantHistoryItemsForReplay(
|
|
329
340
|
items: Array<Record<string, unknown>>,
|
|
@@ -331,34 +342,68 @@ export function sanitizeOpenAIResponsesAssistantHistoryItemsForReplay(
|
|
|
331
342
|
): ResponseInput | undefined {
|
|
332
343
|
const sanitized = sanitizeOpenAIResponsesHistoryItemsForReplay(items, options);
|
|
333
344
|
let hasReplayableAssistantOutput = false;
|
|
345
|
+
let hasEmptyAssistantMessage = false;
|
|
334
346
|
|
|
335
347
|
for (const item of sanitized) {
|
|
336
348
|
if (item.type === "reasoning") continue;
|
|
337
|
-
if (item
|
|
338
|
-
|
|
339
|
-
|
|
349
|
+
if (isEmptyAssistantMessage(item)) {
|
|
350
|
+
hasEmptyAssistantMessage = true;
|
|
351
|
+
continue;
|
|
340
352
|
}
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
353
|
+
hasReplayableAssistantOutput = true;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (!hasReplayableAssistantOutput) return undefined;
|
|
357
|
+
if (!hasEmptyAssistantMessage) return sanitized;
|
|
358
|
+
return dropEmptyAssistantMessagesForReplay(sanitized);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function isEmptyAssistantMessage(item: OpenAIResponsesReplayItem): boolean {
|
|
362
|
+
if (item.type !== "message" || item.role !== "assistant") return false;
|
|
363
|
+
if (typeof item.content === "string") return !NON_WHITESPACE_RE.test(item.content);
|
|
364
|
+
for (const part of item.content) {
|
|
365
|
+
if (part.type === "output_text" && NON_WHITESPACE_RE.test(part.text)) return false;
|
|
366
|
+
if (part.type === "refusal" && NON_WHITESPACE_RE.test(part.refusal)) return false;
|
|
367
|
+
}
|
|
368
|
+
return true;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Remove whitespace-only assistant messages plus any reasoning item left with
|
|
373
|
+
* no output to introduce: a reasoning item is only meaningful ahead of the
|
|
374
|
+
* output it produced, and a dangling one is rejected by the Responses API when
|
|
375
|
+
* it carries an id.
|
|
376
|
+
*/
|
|
377
|
+
function dropEmptyAssistantMessagesForReplay(items: ResponseInput): ResponseInput {
|
|
378
|
+
const kept: ResponseInput = [];
|
|
379
|
+
let pendingReasoning: ResponseInput = [];
|
|
380
|
+
let removedAssistantMessage = false;
|
|
381
|
+
const flushReasoning = (): void => {
|
|
382
|
+
kept.push(...pendingReasoning);
|
|
383
|
+
pendingReasoning = [];
|
|
384
|
+
};
|
|
385
|
+
for (const item of items) {
|
|
386
|
+
if (isOpenAIResponsesClientInputBoundary(item as unknown as Record<string, unknown>)) {
|
|
387
|
+
pendingReasoning = [];
|
|
388
|
+
removedAssistantMessage = false;
|
|
389
|
+
kept.push(item);
|
|
346
390
|
continue;
|
|
347
391
|
}
|
|
348
|
-
|
|
349
|
-
if (
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
if (part.type === "refusal" && NON_WHITESPACE_RE.test(part.refusal)) {
|
|
354
|
-
hasReplayableAssistantOutput = true;
|
|
355
|
-
break;
|
|
356
|
-
}
|
|
392
|
+
if (item.type === "reasoning") {
|
|
393
|
+
if (removedAssistantMessage) pendingReasoning = [];
|
|
394
|
+
removedAssistantMessage = false;
|
|
395
|
+
pendingReasoning.push(item);
|
|
396
|
+
continue;
|
|
357
397
|
}
|
|
358
|
-
if (
|
|
398
|
+
if (isEmptyAssistantMessage(item)) {
|
|
399
|
+
removedAssistantMessage = true;
|
|
400
|
+
continue;
|
|
401
|
+
}
|
|
402
|
+
flushReasoning();
|
|
403
|
+
removedAssistantMessage = false;
|
|
404
|
+
kept.push(item);
|
|
359
405
|
}
|
|
360
|
-
|
|
361
|
-
return hasReplayableAssistantOutput ? sanitized : undefined;
|
|
406
|
+
return kept;
|
|
362
407
|
}
|
|
363
408
|
|
|
364
409
|
/**
|