feat(server): update model list (#15375)

This commit is contained in:
DarkSky
2026-07-30 13:58:07 +08:00
committed by GitHub
parent 00576e1e78
commit a1defa8a3b
24 changed files with 253 additions and 364 deletions
@@ -282,43 +282,6 @@ Generated by [AVA](https://avajs.dev).
},
]
## should be able to run image executor
> should generate image stream
[
{
params: {
key: [
'https://example.com/test-image.jpg',
'tag1, tag2, tag3, tag4, tag5, ',
],
},
type: 2,
},
]
> should render the prompt with params array
[
{
modelId: 'test-image',
},
[
{
content: 'tag1, tag2, tag3, tag4, tag5, ',
params: {
tags: [
'tag4',
'tag5',
],
},
role: 'user',
},
],
{},
]
## should handle generateSessionTitle correctly under various conditions
> should generate title when conditions are met
@@ -437,66 +400,32 @@ Generated by [AVA](https://avajs.dev).
> should honor requested pro model
'gemini-2.5-pro'
'gpt-5.6-terra'
> should fallback to default model
'gemini-2.5-flash'
'gpt-5.6-luna'
> should fallback to default model when requesting pro model during trialing
'gemini-2.5-flash'
'gpt-5.6-luna'
> should honor requested non-pro model during trialing
'gemini-2.5-flash'
'gpt-5.6-luna'
> should pick default model when no requested model during trialing
'gemini-2.5-flash'
'gpt-5.6-luna'
> should pick default model when no requested model during active
'gemini-2.5-flash'
'gpt-5.6-luna'
> should honor requested pro model during active
'claude-sonnet-4-5@20250929'
'claude-sonnet-4-6'
> should fallback to default model when requesting non-optional model during active
'gemini-2.5-flash'
## should resolve model correctly based on subscription status and prompt config
> should honor requested pro model
'gemini-2.5-pro'
> should fallback to default model
'gemini-2.5-flash'
> should fallback to default model when requesting pro model during trialing
'gemini-2.5-flash'
> should honor requested non-pro model during trialing
'gemini-2.5-flash'
> should pick default model when no requested model during trialing
'gemini-2.5-flash'
> should pick default model when no requested model during active
'gemini-2.5-flash'
> should honor requested pro model during active
'claude-sonnet-4-5@20250929'
> should fallback to default model when requesting non-optional model during active
'gemini-2.5-flash'
'gpt-5.6-luna'
@@ -364,7 +364,7 @@ Generated by [AVA](https://avajs.dev).
'tool_schema_rewrite',
],
},
model: 'gemini-2.5-flash',
model: 'gemini-3.6-flash',
responseMimeType: 'application/json',
schema: {
additionalProperties: false,
@@ -71,14 +71,14 @@ Generated by [AVA](https://avajs.dev).
{
matchedOptionalModel: true,
selectedModel: 'openai-default/gemini-2.5-pro',
selectedModel: 'openai-default/gpt-5.6-terra',
}
> prefixed optional miss
{
matchedOptionalModel: false,
selectedModel: 'gemini-2.5-flash',
selectedModel: 'gpt-5.6-luna',
}
## ExecutionPlan should serialize routed request state and reject host-only signal
@@ -441,7 +441,7 @@ The term **“CRDT”** was first introduced by Marc Shapiro, Nuno Preguiça, Ca
messages: singleUserPromptMessages(
'In one short sentence, explain what AFFiNE AI is and mention AFFiNE by name.'
),
config: { model: 'gemini-2.5-flash' },
config: { model: 'gemini-3.6-flash' },
verifier: (t: ExecutionContext<Tester>, result: string) => {
assertNotWrappedInCodeBlock(t, result);
t.assert(
@@ -458,7 +458,7 @@ The term **“CRDT”** was first introduced by Marc Shapiro, Nuno Preguiça, Ca
messages: singleUserPromptMessages(
'Respond with one short sentence about AFFiNE AI and mention AFFiNE by name.'
),
config: { model: 'gemini-2.5-flash' },
config: { model: 'gemini-3.6-flash' },
verifier: (t: ExecutionContext<Tester>, result: string) => {
t.truthy(checkStreamObjects(result), 'should be valid stream objects');
const assembledText = getStreamObjectText(result);
@@ -985,19 +985,19 @@ const TRANSCRIPT_AUDIO_CASES = [
name: 'short audio',
url: 'https://cdn.affine.pro/copilot-test/MP9qDGuYgnY+ILoEAmHpp3h9Npuw2403EAYMEA.mp3',
mimeType: 'audio/mpeg',
modelId: 'gemini-2.5-flash',
modelId: 'gemini-3.5-flash-lite',
},
{
name: 'middle audio',
url: 'https://cdn.affine.pro/copilot-test/2ed05eo1KvZ2tWB_BAjFo67EAPZZY-w4LylUAw.m4a',
mimeType: 'audio/m4a',
modelId: 'gemini-2.5-flash',
modelId: 'gemini-3.5-flash-lite',
},
{
name: 'long audio',
url: 'https://cdn.affine.pro/copilot-test/nC9-e7P85PPI2rU29QWwf8slBNRMy92teLIIMw.opus',
mimeType: 'audio/opus',
modelId: 'gemini-2.5-pro',
modelId: 'gemini-3.6-flash',
},
];
@@ -588,7 +588,7 @@ test('should be able to chat with api', async t => {
t.is(
array2sse(sse2array(ret3).filter(e => e.event !== 'event')),
textToEventStream(
['https://example.com/gpt-image-1.jpg'],
['https://example.com/gpt-image-2.jpg'],
messageId,
'attachment'
),
@@ -717,14 +717,13 @@ test('should map action stream preparation errors to SSE error events', async t
});
test('should be able to chat with special image model', async t => {
const { app, prompt, storage } = t.context;
const { app, storage } = t.context;
Sinon.stub(storage, 'handleRemoteLink').resolvesArg(2);
const { id } = await createWorkspace(app);
const testWithModel = async (promptName: string, finalPrompt: string) => {
const model = (await prompt.get(promptName))?.model;
const sessionId = await createCopilotSession(
app,
id,
@@ -739,7 +738,7 @@ test('should be able to chat with special image model', async t => {
ret3,
textToEventStream(
[
`https://example.com/${model}.jpg`,
'https://example.com/gpt-image-2.jpg',
`https://example.com/generated/${encodeURIComponent(finalPrompt)}.jpg`,
],
messageId,
@@ -1915,7 +1914,7 @@ test('should be able to transcript', async t => {
const route = input.input?.preparedRoutes?.transcribe?.[0] ?? {};
const result = buildTranscriptActionResult(
route,
'gemini-2.5-flash',
'gemini-3.5-flash-lite',
input.input ?? {}
);
const actionVersion = input.recipeVersion ?? 'v1';
@@ -2414,63 +2414,47 @@ test('model selection policy should resolve requested optional models consistent
t.deepEqual(
modelSelection.resolveRequestedModel({
defaultModel: 'gemini-2.5-flash',
optionalModels: [
'gemini-2.5-flash',
'gemini-2.5-pro',
'claude-sonnet-4-5@20250929',
],
requestedModelId: 'gemini-2.5-pro',
defaultModel: 'gpt-5.6-luna',
optionalModels: ['gpt-5.6-luna', 'gpt-5.6-terra', 'claude-sonnet-4-6'],
requestedModelId: 'gpt-5.6-terra',
}),
{
selectedModel: 'gemini-2.5-pro',
selectedModel: 'gpt-5.6-terra',
matchedOptionalModel: true,
}
);
t.deepEqual(
modelSelection.resolveRequestedModel({
defaultModel: 'gemini-2.5-flash',
optionalModels: [
'gemini-2.5-flash',
'gemini-2.5-pro',
'claude-sonnet-4-5@20250929',
],
requestedModelId: 'openai-default/gemini-2.5-pro',
defaultModel: 'gpt-5.6-luna',
optionalModels: ['gpt-5.6-luna', 'gpt-5.6-terra', 'claude-sonnet-4-6'],
requestedModelId: 'openai-default/gpt-5.6-terra',
}),
{
selectedModel: 'openai-default/gemini-2.5-pro',
selectedModel: 'openai-default/gpt-5.6-terra',
matchedOptionalModel: true,
}
);
t.deepEqual(
modelSelection.resolveRequestedModel({
defaultModel: 'gemini-2.5-flash',
optionalModels: [
'gemini-2.5-flash',
'gemini-2.5-pro',
'claude-sonnet-4-5@20250929',
],
defaultModel: 'gpt-5.6-luna',
optionalModels: ['gpt-5.6-luna', 'gpt-5.6-terra', 'claude-sonnet-4-6'],
requestedModelId: 'not-in-optional',
}),
{
selectedModel: 'gemini-2.5-flash',
selectedModel: 'gpt-5.6-luna',
matchedOptionalModel: false,
}
);
t.is(
modelSelection.resolveRequestedModel({
defaultModel: 'gemini-2.5-flash',
optionalModels: [
'gemini-2.5-flash',
'gemini-2.5-pro',
'claude-sonnet-4-5@20250929',
],
defaultModel: 'gpt-5.6-luna',
optionalModels: ['gpt-5.6-luna', 'gpt-5.6-terra', 'claude-sonnet-4-6'],
requestedModelId: 'not-in-optional',
}).selectedModel,
'gemini-2.5-flash'
'gpt-5.6-luna'
);
});
@@ -2494,45 +2478,33 @@ test('capability policy host should gate pro model requests by subscription stat
{
const model1 = await capabilityPolicy.resolveChatModel({
userId,
defaultModel: 'gemini-2.5-flash',
optionalModels: [
'gemini-2.5-flash',
'gemini-2.5-pro',
'claude-sonnet-4-5@20250929',
],
proModels: ['gemini-2.5-pro', 'claude-sonnet-4-5@20250929'],
requestedModelId: 'gemini-2.5-pro',
defaultModel: 'gpt-5.6-luna',
optionalModels: ['gpt-5.6-luna', 'gpt-5.6-terra', 'claude-sonnet-4-6'],
proModels: ['gpt-5.6-terra', 'claude-sonnet-4-6'],
requestedModelId: 'gpt-5.6-terra',
paymentEnabled: false,
});
t.snapshot(model1, 'should honor requested pro model');
const model1WithPrefix = await capabilityPolicy.resolveChatModel({
userId,
defaultModel: 'gemini-2.5-flash',
optionalModels: [
'gemini-2.5-flash',
'gemini-2.5-pro',
'claude-sonnet-4-5@20250929',
],
proModels: ['gemini-2.5-pro', 'claude-sonnet-4-5@20250929'],
requestedModelId: 'openai-default/gemini-2.5-pro',
defaultModel: 'gpt-5.6-luna',
optionalModels: ['gpt-5.6-luna', 'gpt-5.6-terra', 'claude-sonnet-4-6'],
proModels: ['gpt-5.6-terra', 'claude-sonnet-4-6'],
requestedModelId: 'openai-default/gpt-5.6-terra',
paymentEnabled: false,
});
t.is(
model1WithPrefix,
'openai-default/gemini-2.5-pro',
'openai-default/gpt-5.6-terra',
'should honor requested prefixed pro model'
);
const model2 = await capabilityPolicy.resolveChatModel({
userId,
defaultModel: 'gemini-2.5-flash',
optionalModels: [
'gemini-2.5-flash',
'gemini-2.5-pro',
'claude-sonnet-4-5@20250929',
],
proModels: ['gemini-2.5-pro', 'claude-sonnet-4-5@20250929'],
defaultModel: 'gpt-5.6-luna',
optionalModels: ['gpt-5.6-luna', 'gpt-5.6-terra', 'claude-sonnet-4-6'],
proModels: ['gpt-5.6-terra', 'claude-sonnet-4-6'],
requestedModelId: 'not-in-optional',
paymentEnabled: false,
});
@@ -2544,14 +2516,10 @@ test('capability policy host should gate pro model requests by subscription stat
mockStatus(SubscriptionStatus.Trialing);
const model3 = await capabilityPolicy.resolveChatModel({
userId,
defaultModel: 'gemini-2.5-flash',
optionalModels: [
'gemini-2.5-flash',
'gemini-2.5-pro',
'claude-sonnet-4-5@20250929',
],
proModels: ['gemini-2.5-pro', 'claude-sonnet-4-5@20250929'],
requestedModelId: 'gemini-2.5-pro',
defaultModel: 'gpt-5.6-luna',
optionalModels: ['gpt-5.6-luna', 'gpt-5.6-terra', 'claude-sonnet-4-6'],
proModels: ['gpt-5.6-terra', 'claude-sonnet-4-6'],
requestedModelId: 'gpt-5.6-terra',
paymentEnabled: true,
});
t.snapshot(
@@ -2561,45 +2529,33 @@ test('capability policy host should gate pro model requests by subscription stat
const model3WithPrefix = await capabilityPolicy.resolveChatModel({
userId,
defaultModel: 'gemini-2.5-flash',
optionalModels: [
'gemini-2.5-flash',
'gemini-2.5-pro',
'claude-sonnet-4-5@20250929',
],
proModels: ['gemini-2.5-pro', 'claude-sonnet-4-5@20250929'],
requestedModelId: 'openai-default/gemini-2.5-pro',
defaultModel: 'gpt-5.6-luna',
optionalModels: ['gpt-5.6-luna', 'gpt-5.6-terra', 'claude-sonnet-4-6'],
proModels: ['gpt-5.6-terra', 'claude-sonnet-4-6'],
requestedModelId: 'openai-default/gpt-5.6-terra',
paymentEnabled: true,
});
t.is(
model3WithPrefix,
'gemini-2.5-flash',
'gpt-5.6-luna',
'should fallback to default model when requesting prefixed pro model during trialing'
);
const model4 = await capabilityPolicy.resolveChatModel({
userId,
defaultModel: 'gemini-2.5-flash',
optionalModels: [
'gemini-2.5-flash',
'gemini-2.5-pro',
'claude-sonnet-4-5@20250929',
],
proModels: ['gemini-2.5-pro', 'claude-sonnet-4-5@20250929'],
requestedModelId: 'gemini-2.5-flash',
defaultModel: 'gpt-5.6-luna',
optionalModels: ['gpt-5.6-luna', 'gpt-5.6-terra', 'claude-sonnet-4-6'],
proModels: ['gpt-5.6-terra', 'claude-sonnet-4-6'],
requestedModelId: 'gpt-5.6-luna',
paymentEnabled: true,
});
t.snapshot(model4, 'should honor requested non-pro model during trialing');
const model5 = await capabilityPolicy.resolveChatModel({
userId,
defaultModel: 'gemini-2.5-flash',
optionalModels: [
'gemini-2.5-flash',
'gemini-2.5-pro',
'claude-sonnet-4-5@20250929',
],
proModels: ['gemini-2.5-pro', 'claude-sonnet-4-5@20250929'],
defaultModel: 'gpt-5.6-luna',
optionalModels: ['gpt-5.6-luna', 'gpt-5.6-terra', 'claude-sonnet-4-6'],
proModels: ['gpt-5.6-terra', 'claude-sonnet-4-6'],
paymentEnabled: true,
});
t.snapshot(
@@ -2613,13 +2569,9 @@ test('capability policy host should gate pro model requests by subscription stat
mockStatus(SubscriptionStatus.Active);
const model6 = await capabilityPolicy.resolveChatModel({
userId,
defaultModel: 'gemini-2.5-flash',
optionalModels: [
'gemini-2.5-flash',
'gemini-2.5-pro',
'claude-sonnet-4-5@20250929',
],
proModels: ['gemini-2.5-pro', 'claude-sonnet-4-5@20250929'],
defaultModel: 'gpt-5.6-luna',
optionalModels: ['gpt-5.6-luna', 'gpt-5.6-terra', 'claude-sonnet-4-6'],
proModels: ['gpt-5.6-terra', 'claude-sonnet-4-6'],
paymentEnabled: true,
});
t.snapshot(
@@ -2629,45 +2581,33 @@ test('capability policy host should gate pro model requests by subscription stat
const model7 = await capabilityPolicy.resolveChatModel({
userId,
defaultModel: 'gemini-2.5-flash',
optionalModels: [
'gemini-2.5-flash',
'gemini-2.5-pro',
'claude-sonnet-4-5@20250929',
],
proModels: ['gemini-2.5-pro', 'claude-sonnet-4-5@20250929'],
requestedModelId: 'claude-sonnet-4-5@20250929',
defaultModel: 'gpt-5.6-luna',
optionalModels: ['gpt-5.6-luna', 'gpt-5.6-terra', 'claude-sonnet-4-6'],
proModels: ['gpt-5.6-terra', 'claude-sonnet-4-6'],
requestedModelId: 'claude-sonnet-4-6',
paymentEnabled: true,
});
t.snapshot(model7, 'should honor requested pro model during active');
const model7WithPrefix = await capabilityPolicy.resolveChatModel({
userId,
defaultModel: 'gemini-2.5-flash',
optionalModels: [
'gemini-2.5-flash',
'gemini-2.5-pro',
'claude-sonnet-4-5@20250929',
],
proModels: ['gemini-2.5-pro', 'claude-sonnet-4-5@20250929'],
requestedModelId: 'openai-default/claude-sonnet-4-5@20250929',
defaultModel: 'gpt-5.6-luna',
optionalModels: ['gpt-5.6-luna', 'gpt-5.6-terra', 'claude-sonnet-4-6'],
proModels: ['gpt-5.6-terra', 'claude-sonnet-4-6'],
requestedModelId: 'openai-default/claude-sonnet-4-6',
paymentEnabled: true,
});
t.is(
model7WithPrefix,
'openai-default/claude-sonnet-4-5@20250929',
'openai-default/claude-sonnet-4-6',
'should honor requested prefixed pro model during active'
);
const model8 = await capabilityPolicy.resolveChatModel({
userId,
defaultModel: 'gemini-2.5-flash',
optionalModels: [
'gemini-2.5-flash',
'gemini-2.5-pro',
'claude-sonnet-4-5@20250929',
],
proModels: ['gemini-2.5-pro', 'claude-sonnet-4-5@20250929'],
defaultModel: 'gpt-5.6-luna',
optionalModels: ['gpt-5.6-luna', 'gpt-5.6-terra', 'claude-sonnet-4-6'],
proModels: ['gpt-5.6-terra', 'claude-sonnet-4-6'],
requestedModelId: 'not-in-optional',
paymentEnabled: true,
});
@@ -2684,10 +2624,10 @@ test('prompt runtime should resolve prefixed optional models consistently', asyn
const promptName = randomUUID().replaceAll('-', '');
await prompt.set(
promptName,
'gemini-2.5-flash',
'gpt-5.6-luna',
[{ role: 'user', content: '{{content}}' }],
{ proModels: ['gemini-2.5-pro'] },
{ optionalModels: ['gemini-2.5-pro'] }
{ proModels: ['gpt-5.6-terra'] },
{ optionalModels: ['gpt-5.6-terra'] }
);
const textStub = Sinon.stub(chatRuntime, 'text').resolves('ok');
@@ -2695,11 +2635,11 @@ test('prompt runtime should resolve prefixed optional models consistently', asyn
await promptRuntime.runText(
promptName,
{ content: 'hello' },
{ modelId: 'openai-default/gemini-2.5-pro' }
{ modelId: 'openai-default/gpt-5.6-terra' }
);
t.is(
textStub.firstCall.args[0].modelId,
'openai-default/gemini-2.5-pro',
'openai-default/gpt-5.6-terra',
'should preserve accepted provider-prefixed optional model'
);
@@ -2710,7 +2650,7 @@ test('prompt runtime should resolve prefixed optional models consistently', asyn
);
t.is(
textStub.secondCall.args[0].modelId,
'gemini-2.5-flash',
'gpt-5.6-luna',
'should fallback to default model for non-optional prefixed model'
);
});
@@ -2722,10 +2662,10 @@ test('resolver models should use resolved provider metadata for display names',
const promptName = randomUUID().replaceAll('-', '');
await prompt.set(
promptName,
'gemini-2.5-flash',
'gpt-5.6-luna',
[{ role: 'system', content: 'test' }],
{ proModels: ['gemini-2.5-pro'] },
{ optionalModels: ['gemini-2.5-flash', 'gemini-2.5-pro'] }
{ proModels: ['gpt-5.6-terra'] },
{ optionalModels: ['gpt-5.6-luna', 'gpt-5.6-terra'] }
);
const resolveProvider = Sinon.stub(factory, 'resolveProvider').callsFake(
@@ -2754,11 +2694,11 @@ test('resolver models should use resolved provider metadata for display names',
const models = await resolver.models(promptName);
t.deepEqual(models.optionalModels, [
{ id: 'gemini-2.5-flash', name: 'Resolved gemini-2.5-flash' },
{ id: 'gemini-2.5-pro', name: 'Resolved gemini-2.5-pro' },
{ id: 'gpt-5.6-luna', name: 'Resolved gpt-5.6-luna' },
{ id: 'gpt-5.6-terra', name: 'Resolved gpt-5.6-terra' },
]);
t.deepEqual(models.proModels, [
{ id: 'gemini-2.5-pro', name: 'Resolved gemini-2.5-pro' },
{ id: 'gpt-5.6-terra', name: 'Resolved gpt-5.6-terra' },
]);
t.true(
resolveProvider.alwaysCalledWithMatch({
@@ -362,7 +362,7 @@ class TestGeminiProvider extends GeminiProvider<{ apiKey: string }> {
structuredFactory: (request: LlmStructuredRequest) => LlmStructuredResponse =
() => ({
id: 'structured_1',
model: 'gemini-2.5-flash',
model: 'gemini-3.6-flash',
output_text: '{"summary":"AFFiNE native"}',
output_json: { summary: 'AFFiNE native' },
usage: {
@@ -1070,7 +1070,7 @@ test('buildCanonicalNativeStructuredRequest should honor explicit structured opt
test('buildCanonicalNativeStructuredRequest should honor explicit responseSchema for array outputs', async t => {
const schema = z.array(z.object({ speaker: z.string(), text: z.string() }));
const { request } = await buildCanonicalNativeStructuredRequest({
model: 'gemini-2.5-flash',
model: 'gemini-3.6-flash',
messages: jsonOnlyPromptMessages('Transcribe this audio.'),
options: {},
responseContract: buildStructuredResponseContract(schema),
@@ -1083,7 +1083,7 @@ test('buildCanonicalNativeStructuredRequest should consume explicit structured r
const schema = z.object({ summary: z.string() });
const responseContract = buildStructuredResponseContract(schema);
const { request } = await buildCanonicalNativeStructuredRequest({
model: 'gemini-2.5-flash',
model: 'gemini-3.6-flash',
messages: jsonOnlyPromptMessages('Summarize AFFiNE.'),
options: { strict: false },
responseContract,
@@ -1120,7 +1120,7 @@ test('buildNativeRequest should canonicalize Gemini attachments', async t => {
{
title: 'remote file url',
input: {
model: 'gemini-2.5-flash',
model: 'gemini-3.6-flash',
messages: [
{
role: 'user' as const,
@@ -1134,7 +1134,7 @@ test('buildNativeRequest should canonicalize Gemini attachments', async t => {
{
title: 'remote image url',
input: {
model: 'gemini-2.5-flash',
model: 'gemini-3.6-flash',
messages: [
{
role: 'user' as const,
@@ -1147,7 +1147,7 @@ test('buildNativeRequest should canonicalize Gemini attachments', async t => {
{
title: 'data url',
input: {
model: 'gemini-2.5-flash',
model: 'gemini-3.6-flash',
messages: [
{
role: 'user' as const,
@@ -1161,7 +1161,7 @@ test('buildNativeRequest should canonicalize Gemini attachments', async t => {
{
title: 'remote audio url',
input: {
model: 'gemini-2.5-flash',
model: 'gemini-3.6-flash',
messages: [
{
role: 'user' as const,
@@ -1175,7 +1175,7 @@ test('buildNativeRequest should canonicalize Gemini attachments', async t => {
{
title: 'bytes and file handle',
input: {
model: 'gemini-2.5-flash',
model: 'gemini-3.6-flash',
messages: [
{
role: 'user' as const,
@@ -1316,7 +1316,7 @@ test('buildNativeRequest should preserve tool schemas and defer Gemini rewrite t
const [{ request: geminiRequest }, { request: openaiRequest }] =
await Promise.all([
buildNativeRequest({
model: 'gemini-2.5-flash',
model: 'gemini-3.6-flash',
messages: promptMessages(userPrompt('read doc')),
toolContracts: buildToolContracts({
doc_read: defineTool({
@@ -1371,7 +1371,7 @@ test('buildNativeStructuredRequest should preserve schemas and defer Gemini rewr
const [{ request: geminiRequest }, { request: openaiRequest }] =
await Promise.all([
buildNativeStructuredRequest({
model: 'gemini-2.5-flash',
model: 'gemini-3.6-flash',
messages: promptMessages(userPrompt('Summarize AFFiNE.')),
responseContract: buildStructuredResponseContract(schema),
}),
@@ -1418,7 +1418,7 @@ test('GeminiProvider should use native path for text-only requests', async t =>
const provider = new TestGeminiProvider();
const result = await getProviderRuntimeHost(provider).run.text(
{ modelId: 'gemini-2.5-flash' },
{ modelId: 'gemini-3.6-flash' },
promptMessages(userPrompt('hello')),
{ reasoning: true }
);
@@ -1439,7 +1439,7 @@ test('GeminiProvider should use native path for structured requests', async t =>
const schema = z.object({ summary: z.string() });
const result = await getProviderRuntimeHost(provider).run.structured(
{ modelId: 'gemini-2.5-flash' },
{ modelId: 'gemini-3.6-flash' },
jsonOnlyPromptMessages('Summarize AFFiNE in one short sentence.'),
structuredOptions(schema),
structuredContract(schema)
@@ -1468,7 +1468,7 @@ test('GeminiProvider should retry when native structured dispatch returns invali
}
return {
id: `structured_retry_${attempts}`,
model: 'gemini-2.5-flash',
model: 'gemini-3.6-flash',
output_text: '{"summary":"ok"}',
output_json: { summary: 'ok' },
usage: {
@@ -1481,7 +1481,7 @@ test('GeminiProvider should retry when native structured dispatch returns invali
};
const result = await getProviderRuntimeHost(provider).run.structured(
{ modelId: 'gemini-2.5-flash' },
{ modelId: 'gemini-3.6-flash' },
jsonOnlyPromptMessages('Summarize AFFiNE in one short sentence.'),
structuredOptions(z.object({ summary: z.string() }), { maxRetries: 2 }),
structuredContract(z.object({ summary: z.string() }))
@@ -1502,7 +1502,7 @@ test('GeminiProvider should treat maxRetries as retry count for backend failures
const error = await t.throwsAsync(
getProviderRuntimeHost(provider).run.structured(
{ modelId: 'gemini-2.5-flash' },
{ modelId: 'gemini-3.6-flash' },
jsonOnlyPromptMessages('Summarize AFFiNE in one short sentence.'),
structuredOptions(z.object({ summary: z.string() }), { maxRetries: 2 }),
structuredContract(z.object({ summary: z.string() }))
@@ -1524,7 +1524,7 @@ test('GeminiProvider should use native structured path for audio attachments', a
});
provider.structuredFactory = () => ({
id: 'structured_audio_1',
model: 'gemini-2.5-flash',
model: 'gemini-3.6-flash',
output_text: '[{"a":"Speaker 1","s":0,"e":1,"t":"Hello"}]',
output_json: [{ a: 'Speaker 1', s: 0, e: 1, t: 'Hello' }],
usage: { prompt_tokens: 4, completion_tokens: 3, total_tokens: 7 },
@@ -1532,7 +1532,7 @@ test('GeminiProvider should use native structured path for audio attachments', a
});
const result = await getProviderRuntimeHost(provider).run.structured(
{ modelId: 'gemini-2.5-flash' },
{ modelId: 'gemini-3.6-flash' },
promptMessages(
systemPrompt('Return JSON only.'),
userPrompt('transcribe the audio', {
@@ -1653,7 +1653,7 @@ test('GeminiProvider should canonicalize native text attachments', async t => {
testCase.setup(provider);
const result = await getProviderRuntimeHost(provider).run.text(
{ modelId: 'gemini-2.5-flash' },
{ modelId: 'gemini-3.6-flash' },
testCase.messages
);
@@ -1677,7 +1677,7 @@ test('GeminiProvider should pass abort signal to remote attachment prefetch', as
const controller = new AbortController();
await getProviderRuntimeHost(provider).run.text(
{ modelId: 'gemini-2.5-flash' },
{ modelId: 'gemini-3.6-flash' },
[
{
role: 'user',
@@ -1701,7 +1701,7 @@ test('GeminiProvider should not pass materialized inline attachment URL to nativ
});
await getProviderRuntimeHost(provider).run.text(
{ modelId: 'gemini-2.5-flash' },
{ modelId: 'gemini-3.6-flash' },
[
{
role: 'user',
@@ -1733,7 +1733,7 @@ test('GeminiProvider should reject unsupported attachment schemes at input valid
const error = await t.throwsAsync(
getProviderRuntimeHost(provider).run.text(
{ modelId: 'gemini-2.5-flash' },
{ modelId: 'gemini-3.6-flash' },
[
{
role: 'user',
@@ -1756,7 +1756,7 @@ test('GeminiProvider should validate malformed attachments before canonicalizati
const error = await t.throwsAsync(
getProviderRuntimeHost(provider).run.text(
{ modelId: 'gemini-2.5-flash' },
{ modelId: 'gemini-3.6-flash' },
[
{
role: 'user',
@@ -1804,7 +1804,7 @@ test('GeminiProvider should drive tool loop on native path', async t => {
};
const result = await getProviderRuntimeHost(provider).run.text(
{ modelId: 'gemini-2.5-flash' },
{ modelId: 'gemini-3.6-flash' },
[{ role: 'user', content: 'read doc a1' }],
{}
);
@@ -1859,7 +1859,7 @@ test('GeminiVertexProvider should materialize remote attachments before native t
});
const result = await getProviderRuntimeHost(provider).run.text(
{ modelId: 'gemini-2.5-flash' },
{ modelId: 'gemini-3.6-flash' },
[
{
role: 'user',
@@ -1443,9 +1443,9 @@ test('checkParams should infer remote image capability from url extension withou
test('llmResolveRequestedModelMatch should preserve provider-prefixed optional matches', t => {
const request = parseRequestedModelMatchRequest({
providerIds: ['openai-default', 'gemini-default'],
defaultModel: 'gemini-2.5-flash',
optionalModels: ['gemini-2.5-flash', 'gemini-2.5-pro'],
requestedModelId: 'openai-default/gemini-2.5-pro',
defaultModel: 'gpt-5.6-luna',
optionalModels: ['gpt-5.6-luna', 'gpt-5.6-terra'],
requestedModelId: 'openai-default/gpt-5.6-terra',
});
t.snapshot(llmResolveRequestedModelMatch(request), 'prefixed optional hit');
@@ -103,7 +103,7 @@ async function buildNativeTranscriptResult(input: any, runId: string) {
openQuestions: [],
blockers: [],
},
providerMeta: { provider: 'gemini', model: 'gemini-2.5-flash' },
providerMeta: { provider: 'gemini', model: 'gemini-3.5-flash-lite' },
version: 'transcript-result-v1',
strategy: 'gemini',
},
@@ -319,7 +319,7 @@ test('retryTask reuses failed task and queues a new action attempt', async t =>
const payload = TranscriptPayloadSchema.parse({
normalizedTranscript: '00:00:05 A: Kickoff',
summaryJson: null,
providerMeta: { provider: 'gemini', model: 'gemini-2.5-flash' },
providerMeta: { provider: 'gemini', model: 'gemini-3.5-flash-lite' },
});
const service = createCopilotTranscriptionService(
{
@@ -341,7 +341,7 @@ test('retryTask reuses failed task and queues a new action attempt', async t =>
} as never,
{} as never,
{
resolveTranscriptionModel: Sinon.stub().resolves('gemini-2.5-flash'),
resolveTranscriptionModel: Sinon.stub().resolves('gemini-3.5-flash-lite'),
} as never,
{} as never,
{} as never
@@ -382,7 +382,7 @@ test('retryTask prechecks quota or BYOK before queueing provider work', async t
{ add } as never,
{} as never,
{
resolveTranscriptionModel: Sinon.stub().resolves('gemini-2.5-flash'),
resolveTranscriptionModel: Sinon.stub().resolves('gemini-3.5-flash-lite'),
} as never,
{} as never,
{} as never,
@@ -427,7 +427,9 @@ for (const status of ['ready', 'settled']) {
} as never,
{} as never,
{
resolveTranscriptionModel: Sinon.stub().resolves('gemini-2.5-flash'),
resolveTranscriptionModel: Sinon.stub().resolves(
'gemini-3.5-flash-lite'
),
} as never,
{} as never,
{} as never
@@ -453,7 +455,9 @@ for (const status of ['ready', 'settled']) {
test('submitTask prechecks quota or BYOK before persisting uploads', async t => {
const assertQuotaOrByok = Sinon.stub().rejects(new Error('quota exceeded'));
const resolveTranscriptionModel = Sinon.stub().resolves('gemini-2.5-flash');
const resolveTranscriptionModel = Sinon.stub().resolves(
'gemini-3.5-flash-lite'
);
const service = createCopilotTranscriptionService(
{
copilotTranscriptTask: {
@@ -492,7 +496,7 @@ test('submitTask rejects unavailable transcript strategy', async t => {
{} as never,
{} as never,
{
resolveTranscriptionModel: Sinon.stub().resolves('gemini-2.5-flash'),
resolveTranscriptionModel: Sinon.stub().resolves('gemini-3.5-flash-lite'),
} as never,
{} as never,
{} as never
@@ -555,7 +559,7 @@ test('transcriptTask runs native transcript recipe through action bridge when av
await service.transcriptTask({
taskId: 'task-1',
payload,
modelId: 'gemini-2.5-flash',
modelId: 'gemini-3.5-flash-lite',
});
t.like(bridgeInputs[0] as Record<string, unknown>, {
@@ -567,7 +571,7 @@ test('transcriptTask runs native transcript recipe through action bridge when av
.prepareStructuredRoutes,
{
stepId: 'transcribe',
modelId: 'gemini-2.5-flash',
modelId: 'gemini-3.5-flash-lite',
}
);
const messages = (
@@ -641,7 +645,7 @@ test('transcriptTask fails task when native action bridge reports an error event
service.transcriptTask({
taskId: 'task-1',
payload,
modelId: 'gemini-2.5-flash',
modelId: 'gemini-3.5-flash-lite',
}),
{ message: /native_failed/ }
);