feat(server): adapt gemini3.1 preview (#14583)

#### PR Dependency Tree


* **PR #14583** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added Gemini 3.1 Pro Preview support (text, image, audio) and new
GPT‑5 variants as defaults; centralized persistent telemetry state for
more reliable client identity.

* **UX**
  * Improved model submenu placement in chat preferences.
* More robust mindmap parsing, preview, regeneration and replace
behavior.

* **Chores**
  * Bumped AI SDK and related dependencies.

* **Tests**
  * Expanded/updated tests and increased timeouts for flaky flows.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
DarkSky
2026-03-08 00:53:16 +08:00
committed by GitHub
parent 9742e9735e
commit 9c55edeb62
36 changed files with 980 additions and 375 deletions
@@ -118,7 +118,6 @@ test.serial.before(async t => {
enabled: true,
scenarios: {
image: 'flux-1/schnell',
rerank: 'gpt-5-mini',
complex_text_generation: 'gpt-5-mini',
coding: 'gpt-5-mini',
quick_decision_making: 'gpt-5-mini',
@@ -931,8 +930,8 @@ test(
t.log('Rerank scores:', scores);
t.is(
scores.filter(s => s > 0.5).length,
4,
'should have 4 related chunks'
5,
'should have 5 related chunks'
);
});
}
@@ -18,7 +18,7 @@ test('NativeProviderAdapter streamText should append citation footnotes', async
const adapter = new NativeProviderAdapter(mockDispatch, {}, 3);
const chunks: string[] = [];
for await (const chunk of adapter.streamText({
model: 'gpt-4.1',
model: 'gpt-5-mini',
stream: true,
messages: [{ role: 'user', content: [{ type: 'text', text: 'hi' }] }],
})) {
@@ -36,7 +36,7 @@ test('NativeProviderAdapter streamObject should append citation footnotes', asyn
const adapter = new NativeProviderAdapter(mockDispatch, {}, 3);
const chunks = [];
for await (const chunk of adapter.streamObject({
model: 'gpt-4.1',
model: 'gpt-5-mini',
stream: true,
messages: [{ role: 'user', content: [{ type: 'text', text: 'hi' }] }],
})) {
@@ -91,7 +91,7 @@ test('NativeProviderAdapter streamObject should append fallback attachment footn
const adapter = new NativeProviderAdapter(dispatch, {}, 3);
const chunks = [];
for await (const chunk of adapter.streamObject({
model: 'gpt-4.1',
model: 'gpt-5-mini',
stream: true,
messages: [{ role: 'user', content: [{ type: 'text', text: 'hi' }] }],
})) {
@@ -148,7 +148,7 @@ test('NativeProviderAdapter streamObject should map tool and text events', async
const events = [];
for await (const event of adapter.streamObject({
model: 'gpt-4.1',
model: 'gpt-5-mini',
stream: true,
messages: [{ role: 'user', content: [{ type: 'text', text: 'read' }] }],
})) {
@@ -169,7 +169,7 @@ test('NativeProviderAdapter streamObject should map tool and text events', async
test('buildNativeRequest should include rust middleware from profile', async t => {
const { request } = await buildNativeRequest({
model: 'gpt-4.1',
model: 'gpt-5-mini',
messages: [{ role: 'user', content: 'hello' }],
tools: {},
middleware: {
@@ -195,7 +195,7 @@ test('NativeProviderAdapter streamText should skip citation footnotes when disab
});
const chunks: string[] = [];
for await (const chunk of adapter.streamText({
model: 'gpt-4.1',
model: 'gpt-5-mini',
stream: true,
messages: [{ role: 'user', content: [{ type: 'text', text: 'hi' }] }],
})) {
@@ -1,7 +1,9 @@
import test from 'ava';
import { ProviderMiddlewareConfig } from '../../plugins/copilot/config';
import { normalizeOpenAIOptionsForModel } from '../../plugins/copilot/providers/openai';
import { CopilotProvider } from '../../plugins/copilot/providers/provider';
import { normalizeRerankModel } from '../../plugins/copilot/providers/rerank';
import {
CopilotProviderType,
ModelInputType,
@@ -12,7 +14,7 @@ class TestOpenAIProvider extends CopilotProvider<{ apiKey: string }> {
readonly type = CopilotProviderType.OpenAI;
readonly models = [
{
id: 'gpt-4.1',
id: 'gpt-5-mini',
capabilities: [
{
input: [ModelInputType.Text],
@@ -36,7 +38,7 @@ class TestOpenAIProvider extends CopilotProvider<{ apiKey: string }> {
}
exposeMetricLabels() {
return this.metricLabels('gpt-4.1');
return this.metricLabels('gpt-5-mini');
}
exposeMiddleware() {
@@ -97,3 +99,41 @@ test('getActiveProviderMiddleware should merge defaults with profile override',
'thinking_format',
]);
});
test('normalizeOpenAIOptionsForModel should drop sampling knobs for gpt-5.2', t => {
t.deepEqual(
normalizeOpenAIOptionsForModel(
{
temperature: 0.7,
topP: 0.8,
presencePenalty: 0.2,
frequencyPenalty: 0.1,
maxTokens: 128,
},
'gpt-5.4'
),
{ maxTokens: 128 }
);
});
test('normalizeOpenAIOptionsForModel should keep options for gpt-4.1', t => {
t.deepEqual(
normalizeOpenAIOptionsForModel(
{ temperature: 0.7, topP: 0.8, maxTokens: 128 },
'gpt-4.1'
),
{ temperature: 0.7, topP: 0.8, maxTokens: 128 }
);
});
test('normalizeOpenAIRerankModel should keep supported rerank models', t => {
t.is(normalizeRerankModel('gpt-4.1'), 'gpt-4.1');
t.is(normalizeRerankModel('gpt-4.1-mini'), 'gpt-4.1-mini');
t.is(normalizeRerankModel('gpt-5.2'), 'gpt-5.2');
});
test('normalizeOpenAIRerankModel should fall back for unsupported models', t => {
t.is(normalizeRerankModel('gpt-5-mini'), 'gpt-5.2');
t.is(normalizeRerankModel('gemini-2.5-flash'), 'gpt-5.2');
t.is(normalizeRerankModel(undefined), 'gpt-5.2');
});
@@ -88,11 +88,11 @@ test('resolveModel should support explicit provider prefix and keep slash models
const prefixed = resolveModel({
registry,
modelId: 'openai-main/gpt-4.1',
modelId: 'openai-main/gpt-5-mini',
});
t.deepEqual(prefixed, {
rawModelId: 'openai-main/gpt-4.1',
modelId: 'gpt-4.1',
rawModelId: 'openai-main/gpt-5-mini',
modelId: 'gpt-5-mini',
explicitProviderId: 'openai-main',
candidateProviderIds: ['openai-main'],
});
@@ -154,12 +154,15 @@ test('stripProviderPrefix should only strip matched provider prefix', t => {
});
t.is(
stripProviderPrefix(registry, 'openai-main', 'openai-main/gpt-4.1'),
'gpt-4.1'
stripProviderPrefix(registry, 'openai-main', 'openai-main/gpt-5-mini'),
'gpt-5-mini'
);
t.is(
stripProviderPrefix(registry, 'openai-main', 'another-main/gpt-4.1'),
'another-main/gpt-4.1'
stripProviderPrefix(registry, 'openai-main', 'another-main/gpt-5-mini'),
'another-main/gpt-5-mini'
);
t.is(
stripProviderPrefix(registry, 'openai-main', 'gpt-5-mini'),
'gpt-5-mini'
);
t.is(stripProviderPrefix(registry, 'openai-main', 'gpt-4.1'), 'gpt-4.1');
});
@@ -116,7 +116,7 @@ test('ToolCallLoop should execute tool call and continue to next round', async t
const events: NativeLlmStreamEvent[] = [];
for await (const event of loop.run({
model: 'gpt-4.1',
model: 'gpt-5-mini',
stream: true,
messages: [{ role: 'user', content: [{ type: 'text', text: 'read doc' }] }],
})) {
@@ -39,33 +39,6 @@ export class MockCopilotProvider extends OpenAIProvider {
},
],
},
{
id: 'gpt-4o',
capabilities: [
{
input: [ModelInputType.Text, ModelInputType.Image],
output: [ModelOutputType.Text, ModelOutputType.Object],
},
],
},
{
id: 'gpt-4o-2024-08-06',
capabilities: [
{
input: [ModelInputType.Text, ModelInputType.Image],
output: [ModelOutputType.Text, ModelOutputType.Object],
},
],
},
{
id: 'gpt-4.1-2025-04-14',
capabilities: [
{
input: [ModelInputType.Text, ModelInputType.Image],
output: [ModelOutputType.Text, ModelOutputType.Object],
},
],
},
{
id: 'gpt-5',
capabilities: [
@@ -97,6 +70,19 @@ export class MockCopilotProvider extends OpenAIProvider {
},
],
},
{
id: 'gpt-5-nano',
capabilities: [
{
input: [ModelInputType.Text, ModelInputType.Image],
output: [
ModelOutputType.Text,
ModelOutputType.Object,
ModelOutputType.Structured,
],
},
],
},
{
id: 'gpt-image-1',
capabilities: [
@@ -133,6 +119,23 @@ export class MockCopilotProvider extends OpenAIProvider {
},
],
},
{
id: 'gemini-3.1-pro-preview',
capabilities: [
{
input: [
ModelInputType.Text,
ModelInputType.Image,
ModelInputType.Audio,
],
output: [
ModelOutputType.Text,
ModelOutputType.Object,
ModelOutputType.Structured,
],
},
],
},
];
override async text(