Skip to content

Commit c329c13

Browse files
Simplify and clarify data collection setting enforcement (#3589)
* Simplify and clarify data collection setting enforcement * test(ai-gateway): update tests for data collection rename --------- Co-authored-by: kiloconnect[bot] <240665456+kiloconnect[bot]@users.noreply.github.com>
1 parent 7c9e510 commit c329c13

5 files changed

Lines changed: 56 additions & 18 deletions

File tree

apps/web/src/app/api/openrouter/[...path]/route.ts

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import {
3030
isExcludedForFeature,
3131
isKiloExclusiveFreeModel,
3232
isKiloStealthModel,
33-
requiresKiloDataCollection,
33+
isKiloExclusiveModelRequiringDataCollection,
3434
} from '@/lib/ai-gateway/models';
3535
import { isFreeModel } from '@/lib/ai-gateway/is-free-model';
3636
import {
@@ -58,7 +58,7 @@ import {
5858
import { ProxyErrorType } from '@/lib/proxy-error-types';
5959
import { getBalanceAndOrgSettings } from '@/lib/organizations/organization-usage';
6060
import { repairTools, sanitizeBinaryToolResults } from '@/lib/ai-gateway/tool-calling';
61-
import { isFreePromptTrainingAllowed } from '@/lib/ai-gateway/providers/openrouter/types';
61+
import { isDataCollectionExplicitlyDisallowed } from '@/lib/ai-gateway/providers/openrouter/types';
6262
import {
6363
rewriteFreeModelResponse_ChatCompletions,
6464
rewriteFreeModelResponse_Messages,
@@ -419,7 +419,10 @@ export async function POST(request: NextRequest): Promise<NextResponseType<unkno
419419
// for partner evaluation — which violates the caller's stated
420420
// intent. Refuse here regardless of org settings, anon/BYOK status,
421421
// or the org-level check below.
422-
if (experiment && !isFreePromptTrainingAllowed(requestBodyParsed.body.provider)) {
422+
if (
423+
(experiment || isKiloExclusiveModelRequiringDataCollection(originalModelIdLowerCased)) &&
424+
isDataCollectionExplicitlyDisallowed(requestBodyParsed.body.provider)
425+
) {
423426
return dataCollectionRequiredResponse();
424427
}
425428

@@ -554,13 +557,6 @@ export async function POST(request: NextRequest): Promise<NextResponseType<unkno
554557
op: 'http.client',
555558
});
556559

557-
if (
558-
requiresKiloDataCollection(originalModelIdLowerCased) &&
559-
!isFreePromptTrainingAllowed(requestBodyParsed.body.provider)
560-
) {
561-
return dataCollectionRequiredResponse();
562-
}
563-
564560
applyTrackingIds(requestBodyParsed, provider, user.id, taskId ?? null);
565561

566562
sanitizeBinaryToolResults(requestBodyParsed);

apps/web/src/lib/ai-gateway/models.test.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import {
33
autoFreeModels,
44
findKiloExclusiveModel,
55
kiloExclusiveModels,
6-
requiresKiloDataCollection,
6+
isKiloExclusiveModelRequiringDataCollection,
77
} from './models';
88
import { isFreeModel } from './is-free-model';
99
import { getInferenceProvider } from './providers/kilo-exclusive-model';
@@ -80,10 +80,16 @@ describe('isFreeModel', () => {
8080
});
8181

8282
test('requires data collection for paid training-enabled offerings', () => {
83-
expect(requiresKiloDataCollection(claude_opus_4_7_stealth_model.public_id)).toBe(true);
84-
expect(requiresKiloDataCollection(claude_sonnet_4_6_stealth_model.public_id)).toBe(true);
85-
expect(requiresKiloDataCollection(claude_opus_4_6_stealth_model.public_id)).toBe(true);
86-
expect(requiresKiloDataCollection(qwen36_plus_model.public_id)).toBe(false);
83+
expect(
84+
isKiloExclusiveModelRequiringDataCollection(claude_opus_4_7_stealth_model.public_id)
85+
).toBe(true);
86+
expect(
87+
isKiloExclusiveModelRequiringDataCollection(claude_sonnet_4_6_stealth_model.public_id)
88+
).toBe(true);
89+
expect(
90+
isKiloExclusiveModelRequiringDataCollection(claude_opus_4_6_stealth_model.public_id)
91+
).toBe(true);
92+
expect(isKiloExclusiveModelRequiringDataCollection(qwen36_plus_model.public_id)).toBe(false);
8793
});
8894

8995
test('all Kilo exclusive models should have either no pricing or valid pricing', () => {

apps/web/src/lib/ai-gateway/models.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ export const kiloExclusiveModels = [
9999
stepfun_37_flash_free_model,
100100
] as KiloExclusiveModel[];
101101

102-
export function requiresKiloDataCollection(model: string): boolean {
102+
export function isKiloExclusiveModelRequiringDataCollection(model: string): boolean {
103103
return kiloExclusiveModels.some(
104104
m =>
105105
m.public_id === model &&
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { describe, it, expect } from '@jest/globals';
2+
import { isDataCollectionExplicitlyDisallowed } from './types';
3+
4+
describe('isDataCollectionExplicitlyDisallowed', () => {
5+
it('returns false when no provider config is given', () => {
6+
expect(isDataCollectionExplicitlyDisallowed(undefined)).toBe(false);
7+
});
8+
9+
it('returns false for an empty provider config', () => {
10+
expect(isDataCollectionExplicitlyDisallowed({})).toBe(false);
11+
});
12+
13+
it('returns false when data collection is explicitly allowed', () => {
14+
expect(isDataCollectionExplicitlyDisallowed({ data_collection: 'allow' })).toBe(false);
15+
});
16+
17+
it('returns true when data collection is denied', () => {
18+
expect(isDataCollectionExplicitlyDisallowed({ data_collection: 'deny' })).toBe(true);
19+
});
20+
21+
it('returns true when zero data retention is requested', () => {
22+
expect(isDataCollectionExplicitlyDisallowed({ zdr: true })).toBe(true);
23+
});
24+
25+
it('returns false when zero data retention is explicitly disabled', () => {
26+
expect(isDataCollectionExplicitlyDisallowed({ zdr: false })).toBe(false);
27+
});
28+
29+
it('returns true when zdr is set even though data collection is allowed', () => {
30+
expect(isDataCollectionExplicitlyDisallowed({ data_collection: 'allow', zdr: true })).toBe(
31+
true
32+
);
33+
});
34+
});

apps/web/src/lib/ai-gateway/providers/openrouter/types.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,10 @@ export type VercelProviderConfig = {
2525
anthropic?: AnthropicProviderOptions;
2626
};
2727

28-
export function isFreePromptTrainingAllowed(provider: OpenRouterProviderConfig | undefined) {
29-
return provider?.data_collection !== 'deny' && !provider?.zdr;
28+
export function isDataCollectionExplicitlyDisallowed(
29+
provider: OpenRouterProviderConfig | undefined
30+
) {
31+
return provider?.data_collection === 'deny' || provider?.zdr === true;
3032
}
3133

3234
export type OpenRouterReasoningConfig = {

0 commit comments

Comments
 (0)