fix(ai,agent,coding-agent): share UUIDv7 and use for Codex (#6834)
This commit is contained in:
@@ -2,6 +2,10 @@
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Breaking Changes
|
||||||
|
|
||||||
|
- Moved the `uuidv7` export to `@earendil-works/pi-ai`.
|
||||||
|
|
||||||
## [0.80.10] - 2026-07-16
|
## [0.80.10] - 2026-07-16
|
||||||
|
|
||||||
## [0.80.9] - 2026-07-16
|
## [0.80.9] - 2026-07-16
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
|
import { uuidv7 } from "@earendil-works/pi-ai";
|
||||||
import type { FileSystem, JsonlSessionMetadata, LeafEntry, SessionStorage, SessionTreeEntry } from "../types.ts";
|
import type { FileSystem, JsonlSessionMetadata, LeafEntry, SessionStorage, SessionTreeEntry } from "../types.ts";
|
||||||
import { SessionError, toError } from "../types.ts";
|
import { SessionError, toError } from "../types.ts";
|
||||||
import { getFileSystemResultOrThrow } from "./repo-utils.ts";
|
import { getFileSystemResultOrThrow } from "./repo-utils.ts";
|
||||||
import { uuidv7 } from "./uuid.ts";
|
|
||||||
|
|
||||||
type JsonlSessionStorageFileSystem = Pick<FileSystem, "readTextFile" | "readTextLines" | "writeFile" | "appendFile">;
|
type JsonlSessionStorageFileSystem = Pick<FileSystem, "readTextFile" | "readTextLines" | "writeFile" | "appendFile">;
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { uuidv7 } from "@earendil-works/pi-ai";
|
||||||
import {
|
import {
|
||||||
type LeafEntry,
|
type LeafEntry,
|
||||||
SessionError,
|
SessionError,
|
||||||
@@ -5,7 +6,6 @@ import {
|
|||||||
type SessionStorage,
|
type SessionStorage,
|
||||||
type SessionTreeEntry,
|
type SessionTreeEntry,
|
||||||
} from "../types.ts";
|
} from "../types.ts";
|
||||||
import { uuidv7 } from "./uuid.ts";
|
|
||||||
|
|
||||||
function updateLabelCache(labelsById: Map<string, string>, entry: SessionTreeEntry): void {
|
function updateLabelCache(labelsById: Map<string, string>, entry: SessionTreeEntry): void {
|
||||||
if (entry.type !== "label") return;
|
if (entry.type !== "label") return;
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { uuidv7 } from "@earendil-works/pi-ai";
|
||||||
import {
|
import {
|
||||||
type FileError,
|
type FileError,
|
||||||
type Result,
|
type Result,
|
||||||
@@ -7,7 +8,6 @@ import {
|
|||||||
type SessionTreeEntry,
|
type SessionTreeEntry,
|
||||||
} from "../types.ts";
|
} from "../types.ts";
|
||||||
import { Session } from "./session.ts";
|
import { Session } from "./session.ts";
|
||||||
import { uuidv7 } from "./uuid.ts";
|
|
||||||
|
|
||||||
export function createSessionId(): string {
|
export function createSessionId(): string {
|
||||||
return uuidv7();
|
return uuidv7();
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ export * from "./harness/session/memory-repo.ts";
|
|||||||
export * from "./harness/session/memory-storage.ts";
|
export * from "./harness/session/memory-storage.ts";
|
||||||
export * from "./harness/session/repo-utils.ts";
|
export * from "./harness/session/repo-utils.ts";
|
||||||
export * from "./harness/session/session.ts";
|
export * from "./harness/session/session.ts";
|
||||||
export { uuidv7 } from "./harness/session/uuid.ts";
|
|
||||||
export * from "./harness/skills.ts";
|
export * from "./harness/skills.ts";
|
||||||
export * from "./harness/system-prompt.ts";
|
export * from "./harness/system-prompt.ts";
|
||||||
// Harness
|
// Harness
|
||||||
|
|||||||
@@ -2,8 +2,13 @@
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added a shared `uuidv7` utility for time-ordered identifiers.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed sessionless OpenAI Codex WebSocket requests to use UUIDv7 request IDs, enabling models that reject UUIDv4 IDs.
|
||||||
- Fixed GitHub Copilot long-context pricing tiers in generated model metadata ([#6668](https://github.com/earendil-works/pi/issues/6668)).
|
- Fixed GitHub Copilot long-context pricing tiers in generated model metadata ([#6668](https://github.com/earendil-works/pi/issues/6668)).
|
||||||
- Fixed Kimi Coding subscription models to report API-equivalent implied costs when models.dev reports zero pricing.
|
- Fixed Kimi Coding subscription models to report API-equivalent implied costs when models.dev reports zero pricing.
|
||||||
- Fixed OpenAI Responses early stream endings to be classified as retryable provider errors ([#6727](https://github.com/earendil-works/pi/issues/6727)).
|
- Fixed OpenAI Responses early stream endings to be classified as retryable provider errors ([#6727](https://github.com/earendil-works/pi/issues/6727)).
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ import { formatProviderError, normalizeProviderError } from "../utils/error-body
|
|||||||
import { AssistantMessageEventStream } from "../utils/event-stream.ts";
|
import { AssistantMessageEventStream } from "../utils/event-stream.ts";
|
||||||
import { headersToRecord } from "../utils/headers.ts";
|
import { headersToRecord } from "../utils/headers.ts";
|
||||||
import { resolveHttpProxyUrlForTarget } from "../utils/node-http-proxy.ts";
|
import { resolveHttpProxyUrlForTarget } from "../utils/node-http-proxy.ts";
|
||||||
|
import { uuidv7 } from "../utils/uuid.ts";
|
||||||
import { clampOpenAIPromptCacheKey } from "./openai-prompt-cache.ts";
|
import { clampOpenAIPromptCacheKey } from "./openai-prompt-cache.ts";
|
||||||
import { convertResponsesMessages, convertResponsesTools, processResponsesStream } from "./openai-responses-shared.ts";
|
import { convertResponsesMessages, convertResponsesTools, processResponsesStream } from "./openai-responses-shared.ts";
|
||||||
import { buildBaseOptions } from "./simple-options.ts";
|
import { buildBaseOptions } from "./simple-options.ts";
|
||||||
@@ -259,7 +260,7 @@ export const stream: StreamFunction<"openai-codex-responses", OpenAICodexRespons
|
|||||||
body = nextBody as RequestBody;
|
body = nextBody as RequestBody;
|
||||||
}
|
}
|
||||||
const codexSessionId = clampOpenAIPromptCacheKey(options?.sessionId);
|
const codexSessionId = clampOpenAIPromptCacheKey(options?.sessionId);
|
||||||
const websocketRequestId = codexSessionId || createCodexRequestId();
|
const websocketRequestId = codexSessionId || uuidv7();
|
||||||
const sseHeaders = buildSSEHeaders(model.headers, options?.headers, accountId, apiKey, codexSessionId);
|
const sseHeaders = buildSSEHeaders(model.headers, options?.headers, accountId, apiKey, codexSessionId);
|
||||||
const websocketHeaders = buildWebSocketHeaders(
|
const websocketHeaders = buildWebSocketHeaders(
|
||||||
model.headers,
|
model.headers,
|
||||||
@@ -1505,13 +1506,6 @@ function extractAccountId(token: string): string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function createCodexRequestId(): string {
|
|
||||||
if (typeof globalThis.crypto?.randomUUID === "function") {
|
|
||||||
return globalThis.crypto.randomUUID();
|
|
||||||
}
|
|
||||||
return `codex_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildBaseCodexHeaders(
|
function buildBaseCodexHeaders(
|
||||||
initHeaders: Record<string, string> | undefined,
|
initHeaders: Record<string, string> | undefined,
|
||||||
additionalHeaders: ProviderHeaders | undefined,
|
additionalHeaders: ProviderHeaders | undefined,
|
||||||
|
|||||||
@@ -42,4 +42,5 @@ export * from "./utils/json-parse.ts";
|
|||||||
export * from "./utils/overflow.ts";
|
export * from "./utils/overflow.ts";
|
||||||
export * from "./utils/retry.ts";
|
export * from "./utils/retry.ts";
|
||||||
export * from "./utils/typebox-helpers.ts";
|
export * from "./utils/typebox-helpers.ts";
|
||||||
|
export { uuidv7 } from "./utils/uuid.ts";
|
||||||
export * from "./utils/validation.ts";
|
export * from "./utils/validation.ts";
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
let lastTimestamp = -Infinity;
|
let lastTimestamp = -Infinity;
|
||||||
let sequence = 0;
|
let sequence = 0;
|
||||||
|
|
||||||
function fillRandomBytes(bytes: Uint8Array): void {
|
function fillRandomBytes(bytes: Uint8Array<ArrayBuffer>): void {
|
||||||
const crypto = globalThis.crypto;
|
if (globalThis.crypto?.getRandomValues) {
|
||||||
if (crypto?.getRandomValues) {
|
globalThis.crypto.getRandomValues(bytes);
|
||||||
crypto.getRandomValues(bytes);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (let i = 0; i < bytes.length; i++) {
|
for (let i = 0; i < bytes.length; i++) {
|
||||||
@@ -12,6 +11,7 @@ function fillRandomBytes(bytes: Uint8Array): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Generate a time-ordered UUIDv7. */
|
||||||
export function uuidv7(): string {
|
export function uuidv7(): string {
|
||||||
const random = new Uint8Array(16);
|
const random = new Uint8Array(16);
|
||||||
fillRandomBytes(random);
|
fillRandomBytes(random);
|
||||||
@@ -22,9 +22,7 @@ export function uuidv7(): string {
|
|||||||
lastTimestamp = timestamp;
|
lastTimestamp = timestamp;
|
||||||
} else {
|
} else {
|
||||||
sequence = (sequence + 1) >>> 0;
|
sequence = (sequence + 1) >>> 0;
|
||||||
if (sequence === 0) {
|
if (sequence === 0) lastTimestamp++;
|
||||||
lastTimestamp++;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const bytes = new Uint8Array(16);
|
const bytes = new Uint8Array(16);
|
||||||
@@ -45,10 +43,6 @@ export function uuidv7(): string {
|
|||||||
bytes[14] = random[14];
|
bytes[14] = random[14];
|
||||||
bytes[15] = random[15];
|
bytes[15] = random[15];
|
||||||
|
|
||||||
return formatUuid(bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatUuid(bytes: Uint8Array): string {
|
|
||||||
const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0"));
|
const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0"));
|
||||||
return `${hex.slice(0, 4).join("")}-${hex.slice(4, 6).join("")}-${hex.slice(6, 8).join("")}-${hex.slice(8, 10).join("")}-${hex.slice(10, 16).join("")}`;
|
return `${hex.slice(0, 4).join("")}-${hex.slice(4, 6).join("")}-${hex.slice(6, 8).join("")}-${hex.slice(8, 10).join("")}-${hex.slice(10, 16).join("")}`;
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||||
import { uuidv7 } from "../../src/harness/session/uuid.ts";
|
import { uuidv7 } from "../src/utils/uuid.ts";
|
||||||
|
|
||||||
const UUID_V7_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
const UUID_V7_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
||||||
const TIMESTAMP = 0x0123456789ab;
|
const TIMESTAMP = 0x0123456789ab;
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { type AgentMessage, uuidv7 } from "@earendil-works/pi-agent-core";
|
import type { AgentMessage } from "@earendil-works/pi-agent-core";
|
||||||
import type { ImageContent, Message, TextContent } from "@earendil-works/pi-ai";
|
import { type ImageContent, type Message, type TextContent, uuidv7 } from "@earendil-works/pi-ai";
|
||||||
import { randomUUID } from "crypto";
|
import { randomUUID } from "crypto";
|
||||||
import {
|
import {
|
||||||
appendFileSync,
|
appendFileSync,
|
||||||
|
|||||||
Reference in New Issue
Block a user