Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
C
canifa_note
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Vũ Hoàng Anh
canifa_note
Commits
0ad0fec8
Commit
0ad0fec8
authored
Apr 12, 2026
by
boojack
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat(ai): add Anthropic provider option
parent
d87539a1
Changes
12
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
106 additions
and
6 deletions
+106
-6
ai.go
internal/ai/ai.go
+2
-0
instance_service.proto
proto/api/v1/instance_service.proto
+1
-0
instance_service.pb.go
proto/gen/api/v1/instance_service.pb.go
+7
-3
openapi.yaml
proto/gen/openapi.yaml
+1
-0
instance_setting.pb.go
proto/gen/store/instance_setting.pb.go
+6
-2
instance_setting.proto
proto/store/instance_setting.proto
+1
-0
ai_service.go
server/router/api/v1/ai_service.go
+2
-0
instance_service.go
server/router/api/v1/instance_service.go
+3
-0
ai_service_test.go
server/router/api/v1/test/ai_service_test.go
+41
-0
instance_service_test.go
server/router/api/v1/test/instance_service_test.go
+35
-0
AISection.tsx
web/src/components/Settings/AISection.tsx
+1
-0
instance_service_pb.ts
web/src/types/proto/api/v1/instance_service_pb.ts
+6
-1
No files found.
internal/ai/ai.go
View file @
0ad0fec8
...
@@ -10,6 +10,8 @@ const (
...
@@ -10,6 +10,8 @@ const (
ProviderOpenAICompatible
ProviderType
=
"OPENAI_COMPATIBLE"
ProviderOpenAICompatible
ProviderType
=
"OPENAI_COMPATIBLE"
// ProviderGemini is Google's Gemini API.
// ProviderGemini is Google's Gemini API.
ProviderGemini
ProviderType
=
"GEMINI"
ProviderGemini
ProviderType
=
"GEMINI"
// ProviderAnthropic is Anthropic's API.
ProviderAnthropic
ProviderType
=
"ANTHROPIC"
)
)
// ProviderConfig configures a callable AI provider connection.
// ProviderConfig configures a callable AI provider connection.
...
...
proto/api/v1/instance_service.proto
View file @
0ad0fec8
...
@@ -233,6 +233,7 @@ message InstanceSetting {
...
@@ -233,6 +233,7 @@ message InstanceSetting {
OPENAI
=
1
;
OPENAI
=
1
;
OPENAI_COMPATIBLE
=
2
;
OPENAI_COMPATIBLE
=
2
;
GEMINI
=
3
;
GEMINI
=
3
;
ANTHROPIC
=
4
;
}
}
}
}
...
...
proto/gen/api/v1/instance_service.pb.go
View file @
0ad0fec8
...
@@ -100,6 +100,7 @@ const (
...
@@ -100,6 +100,7 @@ const (
InstanceSetting_OPENAI
InstanceSetting_AIProviderType
=
1
InstanceSetting_OPENAI
InstanceSetting_AIProviderType
=
1
InstanceSetting_OPENAI_COMPATIBLE
InstanceSetting_AIProviderType
=
2
InstanceSetting_OPENAI_COMPATIBLE
InstanceSetting_AIProviderType
=
2
InstanceSetting_GEMINI
InstanceSetting_AIProviderType
=
3
InstanceSetting_GEMINI
InstanceSetting_AIProviderType
=
3
InstanceSetting_ANTHROPIC
InstanceSetting_AIProviderType
=
4
)
)
// Enum value maps for InstanceSetting_AIProviderType.
// Enum value maps for InstanceSetting_AIProviderType.
...
@@ -109,12 +110,14 @@ var (
...
@@ -109,12 +110,14 @@ var (
1
:
"OPENAI"
,
1
:
"OPENAI"
,
2
:
"OPENAI_COMPATIBLE"
,
2
:
"OPENAI_COMPATIBLE"
,
3
:
"GEMINI"
,
3
:
"GEMINI"
,
4
:
"ANTHROPIC"
,
}
}
InstanceSetting_AIProviderType_value
=
map
[
string
]
int32
{
InstanceSetting_AIProviderType_value
=
map
[
string
]
int32
{
"AI_PROVIDER_TYPE_UNSPECIFIED"
:
0
,
"AI_PROVIDER_TYPE_UNSPECIFIED"
:
0
,
"OPENAI"
:
1
,
"OPENAI"
:
1
,
"OPENAI_COMPATIBLE"
:
2
,
"OPENAI_COMPATIBLE"
:
2
,
"GEMINI"
:
3
,
"GEMINI"
:
3
,
"ANTHROPIC"
:
4
,
}
}
)
)
...
@@ -1411,7 +1414,7 @@ const file_api_v1_instance_service_proto_rawDesc = "" +
...
@@ -1411,7 +1414,7 @@ const file_api_v1_instance_service_proto_rawDesc = "" +
"
\x04
demo
\x18\x03
\x01
(
\b
R
\x04
demo
\x12
!
\n
"
+
"
\x04
demo
\x18\x03
\x01
(
\b
R
\x04
demo
\x12
!
\n
"
+
"
\f
instance_url
\x18\x06
\x01
(
\t
R
\v
instanceUrl
\x12
(
\n
"
+
"
\f
instance_url
\x18\x06
\x01
(
\t
R
\v
instanceUrl
\x12
(
\n
"
+
"
\x05
admin
\x18\a
\x01
(
\v
2
\x12
.memos.api.v1.UserR
\x05
admin
\"\x1b\n
"
+
"
\x05
admin
\x18\a
\x01
(
\v
2
\x12
.memos.api.v1.UserR
\x05
admin
\"\x1b\n
"
+
"
\x19
GetInstanceProfileRequest
\"\x
d3
\x1a\n
"
+
"
\x19
GetInstanceProfileRequest
\"\x
e2
\x1a\n
"
+
"
\x0f
InstanceSetting
\x12\x17\n
"
+
"
\x0f
InstanceSetting
\x12\x17\n
"
+
"
\x04
name
\x18\x01
\x01
(
\t
B
\x03\xe0
A
\b
R
\x04
name
\x12
W
\n
"
+
"
\x04
name
\x18\x01
\x01
(
\t
B
\x03\xe0
A
\b
R
\x04
name
\x12
W
\n
"
+
"
\x0f
general_setting
\x18\x02
\x01
(
\v
2,.memos.api.v1.InstanceSetting.GeneralSettingH
\x00
R
\x0e
generalSetting
\x12
W
\n
"
+
"
\x0f
general_setting
\x18\x02
\x01
(
\v
2,.memos.api.v1.InstanceSetting.GeneralSettingH
\x00
R
\x0e
generalSetting
\x12
W
\n
"
+
...
@@ -1499,14 +1502,15 @@ const file_api_v1_instance_service_proto_rawDesc = "" +
...
@@ -1499,14 +1502,15 @@ const file_api_v1_instance_service_proto_rawDesc = "" +
"
\f
MEMO_RELATED
\x10\x03\x12\b\n
"
+
"
\f
MEMO_RELATED
\x10\x03\x12\b\n
"
+
"
\x04
TAGS
\x10\x04\x12\x10\n
"
+
"
\x04
TAGS
\x10\x04\x12\x10\n
"
+
"
\f
NOTIFICATION
\x10\x05\x12\x06\n
"
+
"
\f
NOTIFICATION
\x10\x05\x12\x06\n
"
+
"
\x02
AI
\x10\x06\"
a
\n
"
+
"
\x02
AI
\x10\x06\"
p
\n
"
+
"
\x0e
AIProviderType
\x12
\n
"
+
"
\x0e
AIProviderType
\x12
\n
"
+
"
\x1c
AI_PROVIDER_TYPE_UNSPECIFIED
\x10\x00\x12\n
"
+
"
\x1c
AI_PROVIDER_TYPE_UNSPECIFIED
\x10\x00\x12\n
"
+
"
\n
"
+
"
\n
"
+
"
\x06
OPENAI
\x10\x01\x12\x15\n
"
+
"
\x06
OPENAI
\x10\x01\x12\x15\n
"
+
"
\x11
OPENAI_COMPATIBLE
\x10\x02\x12\n
"
+
"
\x11
OPENAI_COMPATIBLE
\x10\x02\x12\n
"
+
"
\n
"
+
"
\n
"
+
"
\x06
GEMINI
\x10\x03
:a
\xea
A^
\n
"
+
"
\x06
GEMINI
\x10\x03\x12\r\n
"
+
"
\t
ANTHROPIC
\x10\x04
:a
\xea
A^
\n
"
+
"
\x1c
memos.api.v1/InstanceSetting
\x12\x1b
instance/settings/{setting}*
\x10
instanceSettings2
\x0f
instanceSettingB
\a\n
"
+
"
\x1c
memos.api.v1/InstanceSetting
\x12\x1b
instance/settings/{setting}*
\x10
instanceSettings2
\x0f
instanceSettingB
\a\n
"
+
"
\x05
value
\"
U
\n
"
+
"
\x05
value
\"
U
\n
"
+
"
\x19
GetInstanceSettingRequest
\x12
8
\n
"
+
"
\x19
GetInstanceSettingRequest
\x12
8
\n
"
+
...
...
proto/gen/openapi.yaml
View file @
0ad0fec8
...
@@ -2421,6 +2421,7 @@ components:
...
@@ -2421,6 +2421,7 @@ components:
-
OPENAI
-
OPENAI
-
OPENAI_COMPATIBLE
-
OPENAI_COMPATIBLE
-
GEMINI
-
GEMINI
-
ANTHROPIC
type
:
string
type
:
string
format
:
enum
format
:
enum
endpoint
:
endpoint
:
...
...
proto/gen/store/instance_setting.pb.go
View file @
0ad0fec8
...
@@ -100,6 +100,7 @@ const (
...
@@ -100,6 +100,7 @@ const (
AIProviderType_OPENAI
AIProviderType
=
1
AIProviderType_OPENAI
AIProviderType
=
1
AIProviderType_OPENAI_COMPATIBLE
AIProviderType
=
2
AIProviderType_OPENAI_COMPATIBLE
AIProviderType
=
2
AIProviderType_GEMINI
AIProviderType
=
3
AIProviderType_GEMINI
AIProviderType
=
3
AIProviderType_ANTHROPIC
AIProviderType
=
4
)
)
// Enum value maps for AIProviderType.
// Enum value maps for AIProviderType.
...
@@ -109,12 +110,14 @@ var (
...
@@ -109,12 +110,14 @@ var (
1
:
"OPENAI"
,
1
:
"OPENAI"
,
2
:
"OPENAI_COMPATIBLE"
,
2
:
"OPENAI_COMPATIBLE"
,
3
:
"GEMINI"
,
3
:
"GEMINI"
,
4
:
"ANTHROPIC"
,
}
}
AIProviderType_value
=
map
[
string
]
int32
{
AIProviderType_value
=
map
[
string
]
int32
{
"AI_PROVIDER_TYPE_UNSPECIFIED"
:
0
,
"AI_PROVIDER_TYPE_UNSPECIFIED"
:
0
,
"OPENAI"
:
1
,
"OPENAI"
:
1
,
"OPENAI_COMPATIBLE"
:
2
,
"OPENAI_COMPATIBLE"
:
2
,
"GEMINI"
:
3
,
"GEMINI"
:
3
,
"ANTHROPIC"
:
4
,
}
}
)
)
...
@@ -1321,14 +1324,15 @@ const file_store_instance_setting_proto_rawDesc = "" +
...
@@ -1321,14 +1324,15 @@ const file_store_instance_setting_proto_rawDesc = "" +
"
\f
MEMO_RELATED
\x10\x04\x12\b\n
"
+
"
\f
MEMO_RELATED
\x10\x04\x12\b\n
"
+
"
\x04
TAGS
\x10\x05\x12\x10\n
"
+
"
\x04
TAGS
\x10\x05\x12\x10\n
"
+
"
\f
NOTIFICATION
\x10\x06\x12\x06\n
"
+
"
\f
NOTIFICATION
\x10\x06\x12\x06\n
"
+
"
\x02
AI
\x10\a
*
a
\n
"
+
"
\x02
AI
\x10\a
*
p
\n
"
+
"
\x0e
AIProviderType
\x12
\n
"
+
"
\x0e
AIProviderType
\x12
\n
"
+
"
\x1c
AI_PROVIDER_TYPE_UNSPECIFIED
\x10\x00\x12\n
"
+
"
\x1c
AI_PROVIDER_TYPE_UNSPECIFIED
\x10\x00\x12\n
"
+
"
\n
"
+
"
\n
"
+
"
\x06
OPENAI
\x10\x01\x12\x15\n
"
+
"
\x06
OPENAI
\x10\x01\x12\x15\n
"
+
"
\x11
OPENAI_COMPATIBLE
\x10\x02\x12\n
"
+
"
\x11
OPENAI_COMPATIBLE
\x10\x02\x12\n
"
+
"
\n
"
+
"
\n
"
+
"
\x06
GEMINI
\x10\x03
B
\x9f\x01\n
"
+
"
\x06
GEMINI
\x10\x03\x12\r\n
"
+
"
\t
ANTHROPIC
\x10\x04
B
\x9f\x01\n
"
+
"
\x0f
com.memos.storeB
\x14
InstanceSettingProtoP
\x01
Z)github.com/usememos/memos/proto/gen/store
\xa2\x02\x03
MSX
\xaa\x02\v
Memos.Store
\xca\x02\v
Memos
\\
Store
\xe2\x02\x17
Memos
\\
Store
\\
GPBMetadata
\xea\x02\f
Memos::Storeb
\x06
proto3"
"
\x0f
com.memos.storeB
\x14
InstanceSettingProtoP
\x01
Z)github.com/usememos/memos/proto/gen/store
\xa2\x02\x03
MSX
\xaa\x02\v
Memos.Store
\xca\x02\v
Memos
\\
Store
\xe2\x02\x17
Memos
\\
Store
\\
GPBMetadata
\xea\x02\f
Memos::Storeb
\x06
proto3"
var
(
var
(
...
...
proto/store/instance_setting.proto
View file @
0ad0fec8
...
@@ -167,4 +167,5 @@ enum AIProviderType {
...
@@ -167,4 +167,5 @@ enum AIProviderType {
OPENAI
=
1
;
OPENAI
=
1
;
OPENAI_COMPATIBLE
=
2
;
OPENAI_COMPATIBLE
=
2
;
GEMINI
=
3
;
GEMINI
=
3
;
ANTHROPIC
=
4
;
}
}
server/router/api/v1/ai_service.go
View file @
0ad0fec8
...
@@ -170,6 +170,8 @@ func convertAIProviderTypeFromStore(providerType storepb.AIProviderType) ai.Prov
...
@@ -170,6 +170,8 @@ func convertAIProviderTypeFromStore(providerType storepb.AIProviderType) ai.Prov
return
ai
.
ProviderOpenAICompatible
return
ai
.
ProviderOpenAICompatible
case
storepb
.
AIProviderType_GEMINI
:
case
storepb
.
AIProviderType_GEMINI
:
return
ai
.
ProviderGemini
return
ai
.
ProviderGemini
case
storepb
.
AIProviderType_ANTHROPIC
:
return
ai
.
ProviderAnthropic
default
:
default
:
return
""
return
""
}
}
...
...
server/router/api/v1/instance_service.go
View file @
0ad0fec8
...
@@ -523,6 +523,9 @@ func (s *APIV1Service) prepareInstanceAISettingForUpdate(ctx context.Context, se
...
@@ -523,6 +523,9 @@ func (s *APIV1Service) prepareInstanceAISettingForUpdate(ctx context.Context, se
if
provider
.
Type
==
storepb
.
AIProviderType_OPENAI
&&
provider
.
Endpoint
==
""
{
if
provider
.
Type
==
storepb
.
AIProviderType_OPENAI
&&
provider
.
Endpoint
==
""
{
provider
.
Endpoint
=
"https://api.openai.com/v1"
provider
.
Endpoint
=
"https://api.openai.com/v1"
}
}
if
provider
.
Type
==
storepb
.
AIProviderType_ANTHROPIC
&&
provider
.
Endpoint
==
""
{
provider
.
Endpoint
=
"https://api.anthropic.com/v1"
}
if
provider
.
Type
==
storepb
.
AIProviderType_OPENAI_COMPATIBLE
&&
provider
.
Endpoint
==
""
{
if
provider
.
Type
==
storepb
.
AIProviderType_OPENAI_COMPATIBLE
&&
provider
.
Endpoint
==
""
{
return
errors
.
Errorf
(
"provider %q endpoint is required"
,
provider
.
Id
)
return
errors
.
Errorf
(
"provider %q endpoint is required"
,
provider
.
Id
)
}
}
...
...
server/router/api/v1/test/ai_service_test.go
View file @
0ad0fec8
...
@@ -154,6 +154,47 @@ func TestTranscribe(t *testing.T) {
...
@@ -154,6 +154,47 @@ func TestTranscribe(t *testing.T) {
require
.
Equal
(
t
,
"gemini transcript"
,
resp
.
Text
)
require
.
Equal
(
t
,
"gemini transcript"
,
resp
.
Text
)
})
})
t
.
Run
(
"rejects Anthropic transcription as unsupported"
,
func
(
t
*
testing
.
T
)
{
ts
:=
NewTestService
(
t
)
defer
ts
.
Cleanup
()
user
,
err
:=
ts
.
CreateRegularUser
(
ctx
,
"anthropic-user"
)
require
.
NoError
(
t
,
err
)
userCtx
:=
ts
.
CreateUserContext
(
ctx
,
user
.
ID
)
_
,
err
=
ts
.
Store
.
UpsertInstanceSetting
(
ctx
,
&
storepb
.
InstanceSetting
{
Key
:
storepb
.
InstanceSettingKey_AI
,
Value
:
&
storepb
.
InstanceSetting_AiSetting
{
AiSetting
:
&
storepb
.
InstanceAISetting
{
Providers
:
[]
*
storepb
.
AIProviderConfig
{
{
Id
:
"anthropic-main"
,
Title
:
"Anthropic"
,
Type
:
storepb
.
AIProviderType_ANTHROPIC
,
Endpoint
:
"https://api.anthropic.com/v1"
,
ApiKey
:
"sk-ant-test"
,
Models
:
[]
string
{
"claude-sonnet-4-5"
},
DefaultModel
:
"claude-sonnet-4-5"
,
},
},
},
},
})
require
.
NoError
(
t
,
err
)
_
,
err
=
ts
.
Service
.
Transcribe
(
userCtx
,
&
v1pb
.
TranscribeRequest
{
ProviderId
:
"anthropic-main"
,
Config
:
&
v1pb
.
TranscriptionConfig
{},
Audio
:
&
v1pb
.
TranscriptionAudio
{
Source
:
&
v1pb
.
TranscriptionAudio_Content
{
Content
:
[]
byte
(
"RIFF"
)},
Filename
:
"voice.wav"
,
ContentType
:
"audio/wav"
,
},
})
require
.
Error
(
t
,
err
)
require
.
Contains
(
t
,
err
.
Error
(),
"capability unsupported"
)
})
t
.
Run
(
"rejects unconfigured model"
,
func
(
t
*
testing
.
T
)
{
t
.
Run
(
"rejects unconfigured model"
,
func
(
t
*
testing
.
T
)
{
ts
:=
NewTestService
(
t
)
ts
:=
NewTestService
(
t
)
defer
ts
.
Cleanup
()
defer
ts
.
Cleanup
()
...
...
server/router/api/v1/test/instance_service_test.go
View file @
0ad0fec8
...
@@ -624,4 +624,39 @@ func TestUpdateInstanceSetting(t *testing.T) {
...
@@ -624,4 +624,39 @@ func TestUpdateInstanceSetting(t *testing.T) {
require
.
Equal
(
t
,
[]
string
{
"gpt-5.4-mini"
,
"gpt-5.4"
},
stored
.
GetProviders
()[
0
]
.
GetModels
())
require
.
Equal
(
t
,
[]
string
{
"gpt-5.4-mini"
,
"gpt-5.4"
},
stored
.
GetProviders
()[
0
]
.
GetModels
())
require
.
Equal
(
t
,
"gpt-5.4-mini"
,
stored
.
GetProviders
()[
0
]
.
GetDefaultModel
())
require
.
Equal
(
t
,
"gpt-5.4-mini"
,
stored
.
GetProviders
()[
0
]
.
GetDefaultModel
())
})
})
t
.
Run
(
"UpdateInstanceSetting - Anthropic provider gets default endpoint"
,
func
(
t
*
testing
.
T
)
{
ts
:=
NewTestService
(
t
)
defer
ts
.
Cleanup
()
hostUser
,
err
:=
ts
.
CreateHostUser
(
ctx
,
"admin"
)
require
.
NoError
(
t
,
err
)
adminCtx
:=
ts
.
CreateUserContext
(
ctx
,
hostUser
.
ID
)
_
,
err
=
ts
.
Service
.
UpdateInstanceSetting
(
adminCtx
,
&
v1pb
.
UpdateInstanceSettingRequest
{
Setting
:
&
v1pb
.
InstanceSetting
{
Name
:
"instance/settings/AI"
,
Value
:
&
v1pb
.
InstanceSetting_AiSetting
{
AiSetting
:
&
v1pb
.
InstanceSetting_AISetting
{
Providers
:
[]
*
v1pb
.
InstanceSetting_AIProviderConfig
{
{
Id
:
"anthropic-main"
,
Title
:
"Anthropic"
,
Type
:
v1pb
.
InstanceSetting_ANTHROPIC
,
ApiKey
:
"sk-ant-test"
,
Models
:
[]
string
{
"claude-sonnet-4-5"
},
DefaultModel
:
"claude-sonnet-4-5"
,
},
},
},
},
},
})
require
.
NoError
(
t
,
err
)
stored
,
err
:=
ts
.
Store
.
GetInstanceAISetting
(
ctx
)
require
.
NoError
(
t
,
err
)
require
.
Len
(
t
,
stored
.
GetProviders
(),
1
)
require
.
Equal
(
t
,
"https://api.anthropic.com/v1"
,
stored
.
GetProviders
()[
0
]
.
GetEndpoint
())
})
}
}
web/src/components/Settings/AISection.tsx
View file @
0ad0fec8
...
@@ -42,6 +42,7 @@ const providerTypeOptions = [
...
@@ -42,6 +42,7 @@ const providerTypeOptions = [
InstanceSetting_AIProviderType
.
OPENAI
,
InstanceSetting_AIProviderType
.
OPENAI
,
InstanceSetting_AIProviderType
.
OPENAI_COMPATIBLE
,
InstanceSetting_AIProviderType
.
OPENAI_COMPATIBLE
,
InstanceSetting_AIProviderType
.
GEMINI
,
InstanceSetting_AIProviderType
.
GEMINI
,
InstanceSetting_AIProviderType
.
ANTHROPIC
,
];
];
const
createProviderID
=
()
=>
{
const
createProviderID
=
()
=>
{
...
...
web/src/types/proto/api/v1/instance_service_pb.ts
View file @
0ad0fec8
This diff is collapsed.
Click to expand it.
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment