@theia/ai-openai 1.76.0-next.7 → 1.76.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/browser/openai-frontend-application-contribution.d.ts +18 -10
- package/lib/browser/openai-frontend-application-contribution.d.ts.map +1 -1
- package/lib/browser/openai-frontend-application-contribution.js +68 -64
- package/lib/browser/openai-frontend-application-contribution.js.map +1 -1
- package/lib/common/openai-language-models-manager.d.ts +20 -1
- package/lib/common/openai-language-models-manager.d.ts.map +1 -1
- package/lib/common/openai-preferences.d.ts +2 -1
- package/lib/common/openai-preferences.d.ts.map +1 -1
- package/lib/common/openai-preferences.js +23 -13
- package/lib/common/openai-preferences.js.map +1 -1
- package/lib/node/openai-language-model.d.ts +26 -2
- package/lib/node/openai-language-model.d.ts.map +1 -1
- package/lib/node/openai-language-model.js +44 -18
- package/lib/node/openai-language-model.js.map +1 -1
- package/lib/node/openai-language-model.spec.js +55 -8
- package/lib/node/openai-language-model.spec.js.map +1 -1
- package/lib/node/openai-language-models-manager-impl.d.ts +38 -1
- package/lib/node/openai-language-models-manager-impl.d.ts.map +1 -1
- package/lib/node/openai-language-models-manager-impl.js +88 -3
- package/lib/node/openai-language-models-manager-impl.js.map +1 -1
- package/lib/node/openai-language-models-manager-impl.spec.js +171 -0
- package/lib/node/openai-language-models-manager-impl.spec.js.map +1 -1
- package/lib/node/openai-model-defaults.d.ts.map +1 -1
- package/lib/node/openai-model-defaults.js +7 -0
- package/lib/node/openai-model-defaults.js.map +1 -1
- package/lib/node/openai-model-defaults.spec.js +9 -0
- package/lib/node/openai-model-defaults.spec.js.map +1 -1
- package/lib/node/openai-reasoning.d.ts +1 -2
- package/lib/node/openai-reasoning.d.ts.map +1 -1
- package/lib/node/openai-reasoning.js +6 -8
- package/lib/node/openai-reasoning.js.map +1 -1
- package/lib/node/openai-response-api-utils.d.ts +25 -1
- package/lib/node/openai-response-api-utils.d.ts.map +1 -1
- package/lib/node/openai-response-api-utils.js +104 -12
- package/lib/node/openai-response-api-utils.js.map +1 -1
- package/lib/node/openai-response-api-utils.spec.js +146 -0
- package/lib/node/openai-response-api-utils.spec.js.map +1 -1
- package/package.json +7 -7
- package/src/browser/openai-frontend-application-contribution.ts +82 -69
- package/src/common/openai-language-models-manager.ts +21 -2
- package/src/common/openai-preferences.ts +25 -12
- package/src/node/openai-language-model.spec.ts +63 -9
- package/src/node/openai-language-model.ts +58 -20
- package/src/node/openai-language-models-manager-impl.spec.ts +178 -0
- package/src/node/openai-language-models-manager-impl.ts +106 -6
- package/src/node/openai-model-defaults.spec.ts +10 -0
- package/src/node/openai-model-defaults.ts +8 -0
- package/src/node/openai-reasoning.ts +6 -9
- package/src/node/openai-response-api-utils.spec.ts +184 -1
- package/src/node/openai-response-api-utils.ts +115 -13
|
@@ -21,12 +21,13 @@ import {
|
|
|
21
21
|
LanguageModelResponse,
|
|
22
22
|
LanguageModelStreamResponsePart,
|
|
23
23
|
TextMessage,
|
|
24
|
+
ThinkingResponsePart,
|
|
24
25
|
ToolCallResult,
|
|
25
26
|
ToolInvocationContext,
|
|
26
27
|
ToolRequest,
|
|
27
28
|
UserRequest
|
|
28
29
|
} from '@theia/ai-core';
|
|
29
|
-
import { CancellationToken, nls, unreachable, ILogger } from '@theia/core';
|
|
30
|
+
import { CancellationToken, isObject, nls, unreachable, ILogger } from '@theia/core';
|
|
30
31
|
import { Deferred } from '@theia/core/lib/common/promise-util';
|
|
31
32
|
import { injectable, inject, named } from '@theia/core/shared/inversify';
|
|
32
33
|
import { OpenAI } from 'openai';
|
|
@@ -116,20 +117,20 @@ export class OpenAiResponseApiUtils {
|
|
|
116
117
|
// If no tools are provided, use simple response handling
|
|
117
118
|
if (!tools || tools.length === 0) {
|
|
118
119
|
if (isStreaming) {
|
|
119
|
-
const stream = openai.responses.stream({
|
|
120
|
+
const stream = this.streamWithReasoningSummaryFallback(effectiveSettings, modelId, sentSettings => openai.responses.stream({
|
|
120
121
|
model: model as ResponsesModel,
|
|
121
122
|
instructions,
|
|
122
123
|
input,
|
|
123
|
-
...
|
|
124
|
-
});
|
|
124
|
+
...sentSettings
|
|
125
|
+
}));
|
|
125
126
|
return { stream: this.createSimpleResponseApiStreamIterator(stream, cancellationToken) };
|
|
126
127
|
} else {
|
|
127
|
-
const response = await openai.responses.create({
|
|
128
|
+
const response = await this.sendWithReasoningSummaryFallback(effectiveSettings, modelId, sentSettings => openai.responses.create({
|
|
128
129
|
model: model as ResponsesModel,
|
|
129
130
|
instructions,
|
|
130
131
|
input,
|
|
131
|
-
...
|
|
132
|
-
});
|
|
132
|
+
...sentSettings
|
|
133
|
+
}));
|
|
133
134
|
|
|
134
135
|
return {
|
|
135
136
|
text: response.output_text || '',
|
|
@@ -189,12 +190,99 @@ export class OpenAiResponseApiUtils {
|
|
|
189
190
|
return converted;
|
|
190
191
|
}
|
|
191
192
|
|
|
193
|
+
/**
|
|
194
|
+
* Maps a reasoning-summary stream event to a thinking part. With `reasoning.summary` set, the Responses API streams
|
|
195
|
+
* one or more `summary_text` parts per reasoning item; parts after the first are separated by a blank line.
|
|
196
|
+
*/
|
|
197
|
+
reasoningSummaryThought(event: ResponseStreamEvent): ThinkingResponsePart | undefined {
|
|
198
|
+
if (event.type === 'response.reasoning_summary_part.added') {
|
|
199
|
+
return event.summary_index > 0 ? { thought: '\n\n', signature: '' } : undefined;
|
|
200
|
+
}
|
|
201
|
+
if (event.type === 'response.reasoning_summary_text.delta') {
|
|
202
|
+
return { thought: event.delta, signature: '' };
|
|
203
|
+
}
|
|
204
|
+
return undefined;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Model ids whose organization rejected `reasoning.summary` (OpenAI requires a verified organization for summaries).
|
|
209
|
+
* Later requests to these models omit the field.
|
|
210
|
+
*/
|
|
211
|
+
protected readonly reasoningSummaryRejectedModels = new Set<string>();
|
|
212
|
+
|
|
213
|
+
protected withoutRejectedReasoningSummary(settings: Record<string, unknown>, modelId: string): Record<string, unknown> {
|
|
214
|
+
const reasoning = settings.reasoning;
|
|
215
|
+
if (!this.reasoningSummaryRejectedModels.has(modelId) || !isObject(reasoning) || !('summary' in reasoning)) {
|
|
216
|
+
return settings;
|
|
217
|
+
}
|
|
218
|
+
const withoutSummary = { ...reasoning };
|
|
219
|
+
delete withoutSummary.summary;
|
|
220
|
+
return { ...settings, reasoning: withoutSummary };
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Returns `true` and remembers the rejection when `error` is the 400 an unverified organization gets for `reasoning.summary`,
|
|
225
|
+
* so the request can be sent once more without it.
|
|
226
|
+
*/
|
|
227
|
+
protected handleReasoningSummaryRejection(error: unknown, sentSettings: Record<string, unknown>, modelId: string): boolean {
|
|
228
|
+
const reasoning = sentSettings.reasoning;
|
|
229
|
+
if (!(error instanceof OpenAI.BadRequestError) || error.param !== 'reasoning.summary' || !isObject(reasoning) || !('summary' in reasoning)) {
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
this.logger.warn(`Model ${modelId} rejected reasoning summaries (${error.message}); continuing without them.`);
|
|
233
|
+
this.reasoningSummaryRejectedModels.add(modelId);
|
|
234
|
+
return true;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Sends a Responses API request, retrying once without `reasoning.summary` if the organization is not allowed to request it.
|
|
239
|
+
*/
|
|
240
|
+
async sendWithReasoningSummaryFallback<T>(
|
|
241
|
+
settings: Record<string, unknown>,
|
|
242
|
+
modelId: string,
|
|
243
|
+
send: (settings: Record<string, unknown>) => Promise<T>
|
|
244
|
+
): Promise<T> {
|
|
245
|
+
const sentSettings = this.withoutRejectedReasoningSummary(settings, modelId);
|
|
246
|
+
try {
|
|
247
|
+
return await send(sentSettings);
|
|
248
|
+
} catch (error) {
|
|
249
|
+
if (!this.handleReasoningSummaryRejection(error, sentSettings, modelId)) {
|
|
250
|
+
throw error;
|
|
251
|
+
}
|
|
252
|
+
return send(this.withoutRejectedReasoningSummary(settings, modelId));
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Streaming counterpart of {@link sendWithReasoningSummaryFallback}. The rejection arrives before any event, so nothing is emitted twice.
|
|
258
|
+
*/
|
|
259
|
+
async *streamWithReasoningSummaryFallback(
|
|
260
|
+
settings: Record<string, unknown>,
|
|
261
|
+
modelId: string,
|
|
262
|
+
open: (settings: Record<string, unknown>) => AsyncIterable<ResponseStreamEvent>
|
|
263
|
+
): AsyncIterable<ResponseStreamEvent> {
|
|
264
|
+
const sentSettings = this.withoutRejectedReasoningSummary(settings, modelId);
|
|
265
|
+
let received = false;
|
|
266
|
+
try {
|
|
267
|
+
for await (const event of open(sentSettings)) {
|
|
268
|
+
received = true;
|
|
269
|
+
yield event;
|
|
270
|
+
}
|
|
271
|
+
} catch (error) {
|
|
272
|
+
if (received || !this.handleReasoningSummaryRejection(error, sentSettings, modelId)) {
|
|
273
|
+
throw error;
|
|
274
|
+
}
|
|
275
|
+
yield* open(this.withoutRejectedReasoningSummary(settings, modelId));
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
192
279
|
protected createSimpleResponseApiStreamIterator(
|
|
193
280
|
stream: AsyncIterable<ResponseStreamEvent>,
|
|
194
281
|
cancellationToken?: CancellationToken
|
|
195
282
|
): AsyncIterable<LanguageModelStreamResponsePart> {
|
|
196
283
|
|
|
197
284
|
const logger = this.logger;
|
|
285
|
+
const reasoningSummaryThought = (event: ResponseStreamEvent): ThinkingResponsePart | undefined => this.reasoningSummaryThought(event);
|
|
198
286
|
|
|
199
287
|
return {
|
|
200
288
|
async *[Symbol.asyncIterator](): AsyncIterator<LanguageModelStreamResponsePart> {
|
|
@@ -210,6 +298,11 @@ export class OpenAiResponseApiUtils {
|
|
|
210
298
|
yield {
|
|
211
299
|
content: event.delta
|
|
212
300
|
};
|
|
301
|
+
} else if (event.type === 'response.reasoning_summary_part.added' || event.type === 'response.reasoning_summary_text.delta') {
|
|
302
|
+
const thought = reasoningSummaryThought(event);
|
|
303
|
+
if (thought) {
|
|
304
|
+
yield thought;
|
|
305
|
+
}
|
|
213
306
|
} else if (event.type === 'response.output_item.done' && event.item?.type === 'compaction') {
|
|
214
307
|
yield {
|
|
215
308
|
compaction: {
|
|
@@ -500,13 +593,13 @@ class ResponseApiToolCallIterator implements AsyncIterableIterator<LanguageModel
|
|
|
500
593
|
|
|
501
594
|
if (this.isStreaming) {
|
|
502
595
|
// Use streaming API
|
|
503
|
-
const stream = this.openai.responses.stream({
|
|
596
|
+
const stream = this.utils.streamWithReasoningSummaryFallback(this.settings, this.modelId, settings => this.openai.responses.stream({
|
|
504
597
|
model: this.model as ResponsesModel,
|
|
505
598
|
instructions: this.instructions,
|
|
506
599
|
input: this.currentInput,
|
|
507
600
|
tools: this.tools,
|
|
508
|
-
...
|
|
509
|
-
});
|
|
601
|
+
...settings
|
|
602
|
+
}));
|
|
510
603
|
|
|
511
604
|
for await (const event of stream) {
|
|
512
605
|
if (this.cancellationToken?.isCancellationRequested) {
|
|
@@ -521,13 +614,13 @@ class ResponseApiToolCallIterator implements AsyncIterableIterator<LanguageModel
|
|
|
521
614
|
}
|
|
522
615
|
|
|
523
616
|
protected async processNonStreamingResponse(): Promise<void> {
|
|
524
|
-
const response = await this.openai.responses.create({
|
|
617
|
+
const response = await this.utils.sendWithReasoningSummaryFallback(this.settings, this.modelId, settings => this.openai.responses.create({
|
|
525
618
|
model: this.model as ResponsesModel,
|
|
526
619
|
instructions: this.instructions,
|
|
527
620
|
input: this.currentInput,
|
|
528
621
|
tools: this.tools,
|
|
529
|
-
...
|
|
530
|
-
});
|
|
622
|
+
...settings
|
|
623
|
+
}));
|
|
531
624
|
|
|
532
625
|
// Record token usage
|
|
533
626
|
if (response.usage) {
|
|
@@ -619,6 +712,15 @@ class ResponseApiToolCallIterator implements AsyncIterableIterator<LanguageModel
|
|
|
619
712
|
}
|
|
620
713
|
break;
|
|
621
714
|
|
|
715
|
+
case 'response.reasoning_summary_part.added':
|
|
716
|
+
case 'response.reasoning_summary_text.delta': {
|
|
717
|
+
const thought = this.utils.reasoningSummaryThought(event);
|
|
718
|
+
if (thought) {
|
|
719
|
+
this.handleIncoming(thought);
|
|
720
|
+
}
|
|
721
|
+
break;
|
|
722
|
+
}
|
|
723
|
+
|
|
622
724
|
case 'response.completed':
|
|
623
725
|
if (event.response?.usage) {
|
|
624
726
|
this.handleIncoming({
|