mirror of
https://github.com/dtzp555-max/olp.git
synced 2026-07-19 09:45:07 +00:00
fix(ir): accept OpenAI role=developer at entry surface, normalize to system (#65)
* fix(ir): accept OpenAI role=developer at entry surface, normalize to system Hermes Agent (v0.13+) and other modern openai-completions clients (Cline, Continue.dev) default to role=developer for the high-priority instruction slot when the model id matches OpenAI o1/o3+ reasoning family. OLP IR's validator rejected this with 400 IR validation failed: role must be one of system|user|assistant|tool, got "developer". Reproduced 2026-05-27 on PI230 Hermes v0.14.0 trying olp-codex/gpt-5.5 through PI231 OLP. olp-claude (Anthropic models) was unaffected because Hermes sends system role for Claude models (no developer-role concept on Anthropic side). Fix: extend openai-to-ir.mjs normalizeRole to map developer to system at the entry boundary. The IR canonical-four-roles invariant is preserved; every provider plugin's role-handling stays unchanged. Same pattern as the existing function-to-tool normalization that already lives in normalizeRole (function role was OpenAI-deprecated). Normalize-at- entry centralizes role-spec-evolution handling in one file rather than bloating the IR schema and forcing every provider plugin to handle each new role. Why not add developer to VALID_ROLES: it would require branches in three provider plugins (anthropic, codex, mistral) all mapping to system-style annotation anyway, plus wider IR surface for any future OpenAI role addition. Tests: two new pin tests in Suite IR translation: - developer role to system translation - mixed-role array including developer validates cleanly 768 to 770 tests, 0 fail. Verified Hermes via olp-codex no longer hits the IR rejection error after this fix (smoke run from PI230). ADR 0003 Amendment 3 documents the rationale + future-role policy (normalize-at-entry unless a role genuinely conveys provider-distinguishable semantics). Authority: OpenAI Responses API spec developer-role + Hermes Agent v0.14.0 reproduction. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * test+docs: PR #65 fold-in reviewer N2 + N3 N2 — Negative control test added: unknown role (e.g. "admin") still raises BadRequestError. Pins that the developer-to-system normalization is the only entry-surface escape hatch; any future widening of normalizeRole that returns role as-is for unknown inputs will be caught by this test. N3 — ADR 0003 Amendment 3 gains a "Cache-key impact" bullet documenting that a developer-form and system-form request with otherwise-identical content now share the same IR and thus the same cache key (per ADR 0005). This is by design and matches OpenAI's own backward-compat semantics; the note exists so a future debug session investigating "why does my developer request hit a cache entry from an old system request" finds the answer. Test count: 770 to 771, all pass. Skipped reviewer N1 (URL precision) — Amendment already cites multiple authorities including the Hermes/Cline tracking convention and live reproduction transcript; single-URL precision is over-spec. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: dtzp555 <dtzp555@gmail.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -319,6 +319,45 @@ describe('openAIToIR translation', () => {
|
||||
assert.equal(ir.messages[0].name, 'my_fn');
|
||||
});
|
||||
|
||||
it('maps role=developer to role=system (OpenAI o1/o3+ reasoning shape)', () => {
|
||||
const ir = openAIToIR({
|
||||
model: 'm',
|
||||
messages: [{ role: 'developer', content: 'High-priority instructions.' }],
|
||||
});
|
||||
assert.equal(ir.messages[0].role, 'system', 'developer should normalize to system');
|
||||
assert.equal(ir.messages[0].content, 'High-priority instructions.');
|
||||
});
|
||||
|
||||
it('mixed roles including developer all validate cleanly through IR', () => {
|
||||
const ir = openAIToIR({
|
||||
model: 'm',
|
||||
messages: [
|
||||
{ role: 'developer', content: 'Be terse.' },
|
||||
{ role: 'user', content: 'Hi.' },
|
||||
{ role: 'assistant', content: 'Hello.' },
|
||||
],
|
||||
});
|
||||
assert.equal(ir.messages.length, 3);
|
||||
assert.equal(ir.messages[0].role, 'system');
|
||||
assert.equal(ir.messages[1].role, 'user');
|
||||
assert.equal(ir.messages[2].role, 'assistant');
|
||||
});
|
||||
|
||||
it('rejects an unknown role at entry — normalize didn\'t accidentally widen allow-list', () => {
|
||||
// Negative control for Amendment 3 — without this pin, a future addition
|
||||
// to normalizeRole that returns `role` as-is for unknown inputs would silently
|
||||
// widen the IR role allow-list. Confirms developer→system mapping is the only
|
||||
// entry-surface escape hatch.
|
||||
assert.throws(
|
||||
() => openAIToIR({
|
||||
model: 'm',
|
||||
messages: [{ role: 'admin', content: 'I am god.' }],
|
||||
}),
|
||||
err => err instanceof BadRequestError && /role must be one of/.test(err.message),
|
||||
'unknown role should still produce BadRequestError'
|
||||
);
|
||||
});
|
||||
|
||||
it('translates request with tools', () => {
|
||||
const ir = openAIToIR({
|
||||
model: 'm',
|
||||
|
||||
Reference in New Issue
Block a user