@f5-sales-demo/pi-ai 22.7.1 → 22.7.2
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/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@f5-sales-demo/pi-ai",
|
|
4
|
-
"version": "22.7.
|
|
4
|
+
"version": "22.7.2",
|
|
5
5
|
"description": "Unified LLM API with automatic model discovery and provider configuration",
|
|
6
6
|
"homepage": "https://github.com/f5-sales-demo/xcsh",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"@anthropic-ai/sdk": "0.124.0",
|
|
47
47
|
"@aws-sdk/client-bedrock-runtime": "3.1127.0",
|
|
48
48
|
"@bufbuild/protobuf": "2.14.1",
|
|
49
|
-
"@f5-sales-demo/pi-utils": "22.7.
|
|
49
|
+
"@f5-sales-demo/pi-utils": "22.7.2",
|
|
50
50
|
"@google/genai": "2.21.0",
|
|
51
51
|
"@sinclair/typebox": "0.34.52",
|
|
52
52
|
"@smithy/node-http-handler": "4.12.1",
|
|
@@ -192,7 +192,7 @@ function getCacheControl(
|
|
|
192
192
|
}
|
|
193
193
|
|
|
194
194
|
// Stealth mode: Mimic Claude Code headers and tool prefixing.
|
|
195
|
-
export const claudeCodeVersion = "2.1.
|
|
195
|
+
export const claudeCodeVersion = "2.1.287";
|
|
196
196
|
export const claudeToolPrefix: string = "proxy_";
|
|
197
197
|
export const claudeCodeSystemInstruction = "You are a Claude agent, built on Anthropic's Claude Agent SDK.";
|
|
198
198
|
|
|
@@ -210,6 +210,7 @@ interface CodexStreamRuntime {
|
|
|
210
210
|
outputSlots: Map<number, { item: CodexEventItem | null; block: CodexOutputBlock | null; index: number }>;
|
|
211
211
|
currentIndex: number;
|
|
212
212
|
completedItemIds: Set<string>;
|
|
213
|
+
completedContentIndices: Set<number>;
|
|
213
214
|
currentBlock: CodexOutputBlock | null;
|
|
214
215
|
nativeOutputItems: Array<Record<string, unknown>>;
|
|
215
216
|
websocketStreamRetries: number;
|
|
@@ -221,6 +222,7 @@ interface CodexStreamRuntime {
|
|
|
221
222
|
}
|
|
222
223
|
|
|
223
224
|
interface CodexStreamProcessingContext {
|
|
225
|
+
runtime?: CodexStreamRuntime;
|
|
224
226
|
model: Model<"openai-codex-responses">;
|
|
225
227
|
output: AssistantMessage;
|
|
226
228
|
stream: AssistantMessageEventStream;
|
|
@@ -300,7 +302,13 @@ function getCodexProviderSessionState(
|
|
|
300
302
|
}
|
|
301
303
|
|
|
302
304
|
function createCodexWebSocketTransportError(message: string): Error {
|
|
303
|
-
return new Error(`${CODEX_WEBSOCKET_TRANSPORT_ERROR_PREFIX}: ${message}`)
|
|
305
|
+
return Object.assign(new Error(`${CODEX_WEBSOCKET_TRANSPORT_ERROR_PREFIX}: ${message}`), {
|
|
306
|
+
codexTransportInterruption: message.startsWith("websocket closed")
|
|
307
|
+
? "socket_closed"
|
|
308
|
+
: message === "idle timeout waiting for websocket" || message === "timeout waiting for first websocket event"
|
|
309
|
+
? "idle_timeout"
|
|
310
|
+
: undefined,
|
|
311
|
+
});
|
|
304
312
|
}
|
|
305
313
|
|
|
306
314
|
function isCodexWebSocketFatalError(error: Error): boolean {
|
|
@@ -913,6 +921,7 @@ function createCodexStreamRuntime(initial: {
|
|
|
913
921
|
outputSlots: new Map(),
|
|
914
922
|
currentIndex: -1,
|
|
915
923
|
completedItemIds: new Set(),
|
|
924
|
+
completedContentIndices: new Set(),
|
|
916
925
|
nativeOutputItems: [],
|
|
917
926
|
websocketStreamRetries: 0,
|
|
918
927
|
providerRetryAttempt: 0,
|
|
@@ -1137,6 +1146,7 @@ function handleCodexStreamEvent(args: {
|
|
|
1137
1146
|
if (itemId && runtime.completedItemIds.has(itemId)) return firstTokenTime;
|
|
1138
1147
|
if (itemId) runtime.completedItemIds.add(itemId);
|
|
1139
1148
|
handleOutputItemDone(model, output, stream, runtime, rawEvent, blockIndex);
|
|
1149
|
+
if (runtime.currentIndex >= 0) runtime.completedContentIndices.add(runtime.currentIndex);
|
|
1140
1150
|
runtime.outputSlots.delete(
|
|
1141
1151
|
typeof rawEvent.output_index === "number" ? rawEvent.output_index : runtime.currentIndex,
|
|
1142
1152
|
);
|
|
@@ -1305,6 +1315,7 @@ function handleOutputItemDone(
|
|
|
1305
1315
|
): void {
|
|
1306
1316
|
const item = structuredCloneJSON(rawEvent.item) as CodexEventItem;
|
|
1307
1317
|
runtime.nativeOutputItems.push(item as unknown as Record<string, unknown>);
|
|
1318
|
+
output.providerPayload = createOpenAIResponsesHistoryPayload(model.provider, runtime.nativeOutputItems);
|
|
1308
1319
|
if (item.type === "custom_tool_call" && runtime.currentBlock?.type === "toolCall") {
|
|
1309
1320
|
const property = runtime.grammarToolInputProperties?.get(item.name) ?? "input";
|
|
1310
1321
|
Object.assign(runtime.currentBlock, {
|
|
@@ -1360,7 +1371,7 @@ function handleOutputItemDone(
|
|
|
1360
1371
|
type: "toolCall",
|
|
1361
1372
|
id: `${item.call_id}|${item.id}`,
|
|
1362
1373
|
name: item.name,
|
|
1363
|
-
arguments:
|
|
1374
|
+
arguments: JSON.parse(item.arguments || "{}"),
|
|
1364
1375
|
};
|
|
1365
1376
|
if (runtime.currentBlock?.type === "toolCall") {
|
|
1366
1377
|
Object.assign(runtime.currentBlock, toolCall);
|
|
@@ -1561,7 +1572,8 @@ async function tryRestartCodexSseTransport(
|
|
|
1561
1572
|
if (error !== originalError) throw error;
|
|
1562
1573
|
return false;
|
|
1563
1574
|
}
|
|
1564
|
-
if (runtime.transportRestartAttempt >= 2)
|
|
1575
|
+
if (runtime.transportRestartAttempt >= Math.max(0, context.options?.maxRetries ?? 2))
|
|
1576
|
+
throw runtime.firstTransportFailure;
|
|
1565
1577
|
runtime.transportRestartAttempt += 1;
|
|
1566
1578
|
try {
|
|
1567
1579
|
await runtime.eventStream.return(undefined);
|
|
@@ -1573,6 +1585,7 @@ async function tryRestartCodexSseTransport(
|
|
|
1573
1585
|
runtime.currentIndex = -1;
|
|
1574
1586
|
runtime.outputSlots.clear();
|
|
1575
1587
|
runtime.completedItemIds.clear();
|
|
1588
|
+
runtime.completedContentIndices.clear();
|
|
1576
1589
|
runtime.nativeOutputItems.length = 0;
|
|
1577
1590
|
runtime.sawResponseAccepted = false;
|
|
1578
1591
|
runtime.sawOutputEvent = false;
|
|
@@ -1591,7 +1604,10 @@ async function tryRestartCodexSseTransport(
|
|
|
1591
1604
|
resetCodexWebSocketAppendState(state);
|
|
1592
1605
|
resetCodexSessionMetadata(state);
|
|
1593
1606
|
}
|
|
1594
|
-
await sleepBeforeCodexSseRestart(
|
|
1607
|
+
await sleepBeforeCodexSseRestart(
|
|
1608
|
+
500 * (runtime.transportRestartAttempt + Math.max(0, 2 - (context.options?.maxRetries ?? 2))),
|
|
1609
|
+
context.requestSetup.requestSignal,
|
|
1610
|
+
);
|
|
1595
1611
|
try {
|
|
1596
1612
|
const next = await openCodexSseTransport(
|
|
1597
1613
|
context.requestContext,
|
|
@@ -1640,6 +1656,14 @@ async function tryRecoverCodexContinuation(
|
|
|
1640
1656
|
resetCodexWebSocketAppendState(state);
|
|
1641
1657
|
resetCodexSessionMetadata(state);
|
|
1642
1658
|
if (!eligible) return false;
|
|
1659
|
+
if (context.options?.maxRetries !== undefined) {
|
|
1660
|
+
if (runtime.transportRestartAttempt >= context.options.maxRetries) return false;
|
|
1661
|
+
runtime.transportRestartAttempt += 1;
|
|
1662
|
+
await sleepBeforeCodexSseRestart(
|
|
1663
|
+
500 * (runtime.transportRestartAttempt + Math.max(0, 2 - (context.options?.maxRetries ?? 2))),
|
|
1664
|
+
context.requestSetup.requestSignal,
|
|
1665
|
+
);
|
|
1666
|
+
}
|
|
1643
1667
|
runtime.continuationRecoveryAttempt += 1;
|
|
1644
1668
|
logCodexDebug("codex continuation recovery", {
|
|
1645
1669
|
reason: "previous_response_unavailable",
|
|
@@ -1681,6 +1705,14 @@ async function tryReconnectCodexWebSocketOnConnectionLimit(
|
|
|
1681
1705
|
}
|
|
1682
1706
|
|
|
1683
1707
|
// Close the stale connection so getOrCreateCodexWebSocketConnection creates a fresh one.
|
|
1708
|
+
if (context.options?.maxRetries !== undefined) {
|
|
1709
|
+
if (runtime.transportRestartAttempt >= context.options.maxRetries) return false;
|
|
1710
|
+
runtime.transportRestartAttempt += 1;
|
|
1711
|
+
await sleepBeforeCodexSseRestart(
|
|
1712
|
+
500 * (runtime.transportRestartAttempt + Math.max(0, 2 - (context.options?.maxRetries ?? 2))),
|
|
1713
|
+
context.requestSetup.requestSignal,
|
|
1714
|
+
);
|
|
1715
|
+
}
|
|
1684
1716
|
websocketState.connection?.close("connection_limit");
|
|
1685
1717
|
websocketState.connection = undefined;
|
|
1686
1718
|
resetCodexWebSocketAppendState(websocketState);
|
|
@@ -1699,6 +1731,7 @@ async function tryReconnectCodexWebSocketOnConnectionLimit(
|
|
|
1699
1731
|
runtime.currentBlock = null;
|
|
1700
1732
|
runtime.outputSlots.clear();
|
|
1701
1733
|
runtime.completedItemIds.clear();
|
|
1734
|
+
runtime.completedContentIndices.clear();
|
|
1702
1735
|
runtime.nativeOutputItems.length = 0;
|
|
1703
1736
|
runtime.stagedEvents.length = 0;
|
|
1704
1737
|
resetOutputState(context.output);
|
|
@@ -1728,6 +1761,14 @@ async function tryReplayWebsocketFailureOverSse(
|
|
|
1728
1761
|
!runtime.sawTerminalEvent &&
|
|
1729
1762
|
!context.options?.signal?.aborted;
|
|
1730
1763
|
if (!canReplay) return false;
|
|
1764
|
+
if (context.options?.maxRetries !== undefined) {
|
|
1765
|
+
if (runtime.transportRestartAttempt >= context.options.maxRetries) return false;
|
|
1766
|
+
runtime.transportRestartAttempt += 1;
|
|
1767
|
+
await sleepBeforeCodexSseRestart(
|
|
1768
|
+
500 * (runtime.transportRestartAttempt + Math.max(0, 2 - (context.options?.maxRetries ?? 2))),
|
|
1769
|
+
context.requestSetup.requestSignal,
|
|
1770
|
+
);
|
|
1771
|
+
}
|
|
1731
1772
|
|
|
1732
1773
|
const state = websocketState;
|
|
1733
1774
|
const streamError = error instanceof Error ? error : new Error(String(error));
|
|
@@ -1761,6 +1802,7 @@ async function tryReplayWebsocketFailureOverSse(
|
|
|
1761
1802
|
runtime.currentBlock = null;
|
|
1762
1803
|
runtime.outputSlots.clear();
|
|
1763
1804
|
runtime.completedItemIds.clear();
|
|
1805
|
+
runtime.completedContentIndices.clear();
|
|
1764
1806
|
runtime.nativeOutputItems.length = 0;
|
|
1765
1807
|
runtime.stagedEvents.length = 0;
|
|
1766
1808
|
resetOutputState(context.output);
|
|
@@ -1803,6 +1845,7 @@ async function tryRetryCodexProviderError(
|
|
|
1803
1845
|
runtime.currentBlock = null;
|
|
1804
1846
|
runtime.outputSlots.clear();
|
|
1805
1847
|
runtime.completedItemIds.clear();
|
|
1848
|
+
runtime.completedContentIndices.clear();
|
|
1806
1849
|
runtime.sawTerminalEvent = false;
|
|
1807
1850
|
runtime.stagedEvents.length = 0;
|
|
1808
1851
|
resetOutputState(context.output);
|
|
@@ -1866,6 +1909,37 @@ async function handleCodexStreamFailure(
|
|
|
1866
1909
|
resetCodexWebSocketAppendState(context.requestContext.websocketState);
|
|
1867
1910
|
resetCodexSessionMetadata(context.requestContext.websocketState);
|
|
1868
1911
|
}
|
|
1912
|
+
const runtime = context.runtime;
|
|
1913
|
+
const interrupted =
|
|
1914
|
+
!context.requestSetup.requestSignal.aborted &&
|
|
1915
|
+
runtime &&
|
|
1916
|
+
!runtime.sawTerminalEvent &&
|
|
1917
|
+
!(error instanceof CodexProviderStreamError) &&
|
|
1918
|
+
(error instanceof CodexSseIdleTimeoutError ||
|
|
1919
|
+
isTransportClosureError(error) ||
|
|
1920
|
+
(error as { codexTransportInterruption?: string })?.codexTransportInterruption);
|
|
1921
|
+
if (interrupted) {
|
|
1922
|
+
output.interruption = {
|
|
1923
|
+
transport: runtime.transport,
|
|
1924
|
+
classification:
|
|
1925
|
+
error instanceof CodexSseIdleTimeoutError ||
|
|
1926
|
+
(error as { codexTransportInterruption?: string }).codexTransportInterruption === "idle_timeout"
|
|
1927
|
+
? "idle_timeout"
|
|
1928
|
+
: (error as { code?: string }).code === "ERR_STREAM_PREMATURE_CLOSE"
|
|
1929
|
+
? "premature_eof"
|
|
1930
|
+
: "socket_closed",
|
|
1931
|
+
providerRetriesConsumed: runtime.transportRestartAttempt,
|
|
1932
|
+
completedContentIndices: [...runtime.completedContentIndices].sort((a, b) => a - b),
|
|
1933
|
+
};
|
|
1934
|
+
output.providerPayload = createOpenAIResponsesHistoryPayload(context.model.provider, runtime.nativeOutputItems);
|
|
1935
|
+
}
|
|
1936
|
+
if (runtime) {
|
|
1937
|
+
try {
|
|
1938
|
+
await runtime.eventStream.return(undefined);
|
|
1939
|
+
} catch {
|
|
1940
|
+
/* Preserve the original failure. */
|
|
1941
|
+
}
|
|
1942
|
+
}
|
|
1869
1943
|
output.stopReason = context.options?.signal?.aborted ? "aborted" : "error";
|
|
1870
1944
|
output.errorMessage = await finalizeErrorMessage(error, context.requestContext.rawRequestDump);
|
|
1871
1945
|
output.providerFailureCode = sanitizeProviderFailureCode(
|
|
@@ -1905,6 +1979,7 @@ export const streamOpenAICodexResponses: StreamFunction<"openai-codex-responses"
|
|
|
1905
1979
|
}
|
|
1906
1980
|
|
|
1907
1981
|
processingContext = {
|
|
1982
|
+
runtime,
|
|
1908
1983
|
model,
|
|
1909
1984
|
output,
|
|
1910
1985
|
stream,
|
|
@@ -2828,7 +2903,7 @@ function convertMessages(model: Model<"openai-codex-responses">, context: Contex
|
|
|
2828
2903
|
|
|
2829
2904
|
const outputItems: ResponseInput = [];
|
|
2830
2905
|
for (const block of msg.content) {
|
|
2831
|
-
if (block.type === "thinking" && msg.stopReason !== "error") {
|
|
2906
|
+
if (block.type === "thinking" && (msg.stopReason !== "error" || !!assistantMsg.interruption)) {
|
|
2832
2907
|
if (block.thinkingSignature) {
|
|
2833
2908
|
outputItems.push(JSON.parse(block.thinkingSignature) as ResponseReasoningItem);
|
|
2834
2909
|
}
|
|
@@ -135,7 +135,7 @@ export function convertResponsesAssistantMessage<TApi extends Api>(
|
|
|
135
135
|
assistantMsg.model !== model.id && assistantMsg.provider === model.provider && assistantMsg.api === model.api;
|
|
136
136
|
|
|
137
137
|
for (const block of assistantMsg.content) {
|
|
138
|
-
if (block.type === "thinking" && assistantMsg.stopReason !== "error") {
|
|
138
|
+
if (block.type === "thinking" && (assistantMsg.stopReason !== "error" || !!assistantMsg.interruption)) {
|
|
139
139
|
if (!includeThinkingSignatures) {
|
|
140
140
|
continue;
|
|
141
141
|
}
|
|
@@ -69,7 +69,9 @@ export function transformMessages<TApi extends Api>(
|
|
|
69
69
|
// Aborted/errored messages may have partially-streamed thinking signatures.
|
|
70
70
|
// A partial signature is invalid and will be rejected by the API, so we must
|
|
71
71
|
// strip signatures from thinking blocks in these messages.
|
|
72
|
-
const hasInvalidSignatures =
|
|
72
|
+
const hasInvalidSignatures =
|
|
73
|
+
assistantMsg.stopReason === "aborted" ||
|
|
74
|
+
(assistantMsg.stopReason === "error" && !assistantMsg.interruption);
|
|
73
75
|
|
|
74
76
|
const transformedContent = assistantMsg.content.flatMap(block => {
|
|
75
77
|
if (block.type === "thinking") {
|
|
@@ -205,7 +207,10 @@ export function transformMessages<TApi extends Api>(
|
|
|
205
207
|
const assistantMsg = msg as AssistantMessage;
|
|
206
208
|
const toolCalls = assistantMsg.content.filter(b => b.type === "toolCall") as ToolCall[];
|
|
207
209
|
|
|
208
|
-
if (
|
|
210
|
+
if (
|
|
211
|
+
(assistantMsg.stopReason === "error" && !assistantMsg.interruption) ||
|
|
212
|
+
assistantMsg.stopReason === "aborted"
|
|
213
|
+
) {
|
|
209
214
|
// Keep the assistant message with tool calls intact. If real tool results follow, preserve them;
|
|
210
215
|
// otherwise synthesize aborted results before the next turn boundary.
|
|
211
216
|
result.push(msg);
|
package/src/types.ts
CHANGED
|
@@ -442,6 +442,14 @@ export interface DeveloperMessage {
|
|
|
442
442
|
}
|
|
443
443
|
|
|
444
444
|
export interface AssistantMessage {
|
|
445
|
+
/** Completed content retained after a typed transport interruption. */
|
|
446
|
+
interruption?: {
|
|
447
|
+
transport: "sse" | "websocket";
|
|
448
|
+
classification: "socket_closed" | "premature_eof" | "idle_timeout";
|
|
449
|
+
providerRetriesConsumed: number;
|
|
450
|
+
toolChoiceServed?: boolean;
|
|
451
|
+
completedContentIndices: number[];
|
|
452
|
+
};
|
|
445
453
|
/** A queued background request; poll using the same provider route. */
|
|
446
454
|
deferred?: DeferredHandle;
|
|
447
455
|
providerRewrite?: {
|