Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,13 @@
- Run `bun run test:patch-branches --base "$(git merge-base HEAD origin/main)"` after coverage; changed branch coverage must be at least 90%.
- Confirm Codecov's `patch` status is successful and meets the target configured in `codecov.yml`; do not lower the patch target or threshold to bypass a coverage failure.
- Use the Codecov PR report as the source of truth for patch coverage because it compares the uploaded `lcov.info` against the PR base commit.

<!-- BEGIN:nextjs-agent-rules -->

# This is NOT the Next.js you know

This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` (resolved from this file's directory; in monorepos the `next` package may not be visible from the repo root) before writing any code. Heed deprecation notices.

This block is written and re-added by `next dev` — verify at `node_modules/next/dist/server/lib/generate-agent-files.js`. Removing it from a diff only re-creates the uncommitted change; committing it with your work keeps the tree clean.

<!-- END:nextjs-agent-rules -->
42 changes: 30 additions & 12 deletions app/credentials/credential-card.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Avatar, Block, Flexbox, Tag } from '@lobehub/ui';
import { Button } from '@lobehub/ui/base-ui';
import { Button, Select } from '@lobehub/ui/base-ui';
import {
CalendarDays,
Clock3,
Expand All @@ -24,7 +24,7 @@ interface CredentialCardProps {
current: CurrentCredentialInfo | null;
form: CredentialFormState;
onCredentialFirstMessageRoleToSystemChange: (value: boolean) => void;
onCredentialResponsesPassthroughChange: (value: boolean) => void;
onCredentialUpstreamProtocolChange: (value: 'chat' | 'responses') => void;
onDelete: () => void;
onEdit: () => void;
onResetCredentialForm: () => void;
Expand All @@ -36,7 +36,7 @@ export const CredentialCard = ({
current,
form,
onCredentialFirstMessageRoleToSystemChange,
onCredentialResponsesPassthroughChange,
onCredentialUpstreamProtocolChange,
onDelete,
onEdit,
onResetCredentialForm,
Expand Down Expand Up @@ -104,9 +104,9 @@ export const CredentialCard = ({
</div>
<div className="credential-card-routing-tags flex flex-wrap gap-2 py-2">
<Tag>
{credential.responses_passthrough
? text('credentials.credentialResponsesDirect')
: text('credentials.credentialResponsesProxyTag')}
{credential.upstream_protocol === 'responses'
? text('credentials.credentialUpstreamResponsesTag')
: text('credentials.credentialUpstreamChatTag')}
</Tag>
<Tag>
{credential.first_message_role_to_system
Expand All @@ -130,12 +130,30 @@ export const CredentialCard = ({
{text('credentials.credentialEditTitle')}
</div>
<div className="mb-4 grid gap-3">
<ToggleOption
checked={form.responsesPassthrough}
description={text('credentials.credentialResponsesDirectHelp')}
onChange={onCredentialResponsesPassthroughChange}
title={text('credentials.credentialResponsesDirect')}
/>
<label className="flex flex-col gap-1 text-sm text-secondary">
<span>{text('credentials.credentialUpstreamProtocol')}</span>
<Select
aria-label={text('credentials.credentialUpstreamProtocol')}
className="w-full"
onChange={(value) =>
onCredentialUpstreamProtocolChange(
value === 'responses' ? 'responses' : 'chat',
)
}
options={[
{
label: text('credentials.credentialUpstreamChat'),
value: 'chat',
},
{
label: text('credentials.credentialUpstreamResponses'),
value: 'responses',
},
]}
value={form.upstreamProtocol}
/>
<span>{text('credentials.credentialUpstreamProtocolHelp')}</span>
</label>
<ToggleOption
checked={form.firstMessageRoleToSystem}
description={text('credentials.credentialRoleAsSystemHelp')}
Expand Down
8 changes: 4 additions & 4 deletions app/credentials/credential-group.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ interface CredentialGroupProps {
form: CredentialFormState;
items: CredentialSummary[];
onCredentialFirstMessageRoleToSystemChange: (value: boolean) => void;
onCredentialResponsesPassthroughChange: (value: boolean) => void;
onCredentialUpstreamProtocolChange: (value: 'chat' | 'responses') => void;
onDelete: (index: number) => void;
onEdit: (credential: CredentialSummary) => void;
onResetCredentialForm: () => void;
Expand All @@ -27,7 +27,7 @@ export const CredentialGroup = ({
form,
items,
onCredentialFirstMessageRoleToSystemChange,
onCredentialResponsesPassthroughChange,
onCredentialUpstreamProtocolChange,
onDelete,
onEdit,
onResetCredentialForm,
Expand All @@ -47,8 +47,8 @@ export const CredentialGroup = ({
onCredentialFirstMessageRoleToSystemChange={
onCredentialFirstMessageRoleToSystemChange
}
onCredentialResponsesPassthroughChange={
onCredentialResponsesPassthroughChange
onCredentialUpstreamProtocolChange={
onCredentialUpstreamProtocolChange
}
onDelete={() => onDelete(credential.index)}
onEdit={() => onEdit(credential)}
Expand Down
61 changes: 44 additions & 17 deletions app/credentials/credentials.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { atom } from 'jotai';
import { createContext, useContext, useState } from 'react';
import { Block, Flexbox, Input, TextArea } from '@lobehub/ui';
import { Button } from '@lobehub/ui/base-ui';
import { Button, Select } from '@lobehub/ui/base-ui';
import {
Copy,
ExternalLink,
Expand Down Expand Up @@ -40,6 +40,7 @@ export interface CredentialSummary {
is_expired: boolean;
name: string | null;
responses_passthrough: boolean;
upstream_protocol: 'chat' | 'responses';
scope: string | null;
session_state: string | null;
tenant_id: string | number | null;
Expand Down Expand Up @@ -92,7 +93,7 @@ export interface CredentialFormState {
bearerToken: string;
editingIndex: number | null;
firstMessageRoleToSystem: boolean;
responsesPassthrough: boolean;
upstreamProtocol: 'chat' | 'responses';
userId: string;
}

Expand Down Expand Up @@ -146,7 +147,7 @@ export const defaultCredentialsState: CredentialsState = {
bearerToken: '',
editingIndex: null,
firstMessageRoleToSystem: false,
responsesPassthrough: false,
upstreamProtocol: 'chat',
userId: '',
},
items: [],
Expand Down Expand Up @@ -187,7 +188,7 @@ export interface CredentialsTabController {
onCallbackUrlChange: (value: string) => void;
onCopyAuthUrl: () => void;
onCredentialFirstMessageRoleToSystemChange: (value: boolean) => void;
onCredentialResponsesPassthroughChange: (value: boolean) => void;
onCredentialUpstreamProtocolChange: (value: 'chat' | 'responses') => void;
onCredentialTokenChange: (value: string) => void;
onCredentialUserIdChange: (value: string) => void;
onDeleteCredential: (index: number) => void;
Expand Down Expand Up @@ -234,7 +235,7 @@ const Credentials = () => {
onCallbackUrlChange,
onCopyAuthUrl,
onCredentialFirstMessageRoleToSystemChange,
onCredentialResponsesPassthroughChange,
onCredentialUpstreamProtocolChange,
onCredentialTokenChange,
onCredentialUserIdChange,
onDeleteAccessKey,
Expand Down Expand Up @@ -452,14 +453,40 @@ const Credentials = () => {
/>
</div>
<div className="mb-4 grid gap-3">
<ToggleOption
checked={credentials.form.responsesPassthrough}
description={credentialsText(
'credentials.credentialResponsesDirectHelp',
)}
onChange={onCredentialResponsesPassthroughChange}
title={credentialsText('credentials.credentialResponsesDirect')}
/>
<label className="flex flex-col gap-1 text-sm text-secondary">
<span>
{credentialsText('credentials.credentialUpstreamProtocol')}
</span>
<Select
aria-label={credentialsText(
'credentials.credentialUpstreamProtocol',
)}
className="w-full"
onChange={(value) =>
onCredentialUpstreamProtocolChange(
value === 'responses' ? 'responses' : 'chat',
)
}
options={[
{
label: credentialsText(
'credentials.credentialUpstreamChat',
),
value: 'chat',
},
{
label: credentialsText(
'credentials.credentialUpstreamResponses',
),
value: 'responses',
},
]}
value={credentials.form.upstreamProtocol}
/>
<span>
{credentialsText('credentials.credentialUpstreamProtocolHelp')}
</span>
</label>
<ToggleOption
checked={credentials.form.firstMessageRoleToSystem}
description={credentialsText(
Expand Down Expand Up @@ -593,8 +620,8 @@ const Credentials = () => {
onCredentialFirstMessageRoleToSystemChange={
onCredentialFirstMessageRoleToSystemChange
}
onCredentialResponsesPassthroughChange={
onCredentialResponsesPassthroughChange
onCredentialUpstreamProtocolChange={
onCredentialUpstreamProtocolChange
}
onDelete={onDeleteCredential}
onEdit={onEditCredential}
Expand All @@ -608,8 +635,8 @@ const Credentials = () => {
onCredentialFirstMessageRoleToSystemChange={
onCredentialFirstMessageRoleToSystemChange
}
onCredentialResponsesPassthroughChange={
onCredentialResponsesPassthroughChange
onCredentialUpstreamProtocolChange={
onCredentialUpstreamProtocolChange
}
onDelete={onDeleteCredential}
onEdit={onEditCredential}
Expand Down
14 changes: 7 additions & 7 deletions app/page-shell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -984,14 +984,14 @@ const AdminPageLayoutContent = ({
index: credentials.form.editingIndex,
first_message_role_to_system:
credentials.form.firstMessageRoleToSystem,
responses_passthrough: credentials.form.responsesPassthrough,
upstream_protocol: credentials.form.upstreamProtocol,
}
: {
access_token: credentials.form.bearerToken.trim(),
bearer_token: credentials.form.bearerToken.trim(),
first_message_role_to_system:
credentials.form.firstMessageRoleToSystem,
responses_passthrough: credentials.form.responsesPassthrough,
upstream_protocol: credentials.form.upstreamProtocol,
user_id: credentials.form.userId.trim() || undefined,
},
),
Expand Down Expand Up @@ -1021,7 +1021,7 @@ const AdminPageLayoutContent = ({
bearerToken: '',
editingIndex: null,
firstMessageRoleToSystem: false,
responsesPassthrough: false,
upstreamProtocol: 'chat',
userId: '',
},
}));
Expand Down Expand Up @@ -1736,12 +1736,12 @@ const AdminPageLayoutContent = ({
},
}));
},
onCredentialResponsesPassthroughChange: (value) => {
onCredentialUpstreamProtocolChange: (value) => {
setCredentials((current) => ({
...current,
form: {
...current.form,
responsesPassthrough: value,
upstreamProtocol: value,
},
}));
},
Expand Down Expand Up @@ -1774,7 +1774,7 @@ const AdminPageLayoutContent = ({
editingIndex: credential.index,
firstMessageRoleToSystem:
credential.first_message_role_to_system,
responsesPassthrough: credential.responses_passthrough,
upstreamProtocol: credential.upstream_protocol,
userId: credential.user_id ?? '',
},
}));
Expand Down Expand Up @@ -1827,7 +1827,7 @@ const AdminPageLayoutContent = ({
bearerToken: '',
editingIndex: null,
firstMessageRoleToSystem: false,
responsesPassthrough: false,
upstreamProtocol: 'chat',
userId: '',
},
}));
Expand Down
1 change: 1 addition & 0 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

33 changes: 27 additions & 6 deletions lib/server/domain/credentials.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export type CredentialData = Record<string, unknown> & {
user_id?: string;
user_info?: Record<string, unknown>;
responses_passthrough?: boolean;
upstream_protocol?: 'chat' | 'responses';
first_message_role_to_system?: boolean;
supported_models?: string;
};
Expand Down Expand Up @@ -473,6 +474,8 @@ export const listCredentials = async (): Promise<{
responses_passthrough: getBooleanSetting(
record.data.responses_passthrough,
),
upstream_protocol: getCredentialProxySettings(record.data)
.upstreamProtocol,
first_message_role_to_system: getBooleanSetting(
record.data.first_message_role_to_system,
),
Expand Down Expand Up @@ -559,17 +562,30 @@ export const addCredential = async (
existingPayload = storedPayload;
}

const upstreamProtocol =
credentialData.upstream_protocol === 'responses'
? 'responses'
: credentialData.upstream_protocol === 'chat'
? 'chat'
: credentialData.responses_passthrough !== undefined
? getBooleanSetting(credentialData.responses_passthrough)
? 'responses'
: 'chat'
: existingPayload.upstream_protocol === 'responses'
? 'responses'
: getBooleanSetting(existingPayload.responses_passthrough)
? 'responses'
: 'chat';

const payload = {
...existingPayload,
...credentialData,
created_at:
typeof existingPayload.created_at === 'number'
? existingPayload.created_at
: now,
responses_passthrough: getBooleanSetting(
credentialData.responses_passthrough ??
existingPayload.responses_passthrough,
),
responses_passthrough: upstreamProtocol === 'responses',
upstream_protocol: upstreamProtocol,
first_message_role_to_system: getBooleanSetting(
credentialData.first_message_role_to_system ??
existingPayload.first_message_role_to_system,
Expand Down Expand Up @@ -779,12 +795,17 @@ export const getCredentialProxySettings = (
credential: CredentialData | null | undefined,
): {
firstMessageRoleToSystem: boolean;
responsesPassthrough: boolean;
upstreamProtocol: 'chat' | 'responses';
} => {
return {
firstMessageRoleToSystem: getBooleanSetting(
credential?.first_message_role_to_system,
),
responsesPassthrough: getBooleanSetting(credential?.responses_passthrough),
upstreamProtocol:
credential?.upstream_protocol === 'responses' ||
(credential?.upstream_protocol !== 'chat' &&
getBooleanSetting(credential?.responses_passthrough))
? 'responses'
: 'chat',
};
};
Loading
Loading