Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -459,7 +459,7 @@ DEBUG_CODEX_PLUGIN=1 opencode run "test" --model=openai/your-model-name

Look for:
```
[openai-codex-plugin] Model config lookup: "your-model-name" → normalized to "gpt-5-codex" for API {
[openhax/codex] Model config lookup: "your-model-name" → normalized to "gpt-5-codex" for API {
hasModelSpecificConfig: true,
resolvedConfig: { ... }
}
Expand Down
6 changes: 3 additions & 3 deletions docs/development/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,13 +193,13 @@ The plugin logs ID filtering for debugging:

```typescript
// Before filtering
console.log(`[openai-codex-plugin] Filtering ${originalIds.length} message IDs from input:`, originalIds);
console.log(`[openhax/codex] Filtering ${originalIds.length} message IDs from input:`, originalIds);

// After filtering
console.log(`[openai-codex-plugin] Successfully removed all ${originalIds.length} message IDs`);
console.log(`[openhax/codex] Successfully removed all ${originalIds.length} message IDs`);

// Or warning if IDs remain
console.warn(`[openai-codex-plugin] WARNING: ${remainingIds.length} IDs still present after filtering:`, remainingIds);
console.warn(`[openhax/codex] WARNING: ${remainingIds.length} IDs still present after filtering:`, remainingIds);
```

**Source**: `lib/request/request-transformer.ts:287-301`
Expand Down
18 changes: 9 additions & 9 deletions docs/development/TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -375,8 +375,8 @@ DEBUG_CODEX_PLUGIN=1 opencode run "test" --model=openai/gpt-5-codex-low
#### Case 1: Custom Model with Config

```
[openai-codex-plugin] Debug logging ENABLED
[openai-codex-plugin] Model config lookup: "gpt-5-codex-low" → normalized to "gpt-5-codex" for API {
[openhax/codex] Debug logging ENABLED
[openhax/codex] Model config lookup: "gpt-5-codex-low" → normalized to "gpt-5-codex" for API {
hasModelSpecificConfig: true,
resolvedConfig: {
reasoningEffort: 'low',
Expand All @@ -385,7 +385,7 @@ DEBUG_CODEX_PLUGIN=1 opencode run "test" --model=openai/gpt-5-codex-low
include: ['reasoning.encrypted_content']
}
}
[openai-codex-plugin] Filtering 0 message IDs from input: []
[openhax/codex] Filtering 0 message IDs from input: []
```

✅ **Verify:** `hasModelSpecificConfig: true` confirms per-model options found
Expand All @@ -399,8 +399,8 @@ DEBUG_CODEX_PLUGIN=1 opencode run "test" --model=openai/gpt-5-codex
```

```
[openai-codex-plugin] Debug logging ENABLED
[openai-codex-plugin] Model config lookup: "gpt-5-codex" → normalized to "gpt-5-codex" for API {
[openhax/codex] Debug logging ENABLED
[openhax/codex] Model config lookup: "gpt-5-codex" → normalized to "gpt-5-codex" for API {
hasModelSpecificConfig: false,
resolvedConfig: {
reasoningEffort: 'medium',
Expand All @@ -409,7 +409,7 @@ DEBUG_CODEX_PLUGIN=1 opencode run "test" --model=openai/gpt-5-codex
include: ['reasoning.encrypted_content']
}
}
[openai-codex-plugin] Filtering 0 message IDs from input: []
[openhax/codex] Filtering 0 message IDs from input: []
```

✅ **Verify:** `hasModelSpecificConfig: false` confirms using global options
Expand All @@ -419,8 +419,8 @@ DEBUG_CODEX_PLUGIN=1 opencode run "test" --model=openai/gpt-5-codex
#### Case 3: Multi-Turn with ID Filtering

```
[openai-codex-plugin] Filtering 3 message IDs from input: ['msg_abc123', 'rs_xyz789', 'msg_def456']
[openai-codex-plugin] Successfully removed all 3 message IDs
[openhax/codex] Filtering 3 message IDs from input: ['msg_abc123', 'rs_xyz789', 'msg_def456']
[openhax/codex] Successfully removed all 3 message IDs
```

✅ **Verify:** All IDs removed, no warnings
Expand All @@ -430,7 +430,7 @@ DEBUG_CODEX_PLUGIN=1 opencode run "test" --model=openai/gpt-5-codex
#### Case 4: Warning if IDs Leak (Should Never Happen)

```
[openai-codex-plugin] WARNING: 1 IDs still present after filtering: ['msg_abc123']
[openhax/codex] WARNING: 1 IDs still present after filtering: ['msg_abc123']
```

❌ **This would indicate a bug** - should never appear
Expand Down
2 changes: 1 addition & 1 deletion lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*/

/** Plugin identifier for logging and error messages */
export const PLUGIN_NAME = "openai-codex-plugin";
export const PLUGIN_NAME = "openhax/codex";

/** Base URL for ChatGPT backend API */
export const CODEX_BASE_URL = "https://chatgpt.com/backend-api";
Expand Down
2 changes: 1 addition & 1 deletion lib/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ function logToConsole(
error?: unknown,
): void {
const shouldLog = CONSOLE_LOGGING_ENABLED || level === "warn" || level === "error";
if (IS_TEST_ENV && !shouldLog) {
if (!shouldLog) {
return;
}
const prefix = `[${PLUGIN_NAME}] ${message}`;
Expand Down
157 changes: 79 additions & 78 deletions lib/prompts/opencode-codex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,14 @@ import { logError, logWarn, logInfo } from "../logger.js";
import { CACHE_FILES, CACHE_TTL_MS, LEGACY_CACHE_FILES, PLUGIN_PREFIX } from "../utils/cache-config.js";
import { getOpenCodePath } from "../utils/file-system-utils.js";

const OPENCODE_CODEX_URL =
"https://raw.githubusercontent.com/sst/opencode/main/packages/opencode/src/session/prompt/codex.txt";
const OPENCODE_CODEX_URLS = [
"https://raw.githubusercontent.com/sst/opencode/dev/packages/opencode/src/session/prompt/codex.txt",
"https://raw.githubusercontent.com/sst/opencode/main/packages/opencode/src/session/prompt/codex.txt",
];

interface OpenCodeCacheMeta {
etag: string;
sourceUrl?: string;
lastFetch?: string; // Legacy field for backwards compatibility
lastChecked: number; // Timestamp for rate limit protection
url?: string; // Track source URL for validation
Expand Down Expand Up @@ -142,88 +145,86 @@ export async function getOpenCodeCodexPrompt(): Promise<string> {
return cachedContent;
}

// Fetch from GitHub with conditional request
const headers: Record<string, string> = {};
if (cachedMeta?.etag) {
headers["If-None-Match"] = cachedMeta.etag;
}

try {
const response = await fetch(OPENCODE_CODEX_URL, { headers });

// 304 Not Modified - cache is still valid
if (response.status === 304 && cachedContent) {
// Store in session cache
openCodePromptCache.set("main", { data: cachedContent, etag: cachedMeta?.etag || undefined });
return cachedContent;
// Fetch from GitHub with conditional requests and fallbacks
let lastError: Error | undefined;
for (const url of OPENCODE_CODEX_URLS) {
const headers: Record<string, string> = {};
if (cachedMeta?.etag && (!cachedMeta.sourceUrl || cachedMeta.sourceUrl === url)) {
headers["If-None-Match"] = cachedMeta.etag;
}

// 200 OK - new content available
if (response.ok) {
const content = await response.text();
const etag = response.headers.get("etag") || "";

// Save to cache with timestamp and plugin identifier
await writeFile(cacheFilePath, content, "utf-8");
await writeFile(
cacheMetaPath,
JSON.stringify(
{
etag,
lastFetch: new Date().toISOString(), // Keep for backwards compat
lastChecked: Date.now(),
url: OPENCODE_CODEX_URL, // Track source URL for validation
} satisfies OpenCodeCacheMeta,
null,
2,
),
"utf-8",
);

// Store in session cache
openCodePromptCache.set("main", { data: content, etag });

return content;
}

// Fallback to cache if available
if (cachedContent) {
logWarn("Using cached OpenCode prompt due to fetch failure", {
status: response.status,
cacheAge: cachedMeta ? Date.now() - cachedMeta.lastChecked : "unknown",
});
openCodePromptCache.set("main", { data: cachedContent, etag: cachedMeta?.etag || undefined });
return cachedContent;
}

throw new Error(`Failed to fetch OpenCode codex.txt: ${response.status}`);
} catch (error) {
const err = error as Error;
logError("Failed to fetch OpenCode codex.txt from GitHub", { error: err.message });

// Network error - fallback to cache
if (cachedContent) {
logWarn("Network error detected, using cached OpenCode prompt", {
error: err.message,
cacheAge: cachedMeta ? Date.now() - cachedMeta.lastChecked : "unknown",
});

// Store in session cache even for fallback
openCodePromptCache.set("main", { data: cachedContent, etag: cachedMeta?.etag || undefined });
return cachedContent;
try {
const response = await fetch(url, { headers });

// 304 Not Modified - cache is still valid
if (response.status === 304 && cachedContent) {
const updatedMeta: OpenCodeCacheMeta = {
etag: cachedMeta?.etag || "",
sourceUrl: cachedMeta?.sourceUrl || url,
lastFetch: cachedMeta?.lastFetch,
lastChecked: Date.now(),
url: cachedMeta?.url,
};
await writeFile(cacheMetaPath, JSON.stringify(updatedMeta, null, 2), "utf-8");

openCodePromptCache.set("main", { data: cachedContent, etag: updatedMeta.etag || undefined });
return cachedContent;
}

// 200 OK - new content available
if (response.ok) {
const content = await response.text();
const etag = response.headers.get("etag") || "";

await writeFile(cacheFilePath, content, "utf-8");
await writeFile(
cacheMetaPath,
JSON.stringify(
{
etag,
sourceUrl: url,
lastFetch: new Date().toISOString(), // Keep for backwards compat
lastChecked: Date.now(),
} satisfies OpenCodeCacheMeta,
null,
2,
),
"utf-8",
);

openCodePromptCache.set("main", { data: content, etag });

return content;
}

lastError = new Error(`HTTP ${response.status} from ${url}`);
} catch (error) {
const err = error as Error;
lastError = new Error(`Failed to fetch ${url}: ${err.message}`);
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

// Provide helpful error message for cache conflicts
if (err.message.includes("404") || err.message.includes("ENOENT")) {
throw new Error(
`Failed to fetch OpenCode prompt and no valid cache available. ` +
`This may happen when switching between different Codex plugins. ` +
`Try clearing the cache with: rm -rf ~/.opencode/cache/opencode* && rm -rf ~/.opencode/cache/codex*`,
);
}
if (lastError) {
logError("Failed to fetch OpenCode codex.txt from GitHub", { error: lastError.message });
}

throw new Error(`Failed to fetch OpenCode codex.txt and no cache available: ${err.message}`);
if (cachedContent) {
const updatedMeta: OpenCodeCacheMeta = {
etag: cachedMeta?.etag || "",
sourceUrl: cachedMeta?.sourceUrl,
lastFetch: cachedMeta?.lastFetch,
lastChecked: Date.now(),
url: cachedMeta?.url,
};
await writeFile(cacheMetaPath, JSON.stringify(updatedMeta, null, 2), "utf-8");

openCodePromptCache.set("main", { data: cachedContent, etag: updatedMeta.etag || undefined });
return cachedContent;
}

throw new Error(
`Failed to fetch OpenCode codex.txt and no cache available: ${lastError?.message || "unknown error"}`,
);
}

/**
Expand Down
3 changes: 2 additions & 1 deletion lib/request/response-handler.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { PLUGIN_NAME } from "../constants.js";
import { LOGGING_ENABLED, logError, logRequest } from "../logger.js";
import type { SSEEventData } from "../types.js";

Expand Down Expand Up @@ -35,7 +36,7 @@ function parseSseStream(sseText: string): unknown | null {
*/
export async function convertSseToJson(response: Response, headers: Headers): Promise<Response> {
if (!response.body) {
throw new Error("[openai-codex-plugin] Response has no body");
throw new Error(`${PLUGIN_NAME} Response has no body`);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
const reader = response.body.getReader();
const decoder = new TextDecoder();
Expand Down
31 changes: 31 additions & 0 deletions spec/handle-missing-codex-prompt-warming.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Handle missing codex prompt warming

## Scope
- Review uncommitted changes on branch `chore/handle-missing-codex-prompt-warming` for plugin log prefix and fallback prompt fetch handling.

## Relevant files & line notes
- `lib/constants.ts`:6-8 rename `PLUGIN_NAME` to `openhax/codex` for logging identity.
- `lib/logger.ts`:144-158 log gating simplified to always mirror warn/error/info when enabled; removes test-env suppression.
- `lib/prompts/opencode-codex.ts`:15-131 adds dev+main fallback URLs, stores `sourceUrl`, handles 304/etag per-source, logs last error, caches when available.
- `lib/request/response-handler.ts`:36-79 updates empty-body error prefix to new plugin name.
- Tests updated for new prefixes and caching behavior: `test/auth.test.ts`, `test/constants.test.ts`, `test/logger.test.ts`, `test/prompts-codex.test.ts`, `test/prompts-opencode-codex.test.ts` (new legacy URL fallback test).
- Docs updated to reflect new logging prefix: `docs/configuration.md`, `docs/development/ARCHITECTURE.md`, `docs/development/TESTING.md`.

## Existing issues / PRs
- No linked issues or PRs referenced in the changes.

## Requirements
- Ensure logging prefix consistently uses `openhax/codex` across code, tests, docs.
- OpenCode prompt fetcher should fall back to main branch when dev URL fails, preserving cache metadata including source URL.
- Maintain ETag-based caching and cache-hit/miss metrics with session/file caches.
- Tests should cover prefix changes and new fallback path.

## Definition of done
- All modified files aligned on new plugin identifier.
- OpenCode codex prompt fetch resilient when dev URL missing; cache metadata persists `sourceUrl` and uses correct conditional requests.
- Unit tests updated/passing; docs reflect logging prefix.
- Branch ready with meaningful commit(s) and PR targeted to staging.

## Notes
- Untracked spec files present (`spec/opencode-prompt-cache-404.md`, `spec/plugin-name-rename.md`); keep intact.
- Build/test commands: `npm test`, `npm run build`, `npm run typecheck` per AGENTS.md.
25 changes: 25 additions & 0 deletions spec/opencode-prompt-cache-404.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# OpenCode Prompt Cache 404

## Context
- Timestamped warnings during startup show `getOpenCodeCodexPrompt` failing to seed cache due to 404 on codex.txt (logs at 2025-11-19, default config path missing).
- Current fetch URL targets `sst/opencode` on the `main` branch, which no longer hosts `packages/opencode/src/session/prompt/codex.txt`.

## Existing Issues / PRs
- No related issues/PRs reviewed yet; check backlog if needed.

## Code Files & References
- lib/prompts/opencode-codex.ts:15 – `OPENCODE_CODEX_URL` points to raw GitHub main branch and returns 404.
- lib/cache/cache-warming.ts:41-99 – startup warming logs errors when `getOpenCodeCodexPrompt` fails.
- lib/utils/file-system-utils.ts:15-23 – cache path under `~/.opencode/cache` used for prompt storage.
- test/prompts-opencode-codex.test.ts:82-297 – coverage for caching, TTL, and fetch fallback behavior.

## Definition of Done
1. Update OpenCode prompt fetch logic to use a valid source and avoid 404s.
2. Preserve caching semantics (session + disk + TTL) and existing metrics behavior.
3. Ensure cache warming no longer logs repeated OpenCode fetch errors when network is available.
4. Tests cover the new fetch path/fallback path and continue to pass.

## Requirements
- Add a resilient fetch strategy (e.g., prefer current branch/file path with fallback to legacy path) without breaking existing interfaces.
- Keep cache directory/filenames unchanged to avoid disrupting existing users.
- Maintain log levels (warn on failures) but succeed when a fallback fetch works.
18 changes: 18 additions & 0 deletions spec/plugin-name-rename.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Plugin name rename to npm package name

## Context
- Update plugin/service identifier to use the npm package name `openhax/codex`.

## Relevant code
- lib/constants.ts:7 exports `PLUGIN_NAME` that is used for logging.
- test/constants.test.ts:18-21 asserts the current plugin identity string.

## Tasks / Plan
1. Change `PLUGIN_NAME` to `openhax/codex` in `lib/constants.ts`.
2. Update tests and any string expectations to the new identifier.
3. Keep docs/examples consistent if they explicitly show the service name.

## Definition of done
- Plugin logs use `openhax/codex` as the service name.
- Tests updated to match the new identifier and pass locally if run.
- No references to the legacy identifier remain in code/tests relevant to logging.
Loading
Loading