diff --git a/.changeset/server-compaction-model-settings.md b/.changeset/server-compaction-model-settings.md new file mode 100644 index 000000000..d26e08d14 --- /dev/null +++ b/.changeset/server-compaction-model-settings.md @@ -0,0 +1,6 @@ +--- +'@openai/agents-core': patch +'@openai/agents-openai': patch +--- + +feat: add model settings support for context management diff --git a/packages/agents-core/src/index.ts b/packages/agents-core/src/index.ts index d497fb02d..a1b79c8fb 100644 --- a/packages/agents-core/src/index.ts +++ b/packages/agents-core/src/index.ts @@ -147,6 +147,7 @@ export { ModelRequest, ModelResponse, ModelSettings, + ModelSettingsContextManagement, ModelSettingsToolChoice, RetryDecision, RetryPolicy, diff --git a/packages/agents-core/src/model.ts b/packages/agents-core/src/model.ts index 141271608..32e3ab8c2 100644 --- a/packages/agents-core/src/model.ts +++ b/packages/agents-core/src/model.ts @@ -67,6 +67,25 @@ export interface ModelSettingsText { verbosity?: 'low' | 'medium' | 'high' | null; } +export type ModelSettingsContextManagement = Array<{ + /** + * The context-management strategy to apply. + */ + type: 'compaction' | (string & {}); + + /** + * Rendered-token threshold that triggers server-side compaction. + */ + compactThreshold?: number; + + /** + * Rendered-token threshold that triggers server-side compaction. + */ + compact_threshold?: number; + + [key: string]: unknown; +}>; + export type RetryDecision = | boolean | { @@ -281,6 +300,13 @@ export type ModelSettings = { */ promptCacheRetention?: 'in-memory' | '24h' | null; + /** + * Context-management strategies to apply when calling the model. + * This setting is available on OpenAI Responses requests, including server-side compaction. + * See https://developers.openai.com/api/docs/guides/compaction. + */ + contextManagement?: ModelSettingsContextManagement; + /** * The reasoning settings to use when calling the model. */ diff --git a/packages/agents-openai/src/openaiResponsesModel.ts b/packages/agents-openai/src/openaiResponsesModel.ts index 19be97cbe..899719555 100644 --- a/packages/agents-openai/src/openaiResponsesModel.ts +++ b/packages/agents-openai/src/openaiResponsesModel.ts @@ -16,6 +16,7 @@ import type { SerializedTool, ModelRequest, ModelResponse, + ModelSettingsContextManagement, ModelSettingsToolChoice, ResponseStreamEvent, SerializedOutputType, @@ -700,6 +701,16 @@ function getResponseFormat( }; } +function getContextManagement( + contextManagement: ModelSettingsContextManagement | undefined, +): unknown { + if (!contextManagement) { + return undefined; + } + + return contextManagement.map((entry) => camelOrSnakeToSnakeCase(entry)); +} + function normalizeFunctionCallOutputForRequest( output: protocol.FunctionCallResultItem['output'], ): string | ResponseFunctionCallOutputListItem[] { @@ -3026,6 +3037,9 @@ export class OpenAIResponsesModel implements Model { text: responseFormat, store: request.modelSettings.store, prompt_cache_retention: request.modelSettings.promptCacheRetention, + context_management: getContextManagement( + request.modelSettings.contextManagement, + ), ...restOfProviderData, }; diff --git a/packages/agents-openai/test/openaiResponsesModel.test.ts b/packages/agents-openai/test/openaiResponsesModel.test.ts index b08c3014e..ca2c6744d 100644 --- a/packages/agents-openai/test/openaiResponsesModel.test.ts +++ b/packages/agents-openai/test/openaiResponsesModel.test.ts @@ -1083,6 +1083,40 @@ describe('OpenAIResponsesModel', () => { }); }); + it('sends context management settings to the Responses API', async () => { + await withTrace('test', async () => { + const fakeResponse = { id: 'res-context', usage: {}, output: [] }; + const createMock = vi.fn().mockResolvedValue(fakeResponse); + const fakeClient = { + responses: { create: createMock }, + } as unknown as OpenAI; + const model = new OpenAIResponsesModel(fakeClient, 'gpt-context'); + + const request = { + systemInstructions: undefined, + input: 'hello', + modelSettings: { + contextManagement: [{ type: 'compaction', compactThreshold: 200000 }], + }, + tools: [], + outputType: 'text', + handoffs: [], + tracing: false, + signal: undefined, + }; + + await model.getResponse(request as any); + + const [args] = createMock.mock.calls[0]; + expect(args.context_management).toEqual([ + { + type: 'compaction', + compact_threshold: 200000, + }, + ]); + }); + }); + it('still sends an empty tools array when no prompt is provided', async () => { await withTrace('test', async () => { const fakeResponse = { id: 'res-no-prompt', usage: {}, output: [] };