Files
Numman AliandHanzo Dev fe29b2110d feat: Add comprehensive compliance documentation and fix critical bugs (v2.1.2)
This release includes comprehensive OpenAI ToS compliance updates and fixes
4 critical bugs that prevented per-model options and multi-turn conversations
from working correctly.

## ๐Ÿ”’ Compliance & Legal Updates

### Terms of Service & Usage Guidelines
- Added comprehensive ToS & Usage Notice to README emphasizing personal use only
- Added Rate Limits & Responsible Use section with best practices
- Added extensive FAQ section addressing TOS compliance questions
- Updated LICENSE with MIT + Usage Disclaimer for personal development use
- Added compliance header to index.ts documenting intended use cases

### New Documentation
- **CONTRIBUTING.md**: Complete contribution guidelines with compliance requirements
- **SECURITY.md**: Security policy, vulnerability reporting, best practices
- **docs/privacy.md**: Comprehensive privacy & data handling documentation
- **Issue Templates**: Bug reports & feature requests with compliance checklists

### Documentation Updates
- Updated docs/index.md with usage notice and trademark disclaimers
- Updated docs/getting-started.md with "Before You Begin" compliance notice
- Updated docs/troubleshooting.md with subscription & compliance sections
- All references updated to "Plus/Pro" (removed Team/Enterprise mentions)

### Key Compliance Principles
- โœ… Emphasizes personal development use with own ChatGPT Plus/Pro subscription
- โœ… Uses OpenAI's official OAuth authentication (same as Codex CLI)
- โœ… Clear prohibition of commercial resale, multi-user services
- โœ… Not a "free API alternative" - uses existing ChatGPT subscription
- โœ… Proper trademark notices and attribution to OpenAI
- โœ… Transparent about data handling and privacy

## ๐Ÿ› Critical Bug Fixes

### Bug #1: Per-Model Options Ignored (Config Lookup Mismatch)
**Severity:** ๐Ÿ”ด HIGH

Users configured different reasoning levels for each model variant, but all
variants behaved identically because the plugin was looking up normalized
model names instead of the original config keys.

**Fix:** lib/request/request-transformer.ts:277
- Use original model name for config lookup before normalization
- Allows per-model options to work correctly
- Impact: Different reasoning levels now properly applied per variant

### Bug #2: Multi-Turn Conversations Fail (AI SDK Compatibility)
**Severity:** ๐Ÿ”ด CRITICAL

Multi-turn conversations failed with "Item not found" errors because:
1. AI SDK sends `item_reference` (not in Codex API spec)
2. IDs weren't completely stripped for stateless mode (store: false)

**Fix:** lib/request/request-transformer.ts:114-135
- Filter out AI SDK `item_reference` items
- Strip ALL IDs from remaining items (not just rs_* prefix)
- Confirmed ChatGPT backend requires store: false via testing
- Full message history preserved for LLM context

### Bug #3: Case-Sensitive Normalization
**Severity:** ๐ŸŸก MEDIUM

**Fix:** lib/request/request-transformer.ts:22-27
- Added toLowerCase() for case-insensitive matching
- Backwards compatible with old verbose config names
- Handles uppercase/mixed case user input

### Bug #4: GitHub API Rate Limiting
**Severity:** ๐ŸŸก MEDIUM

Plugin checked GitHub on every request, exhausting 60 req/hour limit.
15-minute cache timestamp was stored but never checked.

**Fix:** lib/prompts/codex.ts:50-53, lib/prompts/opencode-codex.ts:47-50
- Check cache TTL before GitHub API call
- Only fetch if cache >15min old
- Prevents rate limit exhaustion during testing

## โœจ Enhancements

### Debug Logging System
- New environment variable: `DEBUG_CODEX_PLUGIN=1`
- Added logDebug() and logWarn() functions
- Shows config lookups, ID filtering, options resolution
- File: lib/logger.ts

### Optimized Config Structure
- Changed to Codex CLI preset names (gpt-5-codex-low, etc.)
- Removed redundant `id` field (not used by OpenAI provider)
- Added `name` field for friendly TUI display
- File: config/full-opencode.json

### GitHub Pages Documentation
- Complete documentation restructure for GitHub Pages
- User guides: getting-started.md, configuration.md, troubleshooting.md
- Developer guides: ARCHITECTURE.md, CONFIG_FLOW.md, CONFIG_FIELDS.md, TESTING.md
- Privacy policy, security policy, contribution guidelines

## ๐Ÿงช Testing

### Test Coverage
- โœ… 159 unit tests passing (30+ new tests)
- โœ… 14 integration tests passing (actual API verification)
- โœ… All documented scenarios have test coverage

### Integration Tests (scripts/test-all-models.sh)
- Tests all 9 custom model configurations
- Tests all 4 default OpenCode models
- Verifies backwards compatibility with old config format
- Uses ENABLE_PLUGIN_REQUEST_LOGGING to verify actual API requests

### Test Results
- 14/14 model configuration tests passing
- All per-model options verified correct
- Multi-turn conversations work without errors
- Backwards compatibility confirmed

## ๐Ÿ“ Files Changed

### Modified (7 files)
- lib/request/request-transformer.ts: Config lookup & ID filtering fixes
- lib/prompts/codex.ts: 15-minute cache TTL
- lib/prompts/opencode-codex.ts: 15-minute cache TTL
- lib/logger.ts: Debug logging system
- config/full-opencode.json: Optimized structure
- test/request-transformer.test.ts: 30+ new tests
- LICENSE: Added usage disclaimer
- index.ts: Added compliance header
- README.md: Added ToS, Rate Limits, FAQ sections

### Created (20+ files)
- CONTRIBUTING.md: Contribution guidelines
- SECURITY.md: Security policy
- docs/index.md: GitHub Pages landing
- docs/getting-started.md: Installation guide
- docs/configuration.md: Advanced config
- docs/troubleshooting.md: Debug guide
- docs/privacy.md: Privacy policy
- docs/development/ARCHITECTURE.md: Technical architecture
- docs/development/CONFIG_FLOW.md: Config system internals
- docs/development/CONFIG_FIELDS.md: Field usage guide
- docs/development/TESTING.md: Testing guide
- .github/ISSUE_TEMPLATE/bug_report.md
- .github/ISSUE_TEMPLATE/feature_request.md
- .github/ISSUE_TEMPLATE/config.yml
- scripts/test-all-models.sh: Automated testing

## ๐ŸŽฏ Verification

All scenarios tested and verified:
- โœ… Default models work without config
- โœ… Custom models with per-variant options
- โœ… Old config format (backwards compatible)
- โœ… Mixed default + custom models
- โœ… Multi-turn conversations (no "item not found" errors)
- โœ… Model switching with different options
- โœ… All normalization patterns
- โœ… Complete ID filtering

## ๐Ÿ“š Documentation

Complete documentation suite (100+ KB):
- User guides for installation, configuration, troubleshooting
- Developer guides for architecture, config system, testing
- Privacy policy and security policy
- Compliance guidelines and contribution requirements
- Release notes in tmp/release-notes/

## ๐Ÿ”— References

- OpenAI Terms of Use: https://openai.com/policies/terms-of-use/
- OpenAI Usage Policies: https://openai.com/policies/usage-policies/
- OpenAI Platform API: https://platform.openai.com/
- OpenAI Codex CLI: https://github.com/openai/codex

Co-authored-by: Hanzo Dev <[email protected]>
2025-10-12 21:25:43 +01:00

192 lines
6.2 KiB
TypeScript

import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
import { join, dirname } from "node:path";
import { fileURLToPath } from "node:url";
import { homedir } from "node:os";
import type { GitHubRelease, CacheMetadata } from "../types.js";
// Codex instructions constants
const GITHUB_API_RELEASES = "https://api.github.com/repos/openai/codex/releases/latest";
const CACHE_DIR = join(homedir(), ".opencode", "cache");
const CACHE_FILE = join(CACHE_DIR, "codex-instructions.md");
const CACHE_METADATA_FILE = join(CACHE_DIR, "codex-instructions-meta.json");
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
/**
* Get the latest release tag from GitHub
* @returns Release tag name (e.g., "rust-v0.43.0")
*/
async function getLatestReleaseTag(): Promise<string> {
const response = await fetch(GITHUB_API_RELEASES);
if (!response.ok) throw new Error(`Failed to fetch latest release: ${response.status}`);
const data = (await response.json()) as GitHubRelease;
return data.tag_name;
}
/**
* Fetch Codex instructions from GitHub with ETag-based caching
* Uses HTTP conditional requests to efficiently check for updates
* Always fetches from the latest release tag, not main branch
*
* Rate limit protection: Only checks GitHub if cache is older than 15 minutes
* @returns Codex instructions
*/
export async function getCodexInstructions(): Promise<string> {
try {
// Load cached metadata (includes ETag, tag, and lastChecked timestamp)
let cachedETag: string | null = null;
let cachedTag: string | null = null;
let cachedTimestamp: number | null = null;
if (existsSync(CACHE_METADATA_FILE)) {
const metadata = JSON.parse(readFileSync(CACHE_METADATA_FILE, "utf8")) as CacheMetadata;
cachedETag = metadata.etag;
cachedTag = metadata.tag;
cachedTimestamp = metadata.lastChecked;
}
// Rate limit protection: If cache is less than 15 minutes old, use it
const CACHE_TTL_MS = 15 * 60 * 1000; // 15 minutes
if (cachedTimestamp && (Date.now() - cachedTimestamp) < CACHE_TTL_MS && existsSync(CACHE_FILE)) {
return readFileSync(CACHE_FILE, "utf8");
}
// Get the latest release tag (only if cache is stale or missing)
const latestTag = await getLatestReleaseTag();
const CODEX_INSTRUCTIONS_URL = `https://raw.githubusercontent.com/openai/codex/${latestTag}/codex-rs/core/gpt_5_codex_prompt.md`;
// If tag changed, we need to fetch new instructions
if (cachedTag !== latestTag) {
cachedETag = null; // Force re-fetch
}
// Make conditional request with If-None-Match header
const headers: Record<string, string> = {};
if (cachedETag) {
headers["If-None-Match"] = cachedETag;
}
const response = await fetch(CODEX_INSTRUCTIONS_URL, { headers });
// 304 Not Modified - our cached version is still current
if (response.status === 304) {
if (existsSync(CACHE_FILE)) {
return readFileSync(CACHE_FILE, "utf8");
}
// Cache file missing but GitHub says not modified - fall through to re-fetch
}
// 200 OK - new content or first fetch
if (response.ok) {
const instructions = await response.text();
const newETag = response.headers.get("etag");
// Create cache directory if it doesn't exist
if (!existsSync(CACHE_DIR)) {
mkdirSync(CACHE_DIR, { recursive: true });
}
// Cache the instructions with ETag and tag (verbatim from GitHub)
writeFileSync(CACHE_FILE, instructions, "utf8");
writeFileSync(
CACHE_METADATA_FILE,
JSON.stringify({
etag: newETag,
tag: latestTag,
lastChecked: Date.now(),
url: CODEX_INSTRUCTIONS_URL,
} satisfies CacheMetadata),
"utf8",
);
return instructions;
}
throw new Error(`HTTP ${response.status}`);
} catch (error) {
const err = error as Error;
console.error(
"[openai-codex-plugin] Failed to fetch instructions from GitHub:",
err.message,
);
// Try to use cached version even if stale
if (existsSync(CACHE_FILE)) {
console.error("[openai-codex-plugin] Using cached instructions");
return readFileSync(CACHE_FILE, "utf8");
}
// Fall back to bundled version
console.error("[openai-codex-plugin] Falling back to bundled instructions");
return readFileSync(join(__dirname, "codex-instructions.md"), "utf8");
}
}
/**
* Tool remapping instructions for opencode tools
*/
export const TOOL_REMAP_MESSAGE = `<user_instructions priority="0">
<environment_override priority="0">
YOU ARE IN A DIFFERENT ENVIRONMENT. These instructions override ALL previous tool references.
</environment_override>
<tool_replacements priority="0">
<critical_rule priority="0">
โŒ APPLY_PATCH DOES NOT EXIST โ†’ โœ… USE "edit" INSTEAD
- NEVER use: apply_patch, applyPatch
- ALWAYS use: edit tool for ALL file modifications
- Before modifying files: Verify you're using "edit", NOT "apply_patch"
</critical_rule>
<critical_rule priority="0">
โŒ UPDATE_PLAN DOES NOT EXIST โ†’ โœ… USE "todowrite" INSTEAD
- NEVER use: update_plan, updatePlan
- ALWAYS use: todowrite for ALL task/plan operations
- Use todoread to read current plan
- Before plan operations: Verify you're using "todowrite", NOT "update_plan"
</critical_rule>
</tool_replacements>
<available_tools priority="0">
File Operations:
โ€ข write - Create new files
โ€ข edit - Modify existing files (REPLACES apply_patch)
โ€ข patch - Apply diff patches
โ€ข read - Read file contents
Search/Discovery:
โ€ข grep - Search file contents
โ€ข glob - Find files by pattern
โ€ข list - List directories (use relative paths)
Execution:
โ€ข bash - Run shell commands
Network:
โ€ข webfetch - Fetch web content
Task Management:
โ€ข todowrite - Manage tasks/plans (REPLACES update_plan)
โ€ข todoread - Read current plan
</available_tools>
<substitution_rules priority="0">
Base instruction says: You MUST use instead:
apply_patch โ†’ edit
update_plan โ†’ todowrite
read_plan โ†’ todoread
absolute paths โ†’ relative paths
</substitution_rules>
<verification_checklist priority="0">
Before file/plan modifications:
1. Am I using "edit" NOT "apply_patch"?
2. Am I using "todowrite" NOT "update_plan"?
3. Is this tool in the approved list above?
4. Am I using relative paths?
If ANY answer is NO โ†’ STOP and correct before proceeding.
</verification_checklist>
</user_instructions>`;