Skip to content
Open
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
8 changes: 8 additions & 0 deletions skills/.system/openai-docs/references/upgrading-to-gpt-5p4.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

Use this guide when the user explicitly asks to upgrade an existing integration to GPT-5.4. Pair it with current OpenAI docs lookups. The default target string is `gpt-5.4`.

## Freshness check

Before applying this bundled guide, run `node scripts/resolve-latest-model-info.js` from the OpenAI Docs skill directory.

- If the command returns `modelSlug: "gpt-5p4"`, continue with this bundled guide.
- If the command returns a different `modelSlug`, fetch and use the returned `migrationGuideUrl` and `promptingGuideUrl` as the current source of truth instead of this bundled guide.
- If the command fails, the metadata is missing, or either remote guide cannot be fetched, continue with this bundled guide and say the remote freshness check was unavailable.

## Upgrade posture

Upgrade with the narrowest safe change set:
Expand Down
147 changes: 147 additions & 0 deletions skills/.system/openai-docs/scripts/resolve-latest-model-info.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
#!/usr/bin/env node

const fs = require("node:fs/promises");
const path = require("node:path");

const DEFAULT_URL =
"https://developers.openai.com/api/docs/guides/latest-model.md";
const DEFAULT_BASE_URL = "https://developers.openai.com";

function parseArgs(argv) {
const args = {
source: process.env.LATEST_MODEL_URL || DEFAULT_URL,
baseUrl: process.env.LATEST_MODEL_BASE_URL || DEFAULT_BASE_URL,
};

for (let i = 2; i < argv.length; i += 1) {
const arg = argv[i];
if (arg === "--source" || arg === "--url") {
args.source = argv[i + 1];
i += 1;
} else if (arg === "--base-url") {
args.baseUrl = argv[i + 1];
i += 1;
}
}

return args;
}

async function readSource(source) {
if (source.startsWith("file://")) {
return fs.readFile(new URL(source), "utf8");
}

if (!/^https?:\/\//.test(source)) {
return fs.readFile(path.resolve(source), "utf8");
}

const response = await fetch(source, {
headers: { accept: "text/markdown,text/plain,*/*" },
});

if (!response.ok) {
throw new Error(`failed to fetch ${source}: ${response.status}`);
}

return response.text();
}

function parseIndentedInfo(lines, startIndex) {
const info = {};

for (let i = startIndex + 1; i < lines.length; i += 1) {
const line = lines[i];
if (!line.trim()) {
continue;
}

const match = line.match(/^ {2}([A-Za-z][A-Za-z0-9_-]*):\s*(.+?)\s*$/);
if (!match) {
break;
}

info[match[1]] = match[2].replace(/^["']|["']$/g, "");
}

return info;
}

function parseFlatInfo(block) {
const info = {};

for (const line of block.split(/\r?\n/)) {
const match = line.match(/^([A-Za-z][A-Za-z0-9_-]*):\s*(.+?)\s*$/);
if (match) {
info[match[1]] = match[2].replace(/^["']|["']$/g, "");
}
}

return info;
}

function extractLatestModelInfo(markdown) {
const lines = markdown.split(/\r?\n/);
const latestModelInfoIndex = lines.findIndex((line) =>
/^latestModelInfo:\s*$/.test(line)
);

if (latestModelInfoIndex >= 0) {
return parseIndentedInfo(lines, latestModelInfoIndex);
}

const commentMatch = markdown.match(
/<!--\s*latestModelInfo\s*\n([\s\S]*?)\n\s*-->/m
);
if (commentMatch) {
return parseFlatInfo(commentMatch[1]);
}

return undefined;
}

function modelToSkillSlug(model) {
return model.trim().replace(/\./g, "p");
}

function absoluteUrl(baseUrl, value) {
return new URL(value, baseUrl).toString();
}

function normalizeInfo(info, baseUrl) {
const model = info?.model?.trim();
const migrationGuide = info?.migrationGuide?.trim();
const promptingGuide = info?.promptingGuide?.trim();

if (!model || !migrationGuide || !promptingGuide) {
throw new Error(
"latestModelInfo must include model, migrationGuide, and promptingGuide"
);
}

return {
model,
modelSlug: modelToSkillSlug(model),
migrationGuideUrl: absoluteUrl(baseUrl, migrationGuide),
promptingGuideUrl: absoluteUrl(baseUrl, promptingGuide),
};
}

async function main() {
const { source, baseUrl } = parseArgs(process.argv);
const markdown = await readSource(source);
const info = extractLatestModelInfo(markdown);

if (!info) {
throw new Error(`latestModelInfo block not found in ${source}`);
}

process.stdout.write(
`${JSON.stringify(normalizeInfo(info, baseUrl), null, 2)}\n`
);
}

main().catch((error) => {
console.error(error.message);
process.exit(1);
});