mirror of
https://github.com/earendil-works/pi.git
synced 2026-09-28 05:54:43 +08:00
fix(ai): upgrade openai SDK to 7.19.0 (#10044)
* fix(ai): upgrade openai SDK to 7.19.0
Adds the "fast" service tier to the SDK types, needed to price
GPT-6 Fast mode requests correctly. Drops the local
prompt_cache_options type, which the SDK now defines.
* fix(coding-agent): check Cloudflare compat request via fetch instead of SDK mock
pi-ai now resolves its own nested openai 7.x while evals keeps 6.x at the
root, so vi.mock("openai") in coding-agent no longer reached pi-ai's client.
Capture the outgoing request with a fake fetch and assert the URL and headers.
* fix(ai): drop any casts for stream_options and max_tokens in openai-completions
stream_options is typed by the SDK, so assign it directly. max_tokens is
deprecated by OpenAI but still needed for OpenAI-compatible providers
that reject max_completion_tokens; use a narrow cast instead of any to
avoid the deprecation warning.
This commit is contained in:
Generated
+39
-1
@@ -4276,6 +4276,7 @@
|
||||
"version": "6.40.0",
|
||||
"resolved": "https://registry.npmjs.org/openai/-/openai-6.40.0.tgz",
|
||||
"integrity": "sha512-MWtTjd/gQt4jpbji61NTgFWJLoY/PdRJ6wG9/ZDRMYNMlBKrCrSlkLI+KgHP1vR1qT6LKSAyAqIxno6lcK9JiA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"peerDependencies": {
|
||||
"ws": "^8.18.0",
|
||||
@@ -5542,7 +5543,7 @@
|
||||
"@smithy/node-http-handler": "4.12.1",
|
||||
"http-proxy-agent": "9.1.0",
|
||||
"https-proxy-agent": "9.1.0",
|
||||
"openai": "6.40.0",
|
||||
"openai": "7.19.0",
|
||||
"partial-json": "0.1.7",
|
||||
"typebox": "1.3.27"
|
||||
},
|
||||
@@ -5581,6 +5582,43 @@
|
||||
"node": ">= 20"
|
||||
}
|
||||
},
|
||||
"packages/ai/node_modules/openai": {
|
||||
"version": "7.19.0",
|
||||
"resolved": "https://registry.npmjs.org/openai/-/openai-7.19.0.tgz",
|
||||
"integrity": "sha512-MX2s3u2L5racTO0CC/SWpCOasJQBCJrqLKXK+l82cAhdeF8mPMBEe/gxMm0ZFa2xpKpOFLRjxv5afYEZbBXmbQ==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=22.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@aws-sdk/credential-provider-node": ">=3.972.0 <4",
|
||||
"@smithy/hash-node": ">=4.3.0 <5",
|
||||
"@smithy/signature-v4": ">=5.4.0 <6",
|
||||
"undici": ">=5 <9",
|
||||
"ws": "^8.21.0",
|
||||
"zod": "^3.25 || ^4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@aws-sdk/credential-provider-node": {
|
||||
"optional": true
|
||||
},
|
||||
"@smithy/hash-node": {
|
||||
"optional": true
|
||||
},
|
||||
"@smithy/signature-v4": {
|
||||
"optional": true
|
||||
},
|
||||
"undici": {
|
||||
"optional": true
|
||||
},
|
||||
"ws": {
|
||||
"optional": true
|
||||
},
|
||||
"zod": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"packages/chord": {
|
||||
"name": "@earendil-works/chord",
|
||||
"version": "0.87.1",
|
||||
|
||||
@@ -70,7 +70,7 @@
|
||||
"@smithy/node-http-handler": "4.12.1",
|
||||
"http-proxy-agent": "9.1.0",
|
||||
"https-proxy-agent": "9.1.0",
|
||||
"openai": "6.40.0",
|
||||
"openai": "7.19.0",
|
||||
"partial-json": "0.1.7",
|
||||
"typebox": "1.3.27"
|
||||
},
|
||||
|
||||
@@ -827,7 +827,7 @@ function buildParams(
|
||||
};
|
||||
|
||||
if (compat.supportsUsageInStreaming !== false) {
|
||||
(params as any).stream_options = { include_usage: true };
|
||||
params.stream_options = { include_usage: true };
|
||||
}
|
||||
|
||||
if (compat.supportsStore) {
|
||||
@@ -836,7 +836,8 @@ function buildParams(
|
||||
|
||||
if (options?.maxTokens) {
|
||||
if (compat.maxTokensField === "max_tokens") {
|
||||
(params as any).max_tokens = options.maxTokens;
|
||||
// Deprecated by OpenAI, but some OpenAI-compatible providers only accept max_tokens.
|
||||
(params as { max_tokens?: number }).max_tokens = options.maxTokens;
|
||||
} else {
|
||||
params.max_completion_tokens = options.maxTokens;
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ function getPromptCacheRetention(
|
||||
function getPromptCacheOptions(
|
||||
compat: Required<OpenAIResponsesCompat>,
|
||||
cacheRetention: CacheRetention,
|
||||
): { mode?: "explicit"; ttl?: "30m" } | undefined {
|
||||
): ResponseCreateParamsStreaming["prompt_cache_options"] {
|
||||
if (!compat.supportsExplicitPromptCacheMode) return undefined;
|
||||
if (cacheRetention === "none") return { mode: "explicit" };
|
||||
if (cacheRetention === "long" && compat.supportsLongCacheRetention) return { ttl: "30m" };
|
||||
@@ -307,9 +307,7 @@ function buildParams(
|
||||
});
|
||||
|
||||
const cacheRetention = resolveCacheRetention(options?.cacheRetention, options?.env);
|
||||
const params: ResponseCreateParamsStreaming & {
|
||||
prompt_cache_options?: { mode?: "explicit"; ttl?: "30m" };
|
||||
} = {
|
||||
const params: ResponseCreateParamsStreaming = {
|
||||
model: model.id,
|
||||
input: messages,
|
||||
stream: true,
|
||||
|
||||
+38
-19
@@ -425,7 +425,7 @@
|
||||
"@smithy/node-http-handler": "4.12.1",
|
||||
"http-proxy-agent": "9.1.0",
|
||||
"https-proxy-agent": "9.1.0",
|
||||
"openai": "6.40.0",
|
||||
"openai": "7.19.0",
|
||||
"partial-json": "0.1.7",
|
||||
"typebox": "1.3.27"
|
||||
},
|
||||
@@ -459,6 +459,43 @@
|
||||
"node": ">= 20"
|
||||
}
|
||||
},
|
||||
"node_modules/@earendil-works/pi-ai/node_modules/openai": {
|
||||
"version": "7.19.0",
|
||||
"resolved": "https://registry.npmjs.org/openai/-/openai-7.19.0.tgz",
|
||||
"integrity": "sha512-MX2s3u2L5racTO0CC/SWpCOasJQBCJrqLKXK+l82cAhdeF8mPMBEe/gxMm0ZFa2xpKpOFLRjxv5afYEZbBXmbQ==",
|
||||
"license": "Apache-2.0",
|
||||
"peerDependencies": {
|
||||
"@aws-sdk/credential-provider-node": ">=3.972.0 <4",
|
||||
"@smithy/hash-node": ">=4.3.0 <5",
|
||||
"@smithy/signature-v4": ">=5.4.0 <6",
|
||||
"undici": ">=5 <9",
|
||||
"ws": "^8.21.0",
|
||||
"zod": "^3.25 || ^4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@aws-sdk/credential-provider-node": {
|
||||
"optional": true
|
||||
},
|
||||
"@smithy/hash-node": {
|
||||
"optional": true
|
||||
},
|
||||
"@smithy/signature-v4": {
|
||||
"optional": true
|
||||
},
|
||||
"undici": {
|
||||
"optional": true
|
||||
},
|
||||
"ws": {
|
||||
"optional": true
|
||||
},
|
||||
"zod": {
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=22.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@earendil-works/pi-coding-agent": {
|
||||
"version": "0.87.1",
|
||||
"resolved": "https://registry.npmjs.org/@earendil-works/pi-coding-agent/-/pi-coding-agent-0.87.1.tgz",
|
||||
@@ -1630,24 +1667,6 @@
|
||||
"url": "https://opencollective.com/node-fetch"
|
||||
}
|
||||
},
|
||||
"node_modules/openai": {
|
||||
"version": "6.40.0",
|
||||
"resolved": "https://registry.npmjs.org/openai/-/openai-6.40.0.tgz",
|
||||
"integrity": "sha512-MWtTjd/gQt4jpbji61NTgFWJLoY/PdRJ6wG9/ZDRMYNMlBKrCrSlkLI+KgHP1vR1qT6LKSAyAqIxno6lcK9JiA==",
|
||||
"license": "Apache-2.0",
|
||||
"peerDependencies": {
|
||||
"ws": "^8.18.0",
|
||||
"zod": "^3.25 || ^4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"ws": {
|
||||
"optional": true
|
||||
},
|
||||
"zod": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/p-retry": {
|
||||
"version": "4.6.2",
|
||||
"resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz",
|
||||
|
||||
+38
-19
@@ -447,7 +447,7 @@
|
||||
"@smithy/node-http-handler": "4.12.1",
|
||||
"http-proxy-agent": "9.1.0",
|
||||
"https-proxy-agent": "9.1.0",
|
||||
"openai": "6.40.0",
|
||||
"openai": "7.19.0",
|
||||
"partial-json": "0.1.7",
|
||||
"typebox": "1.3.27"
|
||||
},
|
||||
@@ -481,6 +481,43 @@
|
||||
"node": ">= 20"
|
||||
}
|
||||
},
|
||||
"node_modules/@earendil-works/pi-ai/node_modules/openai": {
|
||||
"version": "7.19.0",
|
||||
"resolved": "https://registry.npmjs.org/openai/-/openai-7.19.0.tgz",
|
||||
"integrity": "sha512-MX2s3u2L5racTO0CC/SWpCOasJQBCJrqLKXK+l82cAhdeF8mPMBEe/gxMm0ZFa2xpKpOFLRjxv5afYEZbBXmbQ==",
|
||||
"license": "Apache-2.0",
|
||||
"peerDependencies": {
|
||||
"@aws-sdk/credential-provider-node": ">=3.972.0 <4",
|
||||
"@smithy/hash-node": ">=4.3.0 <5",
|
||||
"@smithy/signature-v4": ">=5.4.0 <6",
|
||||
"undici": ">=5 <9",
|
||||
"ws": "^8.21.0",
|
||||
"zod": "^3.25 || ^4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@aws-sdk/credential-provider-node": {
|
||||
"optional": true
|
||||
},
|
||||
"@smithy/hash-node": {
|
||||
"optional": true
|
||||
},
|
||||
"@smithy/signature-v4": {
|
||||
"optional": true
|
||||
},
|
||||
"undici": {
|
||||
"optional": true
|
||||
},
|
||||
"ws": {
|
||||
"optional": true
|
||||
},
|
||||
"zod": {
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=22.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@earendil-works/pi-telemetry": {
|
||||
"version": "0.87.1",
|
||||
"resolved": "https://registry.npmjs.org/@earendil-works/pi-telemetry/-/pi-telemetry-0.87.1.tgz",
|
||||
@@ -1620,24 +1657,6 @@
|
||||
"url": "https://opencollective.com/node-fetch"
|
||||
}
|
||||
},
|
||||
"node_modules/openai": {
|
||||
"version": "6.40.0",
|
||||
"resolved": "https://registry.npmjs.org/openai/-/openai-6.40.0.tgz",
|
||||
"integrity": "sha512-MWtTjd/gQt4jpbji61NTgFWJLoY/PdRJ6wG9/ZDRMYNMlBKrCrSlkLI+KgHP1vR1qT6LKSAyAqIxno6lcK9JiA==",
|
||||
"license": "Apache-2.0",
|
||||
"peerDependencies": {
|
||||
"ws": "^8.18.0",
|
||||
"zod": "^3.25 || ^4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"ws": {
|
||||
"optional": true
|
||||
},
|
||||
"zod": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/p-retry": {
|
||||
"version": "4.6.2",
|
||||
"resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz",
|
||||
|
||||
@@ -1,46 +1,34 @@
|
||||
import { complete, resetApiProviders } from "@earendil-works/pi-ai/compat";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { AuthStorage } from "../src/core/auth-storage.ts";
|
||||
import { ModelRegistry } from "../src/core/model-registry.ts";
|
||||
import { ModelRuntime } from "../src/core/model-runtime.ts";
|
||||
|
||||
const openAIState = vi.hoisted(() => ({ clientOptions: undefined as unknown }));
|
||||
interface CapturedRequest {
|
||||
url: string;
|
||||
headers: Headers;
|
||||
}
|
||||
|
||||
vi.mock("openai", () => {
|
||||
class FakeOpenAI {
|
||||
constructor(options: unknown) {
|
||||
openAIState.clientOptions = options;
|
||||
}
|
||||
|
||||
chat = {
|
||||
completions: {
|
||||
create: () => {
|
||||
const stream = {
|
||||
async *[Symbol.asyncIterator]() {
|
||||
yield {
|
||||
choices: [{ delta: {}, finish_reason: "stop" }],
|
||||
usage: { prompt_tokens: 1, completion_tokens: 1 },
|
||||
};
|
||||
},
|
||||
};
|
||||
const promise = Promise.resolve(stream) as Promise<typeof stream> & {
|
||||
withResponse(): Promise<{
|
||||
data: typeof stream;
|
||||
response: { status: number; headers: Headers };
|
||||
}>;
|
||||
};
|
||||
promise.withResponse = async () => ({
|
||||
data: stream,
|
||||
response: { status: 200, headers: new Headers() },
|
||||
});
|
||||
return promise;
|
||||
},
|
||||
},
|
||||
function createCapturingFetch(): { fetch: typeof globalThis.fetch; requests: CapturedRequest[] } {
|
||||
const requests: CapturedRequest[] = [];
|
||||
const fetch: typeof globalThis.fetch = async (input, init) => {
|
||||
const request = new Request(input, init);
|
||||
requests.push({ url: request.url, headers: request.headers });
|
||||
const chunk = {
|
||||
id: "chatcmpl-test",
|
||||
object: "chat.completion.chunk",
|
||||
created: 0,
|
||||
model: "test",
|
||||
choices: [{ index: 0, delta: {}, finish_reason: "stop" }],
|
||||
usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 },
|
||||
};
|
||||
}
|
||||
|
||||
return { default: FakeOpenAI };
|
||||
});
|
||||
return new Response(`data: ${JSON.stringify(chunk)}\n\ndata: [DONE]\n\n`, {
|
||||
status: 200,
|
||||
headers: { "content-type": "text/event-stream" },
|
||||
});
|
||||
};
|
||||
return { fetch, requests };
|
||||
}
|
||||
|
||||
async function createCloudflareRuntime(): Promise<{ modelRuntime: ModelRuntime; modelRegistry: ModelRegistry }> {
|
||||
const authStorage = AuthStorage.inMemory();
|
||||
@@ -56,6 +44,8 @@ async function createCloudflareRuntime(): Promise<{ modelRuntime: ModelRuntime;
|
||||
return { modelRuntime, modelRegistry: new ModelRegistry(modelRuntime) };
|
||||
}
|
||||
|
||||
const CLOUDFLARE_COMPAT_URL = "https://gateway.ai.cloudflare.com/v1/test-account/test-gateway/compat/chat/completions";
|
||||
|
||||
describe("ModelRegistry Cloudflare compat streaming", () => {
|
||||
it("materializes the Cloudflare endpoint through ModelRuntime streaming", async () => {
|
||||
const { modelRuntime } = await createCloudflareRuntime();
|
||||
@@ -63,14 +53,13 @@ describe("ModelRegistry Cloudflare compat streaming", () => {
|
||||
expect(model).toBeDefined();
|
||||
|
||||
resetApiProviders();
|
||||
await modelRuntime.completeSimple(model!, { messages: [] });
|
||||
const { fetch, requests } = createCapturingFetch();
|
||||
const result = await modelRuntime.completeSimple(model!, { messages: [] }, { fetch });
|
||||
|
||||
const clientOptions = openAIState.clientOptions as {
|
||||
baseURL?: string;
|
||||
defaultHeaders?: Record<string, unknown>;
|
||||
};
|
||||
expect(clientOptions.baseURL).toBe("https://gateway.ai.cloudflare.com/v1/test-account/test-gateway/compat");
|
||||
expect(clientOptions.defaultHeaders?.["cf-aig-authorization"]).toBe("Bearer test-token");
|
||||
expect(result.stopReason).toBe("stop");
|
||||
expect(requests).toHaveLength(1);
|
||||
expect(requests[0].url).toBe(CLOUDFLARE_COMPAT_URL);
|
||||
expect(requests[0].headers.get("cf-aig-authorization")).toBe("Bearer test-token");
|
||||
});
|
||||
|
||||
it("materializes the Cloudflare endpoint after extension-style auth resolution", async () => {
|
||||
@@ -88,15 +77,14 @@ describe("ModelRegistry Cloudflare compat streaming", () => {
|
||||
"x-api-key": null,
|
||||
});
|
||||
|
||||
await complete(model!, { messages: [] }, auth);
|
||||
const { fetch, requests } = createCapturingFetch();
|
||||
const result = await complete(model!, { messages: [] }, { ...auth, fetch });
|
||||
|
||||
const clientOptions = openAIState.clientOptions as {
|
||||
baseURL?: string;
|
||||
defaultHeaders?: Record<string, unknown>;
|
||||
};
|
||||
expect(clientOptions.baseURL).toBe("https://gateway.ai.cloudflare.com/v1/test-account/test-gateway/compat");
|
||||
expect(clientOptions.defaultHeaders?.["cf-aig-authorization"]).toBe("Bearer test-token");
|
||||
expect(clientOptions.defaultHeaders?.Authorization).toBeNull();
|
||||
expect(clientOptions.defaultHeaders?.["x-api-key"]).toBeNull();
|
||||
expect(result.stopReason).toBe("stop");
|
||||
expect(requests).toHaveLength(1);
|
||||
expect(requests[0].url).toBe(CLOUDFLARE_COMPAT_URL);
|
||||
expect(requests[0].headers.get("cf-aig-authorization")).toBe("Bearer test-token");
|
||||
expect(requests[0].headers.has("authorization")).toBe(false);
|
||||
expect(requests[0].headers.has("x-api-key")).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user