refactor: standardized outbound User-Agent headers on shared utility constant
- Define a centralized `USER_AGENT` constant in `@oh-my-pi/pi-utils` formatted as `omp/<version>`. - Replace hardcoded and platform-specific user agent strings across AI providers, catalog scrapers, tools, and search providers with the unified `USER_AGENT`. - Add unit tests for update-cli binary release distribution gating.
This commit is contained in:
@@ -2,6 +2,10 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Changed
|
||||
|
||||
- Standardized first-party outbound User-Agent headers on `omp/<version>` via the shared `USER_AGENT` utility.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed the Amazon Bedrock and Cursor transports ignoring `StreamOptions.headers`; both built their request headers from scratch, so caller-supplied tracing or attribution headers were silently dropped while working on every other provider ([#8107](https://github.com/can1357/oh-my-pi/pull/8107) by [@svperfecta](https://github.com/svperfecta)).
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import * as os from "node:os";
|
||||
import { scheduler } from "node:timers/promises";
|
||||
import { type } from "@oh-my-pi/omptype";
|
||||
import { calculateCost } from "@oh-my-pi/pi-catalog/models";
|
||||
@@ -19,8 +18,8 @@ import {
|
||||
parseStreamingJson,
|
||||
readSseJson,
|
||||
structuredCloneJSON,
|
||||
USER_AGENT,
|
||||
} from "@oh-my-pi/pi-utils";
|
||||
import packageJson from "../../package.json" with { type: "json" };
|
||||
import * as AIError from "../error";
|
||||
import { getEnvApiKey, isOfficialCodexApiUrl } from "../stream";
|
||||
import type {
|
||||
@@ -4250,7 +4249,7 @@ function createCodexHeaders(
|
||||
headers.set(OPENAI_HEADERS.BETA, betaHeader);
|
||||
headers.set(OPENAI_HEADERS.ORIGINATOR, OPENAI_HEADER_VALUES.ORIGINATOR_CODEX);
|
||||
headers.set(OPENAI_HEADERS.VERSION, codexClientVersion);
|
||||
headers.set("User-Agent", `pi/${packageJson.version} (${os.platform()} ${os.release()}; ${os.arch()})`);
|
||||
headers.set("User-Agent", USER_AGENT);
|
||||
if (sessionId) {
|
||||
headers.set(OPENAI_HEADERS.CONVERSATION_ID, sessionId);
|
||||
headers.set(OPENAI_HEADERS.SESSION_ID, sessionId);
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
* share one wire contract.
|
||||
*/
|
||||
import { toNumber } from "@oh-my-pi/pi-catalog/utils";
|
||||
import { USER_AGENT } from "@oh-my-pi/pi-utils";
|
||||
import type { FetchImpl } from "../types";
|
||||
import { isRecord } from "../utils";
|
||||
import { normalizeCodexBaseUrl } from "./openai-codex-base-url";
|
||||
@@ -89,7 +90,7 @@ function buildUrl(baseUrl: string | undefined, routePath: string): string {
|
||||
function buildHeaders(auth: CodexResetAuth, json: boolean): Record<string, string> {
|
||||
const headers: Record<string, string> = {
|
||||
Authorization: `Bearer ${auth.accessToken}`,
|
||||
"User-Agent": "OpenCode-Status-Plugin/1.0",
|
||||
"User-Agent": USER_AGENT,
|
||||
};
|
||||
if (auth.accountId) headers["ChatGPT-Account-Id"] = auth.accountId;
|
||||
if (json) headers["Content-Type"] = "application/json";
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Buffer } from "node:buffer";
|
||||
import { toNumber } from "@oh-my-pi/pi-catalog/utils";
|
||||
import { USER_AGENT } from "@oh-my-pi/pi-utils";
|
||||
import type {
|
||||
CredentialRankingContext,
|
||||
CredentialRankingStrategy,
|
||||
@@ -417,7 +418,7 @@ export const openaiCodexUsageProvider: UsageProvider = {
|
||||
|
||||
const headers: Record<string, string> = {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
"User-Agent": "OpenCode-Status-Plugin/1.0",
|
||||
"User-Agent": USER_AGENT,
|
||||
};
|
||||
if (accountId) {
|
||||
headers["ChatGPT-Account-Id"] = accountId;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { toNumber } from "@oh-my-pi/pi-catalog/utils";
|
||||
import { USER_AGENT } from "@oh-my-pi/pi-utils";
|
||||
import type {
|
||||
CredentialRankingStrategy,
|
||||
UsageAmount,
|
||||
@@ -228,7 +229,7 @@ async function fetchZaiUsage(params: UsageFetchParams, ctx: UsageFetchContext):
|
||||
const headers: Record<string, string> = {
|
||||
Authorization: token,
|
||||
"Content-Type": "application/json",
|
||||
"User-Agent": "OpenCode-Status-Plugin/1.0",
|
||||
"User-Agent": USER_AGENT,
|
||||
};
|
||||
|
||||
let payload: ZaiQuotaPayload | null = null;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import packageJson from "../../package.json" with { type: "json" };
|
||||
import { USER_AGENT } from "@oh-my-pi/pi-utils";
|
||||
|
||||
export function getOpenRouterHeaders(): Record<string, string> {
|
||||
return {
|
||||
"User-Agent": `omp/${packageJson.version}`,
|
||||
"User-Agent": USER_AGENT,
|
||||
"HTTP-Referer": "https://omp.sh/",
|
||||
"X-OpenRouter-Title": "omp",
|
||||
"X-OpenRouter-Categories": "cli-agent",
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Changed
|
||||
|
||||
- Standardized catalog discovery User-Agent headers on `omp/<version>` via the shared `USER_AGENT` utility.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Marked `meta/muse-spark-1.2` and `muse-spark-1.2-contributor` as image-capable (`input: ["text", "image"]`) with the same Responses reasoning, thinking, and cost metadata as `muse-spark-1.1` (contributor uses its discounted 0.1/0.2 pricing), so `omp models` no longer lists them as text-only.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { VERSION } from "@oh-my-pi/pi-utils";
|
||||
import { USER_AGENT } from "@oh-my-pi/pi-utils";
|
||||
import * as logger from "@oh-my-pi/pi-utils/logger";
|
||||
import {
|
||||
fetchOpenAICompatibleModels,
|
||||
@@ -111,7 +111,7 @@ const catalogSession: {
|
||||
hasPayload: boolean;
|
||||
} = { inflight: null, payload: undefined, etag: null, hasPayload: false };
|
||||
|
||||
const CATALOG_USER_AGENT = `omp/${VERSION} (+https://omp.sh)`;
|
||||
const CATALOG_USER_AGENT = USER_AGENT;
|
||||
|
||||
/**
|
||||
* Fetches the models.dev catalog via catalog.stencil.so, which serves a
|
||||
|
||||
@@ -22,6 +22,10 @@
|
||||
- Added structured reset-reason logging to advisor context re-primes (issue #7226): every history-rewrite trigger (compact, auto-compaction, compaction-rescue, shake, drop-images, prune-tool-outputs, prune-stale-tool-results, conversation-boundary, context-maintenance) now emits an `advisor context reset` debug event with its reason, so full-transcript replays can be attributed to a concrete path.
|
||||
- Added `quarantine-recovery` and `quarantine-retry-exhausted` reset reasons to advisor context-reset debug logs, so advisor full re-primes after quarantined output remain attributable without changing quarantine retry semantics (issue #7226).
|
||||
|
||||
### Changed
|
||||
|
||||
- Standardized first-party outbound User-Agent headers on `omp/<version>` via the shared `USER_AGENT` utility.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed `/usage`, `/advisor status`, and every other panel command answering only after the agent stopped working. Since `17.0.1` their output was queued until the turn settled (to stop mid-turn transcript mounts duplicating rows in native scrollback, issues #4806/#6767), and the deferral was silent, so on a long turn the command was indistinguishable from a dead one. The panel now renders immediately above the editor in an anchored container that is cleared and rebuilt in place, never entering the transcript, and the full output still lands in the transcript at the next settle. The preview is capped to 40% of the viewport (minimum 6 rows) so a tall report cannot push the prompt off screen.
|
||||
|
||||
@@ -8,10 +8,10 @@
|
||||
* tests to spy on.
|
||||
*/
|
||||
|
||||
import { USER_AGENT } from "@oh-my-pi/pi-utils";
|
||||
import { isTimeoutError, withTimeoutSignal } from "../utils/fetch-timeout";
|
||||
import type { HindsightConfig } from "./config";
|
||||
|
||||
const USER_AGENT = "oh-my-pi-coding-agent";
|
||||
const DEFAULT_USER_AGENT = USER_AGENT;
|
||||
/** Fallback deadlines (ms) applied when the caller supplies no override. */
|
||||
const DEFAULT_REQUEST_TIMEOUT_MS = 30_000;
|
||||
|
||||
@@ -12,10 +12,6 @@ interface XAICredentials {
|
||||
baseURL: string;
|
||||
}
|
||||
|
||||
export function ohMyPiXAIUserAgent(): string {
|
||||
return "oh-my-pi/xai";
|
||||
}
|
||||
|
||||
/** xAI provider ids supported by shared HTTP tool transport resolution. */
|
||||
export type XAIHttpProvider = "xai-oauth" | "xai";
|
||||
|
||||
|
||||
@@ -19,13 +19,13 @@ import {
|
||||
ptree,
|
||||
readSseJson,
|
||||
Snowflake,
|
||||
USER_AGENT,
|
||||
untilAborted,
|
||||
} from "@oh-my-pi/pi-utils";
|
||||
import packageJson from "../../package.json" with { type: "json" };
|
||||
import { isAuthenticated, type ModelRegistry } from "../config/model-registry";
|
||||
import { settings } from "../config/settings";
|
||||
import type { CustomTool } from "../extensibility/custom-tools/types";
|
||||
import { ohMyPiXAIUserAgent, resolveXAIHttpCredentials } from "../lib/xai-http";
|
||||
import { resolveXAIHttpCredentials } from "../lib/xai-http";
|
||||
import imageGenDescription from "../prompts/tools/image-gen.md" with { type: "text" };
|
||||
import { AUTO_IMAGE_PROVIDER_ORDER, type ImageProvider, isImageProviderId } from "./image-providers";
|
||||
import { resolveReadPath } from "./path-utils";
|
||||
@@ -897,7 +897,7 @@ function buildOpenAIImageHeaders(model: Model, apiKey: string, sessionId: string
|
||||
}
|
||||
headers.set(OPENAI_HEADERS.BETA, OPENAI_HEADER_VALUES.BETA_RESPONSES);
|
||||
headers.set(OPENAI_HEADERS.ORIGINATOR, OPENAI_HEADER_VALUES.ORIGINATOR_CODEX);
|
||||
headers.set("User-Agent", `pi/${packageJson.version} (${os.platform()} ${os.release()}; ${os.arch()})`);
|
||||
headers.set("User-Agent", USER_AGENT);
|
||||
if (sessionId) {
|
||||
headers.set(OPENAI_HEADERS.CONVERSATION_ID, sessionId);
|
||||
headers.set(OPENAI_HEADERS.SESSION_ID, sessionId);
|
||||
@@ -1389,7 +1389,7 @@ export const imageGenTool: CustomTool<typeof imageGenSchema, ImageGenToolDetails
|
||||
headers: {
|
||||
Authorization: `Bearer ${key}`,
|
||||
"Content-Type": "application/json",
|
||||
"User-Agent": ohMyPiXAIUserAgent(),
|
||||
"User-Agent": USER_AGENT,
|
||||
},
|
||||
body: JSON.stringify(xaiBody),
|
||||
signal: requestSignal,
|
||||
|
||||
@@ -7,9 +7,10 @@ import { type } from "@oh-my-pi/omptype";
|
||||
import type { AgentToolResult } from "@oh-my-pi/pi-agent-core";
|
||||
import { type ApiKey, withAuth } from "@oh-my-pi/pi-ai";
|
||||
import { ProviderHttpError } from "@oh-my-pi/pi-ai/error";
|
||||
import { USER_AGENT } from "@oh-my-pi/pi-utils";
|
||||
import { settings } from "../config/settings";
|
||||
import type { CustomTool, CustomToolContext } from "../extensibility/custom-tools/types";
|
||||
import { ohMyPiXAIUserAgent, resolveXAIHttpCredentials } from "../lib/xai-http";
|
||||
import { resolveXAIHttpCredentials } from "../lib/xai-http";
|
||||
import { DEFAULT_TTS_LOCAL_MODEL_KEY, DEFAULT_TTS_VOICE, isTtsLocalModelKey, KOKORO_VOICES } from "../tts/models";
|
||||
import { ttsClient } from "../tts/tts-client";
|
||||
import { encodeWav } from "../tts/wav";
|
||||
@@ -150,7 +151,7 @@ async function synthesizeXai(
|
||||
headers: {
|
||||
Authorization: `Bearer ${key}`,
|
||||
"Content-Type": "application/json",
|
||||
"User-Agent": ohMyPiXAIUserAgent(),
|
||||
"User-Agent": USER_AGENT,
|
||||
},
|
||||
body: JSON.stringify(payload),
|
||||
signal: combinedSignal,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as fs from "node:fs";
|
||||
import * as os from "node:os";
|
||||
import * as path from "node:path";
|
||||
import { $which, APP_NAME, getToolsDir, logger, ptree, TempDir } from "@oh-my-pi/pi-utils";
|
||||
import { $which, getToolsDir, logger, ptree, TempDir, USER_AGENT } from "@oh-my-pi/pi-utils";
|
||||
import { extractArchive } from "./zip";
|
||||
|
||||
const TOOLS_DIR = getToolsDir();
|
||||
@@ -174,7 +174,7 @@ async function getLatestVersion(repo: string, signal?: AbortSignal): Promise<str
|
||||
let response: Response;
|
||||
try {
|
||||
response = await fetch(`https://api.github.com/repos/${repo}/releases/latest`, {
|
||||
headers: { "User-Agent": `${APP_NAME}-coding-agent` },
|
||||
headers: { "User-Agent": USER_AGENT },
|
||||
signal: ptree.combineSignals(signal, TOOL_METADATA_TIMEOUT_MS),
|
||||
});
|
||||
} catch (err) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { tryParseJson } from "@oh-my-pi/pi-utils";
|
||||
import { tryParseJson, USER_AGENT } from "@oh-my-pi/pi-utils";
|
||||
import type { RenderResult, SpecialHandler } from "./types";
|
||||
import { buildResult, formatNumber, loadPage, looksLikeHtml } from "./types";
|
||||
|
||||
@@ -26,7 +26,7 @@ export const handleCratesIo: SpecialHandler = async (
|
||||
const result = await loadPage(apiUrl, {
|
||||
timeout,
|
||||
signal,
|
||||
headers: { "User-Agent": "omp-web-fetch/1.0 (https://github.com/anthropics)" },
|
||||
headers: { "User-Agent": USER_AGENT },
|
||||
});
|
||||
|
||||
if (!result.ok) return null;
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* API docs: https://www.discogs.com/developers
|
||||
*/
|
||||
|
||||
import { tryParseJson } from "@oh-my-pi/pi-utils";
|
||||
import { tryParseJson, USER_AGENT } from "@oh-my-pi/pi-utils";
|
||||
import type { RenderResult, SpecialHandler } from "./types";
|
||||
import { buildResult, loadPage } from "./types";
|
||||
|
||||
@@ -277,7 +277,7 @@ export const handleDiscogs: SpecialHandler = async (
|
||||
signal,
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"User-Agent": "CodingAgent/1.0 +https://github.com/can1357/oh-my-pi",
|
||||
"User-Agent": USER_AGENT,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as fs from "node:fs/promises";
|
||||
import * as path from "node:path";
|
||||
import { gunzipSync } from "node:zlib";
|
||||
import { getDocsRsCacheDir, isEnoent, logger, ptree, tryParseJson } from "@oh-my-pi/pi-utils";
|
||||
import { getDocsRsCacheDir, isEnoent, logger, ptree, tryParseJson, USER_AGENT } from "@oh-my-pi/pi-utils";
|
||||
import { ToolAbortError } from "../../tools/tool-errors";
|
||||
import type { RenderResult, SpecialHandler } from "./types";
|
||||
import { buildResult, MAX_BYTES } from "./types";
|
||||
@@ -388,7 +388,7 @@ export const handleDocsRs: SpecialHandler = async (
|
||||
const requestSignal = ptree.combineSignals(signal, timeout * 1000);
|
||||
const response = await fetch(jsonUrl, {
|
||||
signal: requestSignal,
|
||||
headers: { "User-Agent": "omp-web-fetch/1.0", Accept: "application/gzip" },
|
||||
headers: { "User-Agent": USER_AGENT, Accept: "application/gzip" },
|
||||
redirect: "follow",
|
||||
});
|
||||
if (!response.ok) return null;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { $env, ptree } from "@oh-my-pi/pi-utils";
|
||||
import { $env, ptree, USER_AGENT } from "@oh-my-pi/pi-utils";
|
||||
import type { RenderResult, SpecialHandler } from "./types";
|
||||
import { buildResult, formatMediaDuration, loadPage } from "./types";
|
||||
|
||||
@@ -121,7 +121,7 @@ export async function fetchGitHubApi(
|
||||
|
||||
const headers: Record<string, string> = {
|
||||
Accept: "application/vnd.github.v3+json",
|
||||
"User-Agent": "omp-web-fetch/1.0",
|
||||
"User-Agent": USER_AGENT,
|
||||
};
|
||||
|
||||
// Use GITHUB_TOKEN if available
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* MusicBrainz URL handler for artists, releases, and recordings
|
||||
*/
|
||||
|
||||
import { tryParseJson } from "@oh-my-pi/pi-utils";
|
||||
import { tryParseJson, USER_AGENT } from "@oh-my-pi/pi-utils";
|
||||
import type { RenderResult, SpecialHandler } from "./types";
|
||||
import { buildResult, formatMediaDuration, loadPage } from "./types";
|
||||
|
||||
@@ -64,7 +64,6 @@ interface MusicBrainzRelease {
|
||||
}
|
||||
|
||||
const MUSICBRAINZ_HOSTS = new Set(["musicbrainz.org", "www.musicbrainz.org"]);
|
||||
const USER_AGENT = "omp-web-fetch/1.0 (https://github.com/anthropics)";
|
||||
const MAX_TRACKS = 50;
|
||||
|
||||
function parseEntity(url: URL): { entity: MusicBrainzEntity; mbid: string } | null {
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
/**
|
||||
* PubMed handler for web-fetch
|
||||
*/
|
||||
import { tryParseJson } from "@oh-my-pi/pi-utils";
|
||||
import { tryParseJson, USER_AGENT } from "@oh-my-pi/pi-utils";
|
||||
import { buildResult, loadPage, type RenderResult, type SpecialHandler } from "./types";
|
||||
|
||||
const NCBI_HEADERS = {
|
||||
Accept: "application/json, text/plain;q=0.9, */*;q=0.8",
|
||||
"User-Agent": "CodingAgent/1.0 (web scraper)",
|
||||
"User-Agent": USER_AGENT,
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { tryParseJson } from "@oh-my-pi/pi-utils";
|
||||
import { tryParseJson, USER_AGENT } from "@oh-my-pi/pi-utils";
|
||||
import type { RenderResult, SpecialHandler } from "./types";
|
||||
import { buildResult, loadPage } from "./types";
|
||||
|
||||
@@ -178,7 +178,7 @@ export const handleSecEdgar: SpecialHandler = async (
|
||||
timeout,
|
||||
signal,
|
||||
headers: {
|
||||
"User-Agent": "CodingAgent/1.0 (research tool)",
|
||||
"User-Agent": USER_AGENT,
|
||||
Accept: "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
* Uses the configured Codex Responses transport for proxy/API-key setups and
|
||||
* the official ChatGPT backend for OAuth logins.
|
||||
*/
|
||||
import * as os from "node:os";
|
||||
import {
|
||||
type AuthStorage,
|
||||
type FetchImpl,
|
||||
@@ -22,8 +21,7 @@ import {
|
||||
OPENAI_HEADER_VALUES,
|
||||
OPENAI_HEADERS,
|
||||
} from "@oh-my-pi/pi-catalog/wire/codex";
|
||||
import { $env, readSseJson } from "@oh-my-pi/pi-utils";
|
||||
import packageJson from "../../../../package.json" with { type: "json" };
|
||||
import { $env, readSseJson, USER_AGENT } from "@oh-my-pi/pi-utils";
|
||||
import type { ModelRegistry } from "../../../config/model-registry";
|
||||
import type { SearchResponse, SearchSource } from "../../../web/search/types";
|
||||
import { SearchProviderError } from "../../../web/search/types";
|
||||
@@ -441,7 +439,7 @@ function buildCodexHeaders(
|
||||
headers.set(OPENAI_HEADERS.BETA, OPENAI_HEADER_VALUES.BETA_RESPONSES);
|
||||
headers.set(OPENAI_HEADERS.ORIGINATOR, OPENAI_HEADER_VALUES.ORIGINATOR_CODEX);
|
||||
headers.set(OPENAI_HEADERS.VERSION, CODEX_CLIENT_VERSION);
|
||||
headers.set("User-Agent", `pi/${packageJson.version} (${os.platform()} ${os.release()}; ${os.arch()})`);
|
||||
headers.set("User-Agent", USER_AGENT);
|
||||
headers.set("Accept", "text/event-stream");
|
||||
headers.set("Content-Type", "application/json");
|
||||
return headers;
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
getAntigravityUserAgent,
|
||||
getGeminiCliHeaders,
|
||||
} from "@oh-my-pi/pi-catalog/wire/gemini-headers";
|
||||
import { fetchWithRetry } from "@oh-my-pi/pi-utils";
|
||||
import { fetchWithRetry, USER_AGENT } from "@oh-my-pi/pi-utils";
|
||||
|
||||
import type { SearchCitation, SearchResponse, SearchSource } from "../../../web/search/types";
|
||||
import { SearchProviderError } from "../../../web/search/types";
|
||||
@@ -436,8 +436,8 @@ async function callGeminiSearch(
|
||||
requestId: `agent-${crypto.randomUUID()}`,
|
||||
}
|
||||
: {
|
||||
userAgent: "pi-coding-agent",
|
||||
requestId: `pi-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`,
|
||||
userAgent: USER_AGENT,
|
||||
requestId: `omp-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`,
|
||||
};
|
||||
|
||||
const normalizedSystemPrompt = systemPrompt?.toWellFormed();
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
imageGenTool,
|
||||
setImageProviderOrder,
|
||||
} from "@oh-my-pi/pi-coding-agent/tools/image-gen";
|
||||
import { removeWithRetries } from "@oh-my-pi/pi-utils";
|
||||
import { removeWithRetries, USER_AGENT } from "@oh-my-pi/pi-utils";
|
||||
|
||||
const originalOpenRouterKey = Bun.env.OPENROUTER_API_KEY;
|
||||
const generatedImagePaths: string[] = [];
|
||||
@@ -618,7 +618,7 @@ describe("imageGenTool", () => {
|
||||
|
||||
expect(requestUrl).toBe("https://api.x.ai/v1/images/generations");
|
||||
expect(captured.authorization).toBe("Bearer test-xai-token");
|
||||
expect(captured.userAgent).toBe("oh-my-pi/xai");
|
||||
expect(captured.userAgent).toBe(USER_AGENT);
|
||||
expect(requestBody).toMatchObject({
|
||||
model: "grok-imagine-image",
|
||||
prompt: "a cat.",
|
||||
|
||||
@@ -19,7 +19,9 @@ import {
|
||||
replaceBinaryForUpdate,
|
||||
resolveBunGlobalNodeModulesDirFromLocations,
|
||||
resolveReleaseBinaryAsset,
|
||||
resolveReleaseDist,
|
||||
resolveUpdateMethodForTest,
|
||||
shouldForceBinaryUpdate,
|
||||
sweepStaleBackups,
|
||||
updateViaBinaryAt,
|
||||
} from "@oh-my-pi/pi-coding-agent/cli/update-cli";
|
||||
@@ -740,3 +742,38 @@ describe("update-cli stale backup sweep", () => {
|
||||
expect(await Bun.file(`${targetPath}.config.bak`).exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("update-cli binary-only release gating", () => {
|
||||
it("honors an explicit omp.dist field from the registry manifest", () => {
|
||||
expect(resolveReleaseDist({ omp: { dist: "binary" } })).toBe("binary");
|
||||
expect(resolveReleaseDist({ omp: { dist: "npm" } })).toBe("npm");
|
||||
});
|
||||
|
||||
it("treats unknown dist values as binary-only", () => {
|
||||
expect(resolveReleaseDist({ omp: { dist: "cargo" } })).toBe("binary");
|
||||
});
|
||||
|
||||
it("returns undefined when the manifest carries no dist field", () => {
|
||||
expect(resolveReleaseDist({ version: "1.2.3" })).toBeUndefined();
|
||||
expect(resolveReleaseDist({ omp: {} })).toBeUndefined();
|
||||
expect(resolveReleaseDist(undefined)).toBeUndefined();
|
||||
});
|
||||
|
||||
it("forces binary updates when dist is binary regardless of version", () => {
|
||||
expect(shouldForceBinaryUpdate({ version: "1.2.3", dist: "binary" }, "1.2.2")).toBe(true);
|
||||
});
|
||||
|
||||
it("allows package-manager updates across majors when dist is explicitly npm", () => {
|
||||
expect(shouldForceBinaryUpdate({ version: "2.0.0", dist: "npm" }, "1.9.0")).toBe(false);
|
||||
});
|
||||
|
||||
it("forces binary updates on a major bump without a dist field", () => {
|
||||
expect(shouldForceBinaryUpdate({ version: "2.0.0" }, "1.9.0")).toBe(true);
|
||||
expect(shouldForceBinaryUpdate({ version: "2.0.0-rc.1" }, "1.9.0")).toBe(true);
|
||||
});
|
||||
|
||||
it("keeps package-manager updates within the same major and on downgrades", () => {
|
||||
expect(shouldForceBinaryUpdate({ version: "1.10.0" }, "1.9.0")).toBe(false);
|
||||
expect(shouldForceBinaryUpdate({ version: "1.0.0" }, "2.0.0")).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
### Changed
|
||||
|
||||
- Changed stale process-log retention from the newest five files globally to one newest file per completed process and day within the current and previous four local calendar days. This preserves bounded daily diagnostic coverage while continuing to remove one-use audit files.
|
||||
- Changed outbound User-Agent consumers to share the versioned `USER_AGENT` constant (`omp/<version>`).
|
||||
|
||||
### Fixed
|
||||
|
||||
|
||||
@@ -28,6 +28,9 @@ export const MAIN_CONFIG_FILENAMES = ["config.yml", "config.yaml"] as const;
|
||||
/** Version (e.g. "1.0.0") */
|
||||
export const VERSION: string = version;
|
||||
|
||||
/** Default User-Agent header string (e.g. "omp/17.2.12") */
|
||||
export const USER_AGENT = `omp/${VERSION}`;
|
||||
|
||||
/** Minimum Bun version */
|
||||
export const MIN_BUN_VERSION: string = engines.bun.replace(/[^0-9.]/g, "");
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
*/
|
||||
|
||||
import * as path from "node:path";
|
||||
import { USER_AGENT } from "@oh-my-pi/pi-utils";
|
||||
|
||||
const PROVIDER_FILE = path.join(import.meta.dir, "../packages/catalog/src/wire/gemini-headers.ts");
|
||||
|
||||
@@ -36,7 +37,7 @@ async function fetchLatestGitHubRelease(
|
||||
try {
|
||||
// /releases/latest only returns non-prerelease, non-draft releases
|
||||
const res = await fetch(`https://api.github.com/repos/${repo}/releases/latest`, {
|
||||
headers: { Accept: "application/vnd.github+json", "User-Agent": "oh-my-pi/version-check" },
|
||||
headers: { Accept: "application/vnd.github+json", "User-Agent": USER_AGENT },
|
||||
});
|
||||
if (!res.ok) return null;
|
||||
const data = (await res.json()) as { tag_name?: string };
|
||||
|
||||
Reference in New Issue
Block a user