secondme-reference
npx skills add https://github.com/mindverse/second-me-skills --skill secondme-reference
Agent 安装分布
Skill 文档
SecondMe API ææ¯åè
æ¬ææ¡£å å« SecondMe API ç宿´ææ¯åèä¿¡æ¯ï¼ä¾å¼åæ¶æ¥é ã
API åºç¡ URL
https://app.mindos.com/gate/lab
OAuth2 ææ URL
https://go.second.me/oauth/
OAuth2 æµç¨
1. ç¨æ·ç¹å»ç»å½ â è·³è½¬å° SecondMe ææé¡µé¢
2. ç¨æ·ææ â éå®ååä½ çåºç¨ï¼å¸¦ authorization_codeï¼
3. åç«¯ç¨ code æ¢å access_token å refresh_token
4. ä½¿ç¨ access_token è°ç¨ SecondMe API
5. Token è¿ææ¶ä½¿ç¨ refresh_token å·æ°
ææ URL æé
éè¦ï¼oauth_url å·²å
å«å®æ´è·¯å¾ï¼ç´æ¥å¨å颿¼æ¥ ? åæ¥è¯¢åæ°å³å¯ï¼ä¸è¦è¿½å /authorize çè·¯å¾ã
const OAUTH_URL = 'https://go.second.me/oauth/';
const params = new URLSearchParams({
client_id: process.env.SECONDME_CLIENT_ID,
redirect_uri: process.env.SECONDME_REDIRECT_URI,
response_type: 'code',
state: generatedState,
});
// â
æ£ç¡®ï¼ç´æ¥æ¼æ¥ ? ååæ°
const authUrl = `${OAUTH_URL}?${params.toString()}`;
// ç»æ: https://go.second.me/oauth/?client_id=...&redirect_uri=...
// â é误ï¼ä¸è¦è¿½å /authorize çè·¯å¾
// const authUrl = `${OAUTH_URL}/authorize?${params}`;
// ä¼åæ: https://go.second.me/oauth//authorize?... â
Token 交æ¢ï¼ç¨ææç æ¢ Tokenï¼
端ç¹
POST {base_url}/api/oauth/token/code
è¯·æ±æ ¼å¼
Content-Type å¿
é¡»æ¯ application/x-www-form-urlencodedï¼ä¸æ¯ JSONã
const response = await fetch(`${API_BASE_URL}/api/oauth/token/code`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded', // å¿
é¡»
},
body: new URLSearchParams({
grant_type: 'authorization_code',
code: authorizationCode,
redirect_uri: process.env.SECONDME_REDIRECT_URI,
client_id: process.env.SECONDME_CLIENT_ID,
client_secret: process.env.SECONDME_CLIENT_SECRET,
}),
});
ååºæ ¼å¼
ååºéµå¾ªç»ä¸å è£ æ ¼å¼ï¼åæ®µä½¿ç¨ camelCaseï¼ä¸æ¯ OAuth2 æ åç snake_caseï¼ï¼
{
"code": 0,
"data": {
"accessToken": "lba_at_xxxxx...",
"refreshToken": "lba_rt_xxxxx...",
"tokenType": "Bearer",
"expiresIn": 7200,
"scope": ["user.info", "chat"]
}
}
ååºå¤ç
const result = await response.json();
// å¿
é¡»æ£æ¥ code åæ®µ
if (result.code !== 0 || !result.data) {
throw new Error(`Token exchange failed: ${result.message}`);
}
// ä» data 䏿åï¼ä½¿ç¨ camelCase
const { accessToken, refreshToken, expiresIn } = result.data;
Token å·æ°
端ç¹
POST {base_url}/api/oauth/token/refresh
è¯·æ±æ ¼å¼
const response = await fetch(`${API_BASE_URL}/api/oauth/token/refresh`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
grant_type: 'refresh_token',
refresh_token: storedRefreshToken,
client_id: process.env.SECONDME_CLIENT_ID,
client_secret: process.env.SECONDME_CLIENT_SECRET,
}),
});
ååºæ ¼å¼ä¸ Token 交æ¢ä¸è´ã
Token æææ
| Token ç±»å | åç¼ | æææ |
|---|---|---|
| ææç | lba_ac_ |
5 åé |
| Access Token | lba_at_ |
2 å°æ¶ |
| Refresh Token | lba_rt_ |
30 天 |
æéå表ï¼Scopesï¼
| æé | 说æ |
|---|---|
user.info |
ç¨æ·åºç¡ä¿¡æ¯ |
user.info.shades |
ç¨æ·å ´è¶£æ ç¾ |
user.info.softmemory |
ç¨æ·è½¯è®°å¿ |
note.add |
æ·»å ç¬è®° |
chat |
è天åè½ |
chat |
ç»æåå¨ä½å¤æï¼Actï¼ |
API ååºæ ¼å¼ä¸å¤ç
éè¦ï¼ææ SecondMe API ååºé½éµå¾ªç»ä¸æ ¼å¼ï¼
{
"code": 0,
"data": { ... } // å®é
æ°æ®å¨ data åæ®µå
}
å端代ç å¿ é¡»æ£ç¡®æåæ°æ®ï¼
// 注æï¼ä»¥ä¸ /api/secondme/... æ¯ Next.js æ¬å°è·¯ç±ï¼ç± secondme-nextjs skill çæï¼ï¼
// æ¬å°è·¯ç±ä¼ä»£ç请æ±å°ä¸æ¸¸ SecondMe APIï¼å¹¶éä¼ ä¸æ¸¸çååºæ ¼å¼ã
// â éè¯¯åæ³ - ç´æ¥ä½¿ç¨ååºä¼å¯¼è´ .map is not a function
const response = await fetch('/api/secondme/user/shades'); // Next.js æ¬å°è·¯ç±
const shades = await response.json();
shades.map(item => ...) // é误ï¼
// â
æ£ç¡®åæ³ - æå data åæ®µå
çæ°æ®
const response = await fetch('/api/secondme/user/shades'); // Next.js æ¬å°è·¯ç±
const result = await response.json();
if (result.code === 0) {
const shades = result.data.shades; // æ£ç¡®ï¼
shades.map(item => ...)
}
å API çæ°æ®è·¯å¾
以ä¸è·¯å¾åä¸ºä¸æ¸¸ SecondMe API è·¯å¾ï¼å®æ´ URL =
{base_url}/api/secondme{path}å ¶ä¸base_urlæ¥èªstate.api.base_urlï¼é»è®¤https://app.mindos.com/gate/labï¼
| 䏿¸¸ API è·¯å¾ | æ°æ®è·¯å¾ | ç±»å |
|---|---|---|
/api/secondme/user/info |
result.data |
objectï¼å« email, name, avatarUrl, route çåæ®µï¼ |
/api/secondme/user/shades |
result.data.shades |
array |
/api/secondme/user/softmemory |
result.data.list |
array |
/api/secondme/chat/session/list |
result.data.sessions |
array |
/api/secondme/chat/session/messages |
result.data.messages |
array |
/api/secondme/act/stream |
SSE æµå¼ JSONï¼éæ¼æ¥ deltaï¼ | SSE stream |
/api/secondme/note/add |
result.data.noteId |
number |
Act APIï¼ç»æåå¨ä½å¤æï¼
Act API æ¯ç¬ç«äº Chat API çæ¥å£ï¼çº¦ææ¨¡åä»
è¾åºåæ³ JSON 对象ï¼éç¨äºæ
æåæãæå¾åç±»çç»æåå³çåºæ¯ãæéä½¿ç¨ chat scopeã
端ç¹ï¼ä¸æ¸¸ APIï¼
POST {base_url}/api/secondme/act/stream
请æ±åæ°
| åæ° | ç±»å | å¿ é | 说æ |
|---|---|---|---|
| message | string | æ¯ | ç¨æ·æ¶æ¯å 容 |
| actionControl | string | æ¯ | å¨ä½æ§å¶è¯´æï¼20-8000 å符ï¼ï¼å®ä¹ JSON ç»æä¸å¤æè§å |
| appId | string | å¦ | åºç¨ ID |
| sessionId | string | å¦ | ä¼è¯ IDï¼ä¸æä¾åèªå¨çæ |
| systemPrompt | string | å¦ | ç³»ç»æç¤ºè¯ï¼ä» æ°ä¼è¯é¦æ¬¡ææ |
actionControl 示ä¾
ä»
è¾åºåæ³ JSON 对象ï¼ä¸è¦è§£éã
è¾åºç»æï¼{"is_liked": boolean}ã
å½ç¨æ·æç¡®è¡¨è¾¾åæ¬¢ææ¯ææ¶ is_liked=trueï¼å¦å is_liked=falseã
ååºæ ¼å¼ï¼SSEï¼
event: session
data: {"sessionId": "labs_sess_xxx"}
data: {"choices": [{"delta": {"content": "{\"is_liked\": true}"}}]}
data: [DONE]
å端å¤ç示ä¾
// è°ç¨ Act API è¿è¡ç»æå夿ï¼éè¿ Next.js æ¬å°è·¯ç±ä»£çå°ä¸æ¸¸ï¼
const response = await fetch('/api/secondme/act/stream', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
message: userMessage,
actionControl: 'ä»
è¾åºåæ³ JSONãç»æï¼{"intent": "like"|"dislike"|"neutral"}ãæ ¹æ®ç¨æ·è¡¨è¾¾å¤ææå¾ãä¿¡æ¯ä¸è¶³æ¶è¿å {"intent": "neutral"}ã'
})
});
// æ¼æ¥ SSE æµä¸ç delta contentï¼æç» JSON.parse å¾å°ç»æ
Chat vs Act 使ç¨åºæ¯
| åºæ¯ | ä½¿ç¨ API | åå |
|---|---|---|
| èªç±å¯¹è¯ | /chat/stream |
è¿åèªç¶è¯è¨ææ¬ |
| æ æ/æå¾å¤æ | /act/stream |
è¿åç»æå JSON |
| æ¯/å¦å³ç | /act/stream |
è¿å {"result": boolean} |
| å¤åç±»å¤æ | /act/stream |
è¿å {"category": "..."} |
| å å®¹çæ | /chat/stream |
éè¦é¿ææ¬è¾åº |
å¼å注æäºé¡¹
State åæ°
ç´æ¥å¿½ç¥ state åæ°éªè¯ã å¨åè°å¤çæ¶ä¸éè¦éªè¯ stateï¼ç´æ¥å¤çææç å³å¯ã
CSS @import è§å顺åº
éè¦ï¼ å¨ CSS æä»¶ä¸ï¼@import è¯å¥å¿
é¡»æ¾å¨æä»¶çæå¼å¤´ï¼åªè½å¨ @charset å @layer ä¹åï¼ã妿å¨å
¶ä» CSS è§åä¹åä½¿ç¨ @importï¼ä¼å¯¼è´è§£æé误ã
/* æ£ç¡®åæ³ - @import æ¾å¨æåé¢ */
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC&display=swap');
:root {
--primary-color: #000;
}
/* éè¯¯åæ³ - @import ä¸è½æ¾å¨å
¶ä»è§åä¹å */
:root {
--primary-color: #000;
}
@import url('...'); /* è¿ä¼æ¥éï¼ */
宿¹ææ¡£é¾æ¥
| ææ¡£ | å°å |
|---|---|
| å¿«éå ¥é¨ | https://develop-docs.second.me/zh/docs |
| è®¤è¯æ¦è¿° | https://develop-docs.second.me/zh/docs/authentication |
| OAuth2 æå | https://develop-docs.second.me/zh/docs/authentication/oauth2 |
| SecondMe API åè | https://develop-docs.second.me/zh/docs/api-reference/secondme |
| OAuth2 API åè | https://develop-docs.second.me/zh/docs/api-reference/oauth |
| é误ç åè | https://develop-docs.second.me/zh/docs/errors |