feat(ai): add strictResponsesPairing for Azure OpenAI Responses API

Split OpenAICompat into OpenAICompletionsCompat and OpenAIResponsesCompat
for type-safe API-specific compat settings. Added strictResponsesPairing
option to suppress orphaned reasoning/tool calls on incomplete turns,
fixing 400 errors on Azure's Responses API which requires strict pairing.

Closes #768
This commit is contained in:
Mario Zechner 2026-01-18 20:15:26 +01:00
parent def9e4e9a9
commit d43930c818
17 changed files with 112 additions and 23 deletions

View file

@ -2,6 +2,10 @@
## [Unreleased]
### Added
- Added `strictResponsesPairing` compat option for custom OpenAI Responses models on Azure ([#768](https://github.com/badlogic/pi-mono/pull/768) by [@nicobako](https://github.com/nicobako))
### Changed
- Share URLs now use hash fragments (`#`) instead of query strings (`?`) to prevent session IDs from being sent to buildwithpi.ai ([#828](https://github.com/badlogic/pi-mono/issues/828))

View file

@ -735,6 +735,8 @@ To fully replace a built-in provider with custom models, include the `models` ar
**OpenAI compatibility (`compat` field):**
**OpenAI Completions (`openai-completions`):**
| Field | Description |
|-------|-------------|
| `supportsStore` | Whether provider supports `store` field |
@ -743,6 +745,14 @@ To fully replace a built-in provider with custom models, include the `models` ar
| `supportsUsageInStreaming` | Whether provider supports `stream_options: { include_usage: true }`. Default: `true` |
| `maxTokensField` | Use `max_completion_tokens` or `max_tokens` |
**OpenAI Responses (`openai-responses`):**
| Field | Description |
|-------|-------------|
| `strictResponsesPairing` | Enforce strict reasoning/message pairing when replaying OpenAI Responses history on providers like Azure (default: `false`) |
If you see 400 errors like "item of type 'reasoning' was provided without its required following item" or "message/function_call was provided without its required reasoning item", set `compat.strictResponsesPairing: true` on the affected model in `models.json`.
**Live reload:** The file reloads each time you open `/model`. Edit during session; no restart needed.
**Model selection priority:**

View file

@ -20,13 +20,19 @@ import type { AuthStorage } from "./auth-storage.js";
const Ajv = (AjvModule as any).default || AjvModule;
// Schema for OpenAI compatibility settings
const OpenAICompatSchema = Type.Object({
const OpenAICompletionsCompatSchema = Type.Object({
supportsStore: Type.Optional(Type.Boolean()),
supportsDeveloperRole: Type.Optional(Type.Boolean()),
supportsReasoningEffort: Type.Optional(Type.Boolean()),
maxTokensField: Type.Optional(Type.Union([Type.Literal("max_completion_tokens"), Type.Literal("max_tokens")])),
});
const OpenAIResponsesCompatSchema = Type.Object({
strictResponsesPairing: Type.Optional(Type.Boolean()),
});
const OpenAICompatSchema = Type.Union([OpenAICompletionsCompatSchema, OpenAIResponsesCompatSchema]);
// Schema for custom model definition
const ModelDefinitionSchema = Type.Object({
id: Type.String({ minLength: 1 }),