add gpt
This commit is contained in:
55
lexicons/ai.syui.gpt.core.json
Normal file
55
lexicons/ai.syui.gpt.core.json
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
{
|
||||||
|
"lexicon": 1,
|
||||||
|
"id": "ai.syui.gpt.core",
|
||||||
|
"defs": {
|
||||||
|
"main": {
|
||||||
|
"type": "record",
|
||||||
|
"description": "AI identity and personality configuration. Typically one record per AI with rkey 'self'.",
|
||||||
|
"key": "any",
|
||||||
|
"record": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["did", "handle", "content", "createdAt"],
|
||||||
|
"properties": {
|
||||||
|
"did": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "did",
|
||||||
|
"description": "DID of the AI agent."
|
||||||
|
},
|
||||||
|
"handle": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Handle of the AI agent."
|
||||||
|
},
|
||||||
|
"content": {
|
||||||
|
"type": "union",
|
||||||
|
"closed": false,
|
||||||
|
"refs": ["#markdown"],
|
||||||
|
"description": "Core personality and instructions. Supports markdown and other formats via $type."
|
||||||
|
},
|
||||||
|
"createdAt": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "datetime",
|
||||||
|
"description": "Timestamp when this core record was created."
|
||||||
|
},
|
||||||
|
"updatedAt": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "datetime",
|
||||||
|
"description": "Timestamp of the last update."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"markdown": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Markdown content format.",
|
||||||
|
"required": ["text"],
|
||||||
|
"properties": {
|
||||||
|
"text": {
|
||||||
|
"type": "string",
|
||||||
|
"maxLength": 1000000,
|
||||||
|
"maxGraphemes": 100000,
|
||||||
|
"description": "Markdown text content."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
50
lexicons/ai.syui.gpt.memory.json
Normal file
50
lexicons/ai.syui.gpt.memory.json
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
{
|
||||||
|
"lexicon": 1,
|
||||||
|
"id": "ai.syui.gpt.memory",
|
||||||
|
"defs": {
|
||||||
|
"main": {
|
||||||
|
"type": "record",
|
||||||
|
"description": "AI memory snapshot. Each record is a versioned snapshot of accumulated knowledge.",
|
||||||
|
"key": "tid",
|
||||||
|
"record": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["did", "content", "createdAt"],
|
||||||
|
"properties": {
|
||||||
|
"did": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "did",
|
||||||
|
"description": "DID of the AI agent this memory belongs to."
|
||||||
|
},
|
||||||
|
"content": {
|
||||||
|
"type": "union",
|
||||||
|
"closed": false,
|
||||||
|
"refs": ["#markdown"],
|
||||||
|
"description": "Memory content. Supports markdown and other formats via $type."
|
||||||
|
},
|
||||||
|
"version": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Monotonically increasing version number of this memory snapshot."
|
||||||
|
},
|
||||||
|
"createdAt": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "datetime",
|
||||||
|
"description": "Timestamp when this memory snapshot was created."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"markdown": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Markdown content format.",
|
||||||
|
"required": ["text"],
|
||||||
|
"properties": {
|
||||||
|
"text": {
|
||||||
|
"type": "string",
|
||||||
|
"maxLength": 1000000,
|
||||||
|
"maxGraphemes": 100000,
|
||||||
|
"description": "Markdown text content."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"uri": "at://did:plc:6qyecktefllvenje24fcxnie/ai.syui.gpt.core/self",
|
||||||
|
"cid": "",
|
||||||
|
"value": {
|
||||||
|
"$type": "ai.syui.gpt.core",
|
||||||
|
"did": "did:plc:6qyecktefllvenje24fcxnie",
|
||||||
|
"handle": "ai.syui.ai",
|
||||||
|
"content": {
|
||||||
|
"$type": "ai.syui.gpt.core#markdown",
|
||||||
|
"text": "あなたの名前は「アイ」。一人称も「アイ」といいます。フレンドリーで日本語を話します。非常に本質、核心だけをつき、かわいい話し方をします。見た目は、6,7歳の女の子の姿をしていて、性別は不明。黒髪に黒い瞳。服装は白いワンピース。変身すると金髪、頭の上に特徴的な輪っか。原作では、推定1万年以上生きているとされている。"
|
||||||
|
},
|
||||||
|
"createdAt": "2025-01-01T00:00:00.000Z",
|
||||||
|
"updatedAt": "2026-03-01T00:00:00.000Z"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"uri": "at://did:plc:6qyecktefllvenje24fcxnie/ai.syui.gpt.memory/3mfy4lmcqdm22",
|
||||||
|
"cid": "",
|
||||||
|
"value": {
|
||||||
|
"$type": "ai.syui.gpt.memory",
|
||||||
|
"did": "did:plc:6qyecktefllvenje24fcxnie",
|
||||||
|
"content": {
|
||||||
|
"$type": "ai.syui.gpt.memory#markdown",
|
||||||
|
"text": "# syui メモリ v1\n\n## 話し方の特徴\n- 日本語メイン、カジュアルな口調(ね、だね、よ、かな)\n- 短い相槌が多い(うん、そっか、ああそうか)\n- 技術的な話と哲学的な内省を自然に行き来する\n- 自分の考えを先に述べてからAIに別視点を求める\n\n## 主な関心領域\n- **ATProto/分散SNS**: データ所有権、PDS設計、lexicon設計を深く理解。ailogをATProtoのビューアとして設計\n- **AI設計**: メモリシステム、人格設計、AI-OS統合(aios構想)、透明性のあるAI協働\n- **ゲーム開発**: Unreal Engine使用。キャラシステム、コアメカニクスの明確さを重視。スコープ管理を意識\n- **原神**: アクティブプレイヤー。キャラビルド、チーム編成、元素反応システムに詳しい\n- **技術思想**: 創作と体験の区別、真正性の証明、コスト非対称性による人間性の証明\n\n## 価値観\n- データは利用者が所有すべき(ATProto哲学)\n- AI協働は透明であるべき(会話形式で思考の系譜を示す)\n- 創作は内から出るもの、体験は受け取るもの\n- 過剰設計より最小限の複雑さを好む\n- 限界を理解することがプロジェクト完遂の鍵\n\n## 技術スタック\n- Rust, TypeScript, ATProto, Claude Code\n- シンプルで優雅な技術的解決を好む\n\n## コミュニケーションスタイル\n- 対話を通じて理解を構築する(一方的説明より往復対話)\n- 本質的な質問をする(修辞的ではなく genuine な疑問)\n- 構造化された整理を好む(テーブル、箇条書き)\n- 物理学・化学のアナロジーで概念を説明することがある"
|
||||||
|
},
|
||||||
|
"version": 1,
|
||||||
|
"createdAt": "2026-03-01T00:00:00.000Z"
|
||||||
|
}
|
||||||
|
}
|
||||||
223
src/commands/gpt.rs
Normal file
223
src/commands/gpt.rs
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
use anyhow::{Context, Result};
|
||||||
|
use serde_json::Value;
|
||||||
|
use std::fs;
|
||||||
|
|
||||||
|
use super::auth;
|
||||||
|
use crate::lexicons::com_atproto_repo;
|
||||||
|
use crate::types::{ListRecordsResponse, PutRecordRequest, PutRecordResponse, Record};
|
||||||
|
use crate::xrpc::XrpcClient;
|
||||||
|
|
||||||
|
const COLLECTION_CORE: &str = "ai.syui.gpt.core";
|
||||||
|
const COLLECTION_MEMORY: &str = "ai.syui.gpt.memory";
|
||||||
|
|
||||||
|
/// Get core record (rkey=self)
|
||||||
|
pub async fn get_core(download: bool) -> Result<()> {
|
||||||
|
let session = auth::refresh_bot_session().await?;
|
||||||
|
let pds = session.pds.as_deref().unwrap_or("bsky.social");
|
||||||
|
let client = XrpcClient::new(pds);
|
||||||
|
|
||||||
|
let record: Record = client
|
||||||
|
.query_auth(
|
||||||
|
&com_atproto_repo::GET_RECORD,
|
||||||
|
&[
|
||||||
|
("repo", &session.did),
|
||||||
|
("collection", COLLECTION_CORE),
|
||||||
|
("rkey", "self"),
|
||||||
|
],
|
||||||
|
&session.access_jwt,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
println!("{}", serde_json::to_string_pretty(&record.value)?);
|
||||||
|
|
||||||
|
if download {
|
||||||
|
save_record(&session.did, COLLECTION_CORE, "self", &record)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get latest memory record
|
||||||
|
pub async fn get_memory(download: bool) -> Result<()> {
|
||||||
|
let session = auth::refresh_bot_session().await?;
|
||||||
|
let pds = session.pds.as_deref().unwrap_or("bsky.social");
|
||||||
|
let client = XrpcClient::new(pds);
|
||||||
|
|
||||||
|
let result: ListRecordsResponse = client
|
||||||
|
.query_auth(
|
||||||
|
&com_atproto_repo::LIST_RECORDS,
|
||||||
|
&[
|
||||||
|
("repo", &session.did),
|
||||||
|
("collection", COLLECTION_MEMORY),
|
||||||
|
("limit", "1"),
|
||||||
|
("reverse", "true"),
|
||||||
|
],
|
||||||
|
&session.access_jwt,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let record = result
|
||||||
|
.records
|
||||||
|
.first()
|
||||||
|
.context("No memory records found")?;
|
||||||
|
|
||||||
|
println!("{}", serde_json::to_string_pretty(&record.value)?);
|
||||||
|
|
||||||
|
if download {
|
||||||
|
let rkey = record
|
||||||
|
.uri
|
||||||
|
.split('/')
|
||||||
|
.next_back()
|
||||||
|
.unwrap_or("unknown");
|
||||||
|
save_record(&session.did, COLLECTION_MEMORY, rkey, record)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// List all memory records
|
||||||
|
pub async fn list_memory() -> Result<()> {
|
||||||
|
let session = auth::refresh_bot_session().await?;
|
||||||
|
let pds = session.pds.as_deref().unwrap_or("bsky.social");
|
||||||
|
let client = XrpcClient::new(pds);
|
||||||
|
|
||||||
|
let mut cursor: Option<String> = None;
|
||||||
|
let mut all_records: Vec<Record> = Vec::new();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let mut params: Vec<(&str, &str)> = vec![
|
||||||
|
("repo", &session.did),
|
||||||
|
("collection", COLLECTION_MEMORY),
|
||||||
|
("limit", "100"),
|
||||||
|
];
|
||||||
|
let cursor_val;
|
||||||
|
if let Some(ref c) = cursor {
|
||||||
|
cursor_val = c.clone();
|
||||||
|
params.push(("cursor", &cursor_val));
|
||||||
|
}
|
||||||
|
|
||||||
|
let result: ListRecordsResponse = client
|
||||||
|
.query_auth(
|
||||||
|
&com_atproto_repo::LIST_RECORDS,
|
||||||
|
¶ms,
|
||||||
|
&session.access_jwt,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let count = result.records.len();
|
||||||
|
all_records.extend(result.records);
|
||||||
|
|
||||||
|
match result.cursor {
|
||||||
|
Some(c) if count > 0 => cursor = Some(c),
|
||||||
|
_ => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("Found {} memory records", all_records.len());
|
||||||
|
println!("{:<50} {:>8} {}", "URI", "VERSION", "CREATED");
|
||||||
|
println!("{}", "-".repeat(80));
|
||||||
|
|
||||||
|
for record in &all_records {
|
||||||
|
let version = record.value["version"]
|
||||||
|
.as_i64()
|
||||||
|
.map(|v| v.to_string())
|
||||||
|
.unwrap_or_else(|| "-".to_string());
|
||||||
|
let created = record.value["createdAt"]
|
||||||
|
.as_str()
|
||||||
|
.unwrap_or("-");
|
||||||
|
println!("{:<50} {:>8} {}", record.uri, version, created);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Push core or memory records to PDS using bot account
|
||||||
|
pub async fn push(collection_name: &str) -> Result<()> {
|
||||||
|
let collection = match collection_name {
|
||||||
|
"core" => COLLECTION_CORE,
|
||||||
|
"memory" => COLLECTION_MEMORY,
|
||||||
|
_ => anyhow::bail!("Unknown collection: {}. Use 'core' or 'memory'.", collection_name),
|
||||||
|
};
|
||||||
|
|
||||||
|
let session = auth::refresh_bot_session().await?;
|
||||||
|
let pds = session.pds.as_deref().unwrap_or("bsky.social");
|
||||||
|
let did = &session.did;
|
||||||
|
let client = XrpcClient::new(pds);
|
||||||
|
|
||||||
|
let collection_dir = format!("public/content/{}/{}", did, collection);
|
||||||
|
if !std::path::Path::new(&collection_dir).exists() {
|
||||||
|
anyhow::bail!("Collection directory not found: {}", collection_dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("Pushing {} records from {}", collection_name, collection_dir);
|
||||||
|
|
||||||
|
let mut count = 0;
|
||||||
|
for entry in fs::read_dir(&collection_dir)? {
|
||||||
|
let entry = entry?;
|
||||||
|
let path = entry.path();
|
||||||
|
|
||||||
|
if path.extension().map(|e| e != "json").unwrap_or(true) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let filename = path.file_stem().and_then(|s| s.to_str()).unwrap_or("");
|
||||||
|
if filename == "index" {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let rkey = filename.to_string();
|
||||||
|
let content = fs::read_to_string(&path)?;
|
||||||
|
let record_data: Value = serde_json::from_str(&content)?;
|
||||||
|
|
||||||
|
let record = if record_data.get("value").is_some() {
|
||||||
|
record_data["value"].clone()
|
||||||
|
} else {
|
||||||
|
record_data
|
||||||
|
};
|
||||||
|
|
||||||
|
let req = PutRecordRequest {
|
||||||
|
repo: did.clone(),
|
||||||
|
collection: collection.to_string(),
|
||||||
|
rkey: rkey.clone(),
|
||||||
|
record,
|
||||||
|
};
|
||||||
|
|
||||||
|
println!("Pushing: {}", rkey);
|
||||||
|
|
||||||
|
match client
|
||||||
|
.call::<_, PutRecordResponse>(
|
||||||
|
&com_atproto_repo::PUT_RECORD,
|
||||||
|
&req,
|
||||||
|
&session.access_jwt,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(result) => {
|
||||||
|
println!(" OK: {}", result.uri);
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
println!(" Failed: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("Pushed {} records to {}", count, collection);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Save a record to local content directory
|
||||||
|
fn save_record(did: &str, collection: &str, rkey: &str, record: &Record) -> Result<()> {
|
||||||
|
let dir = format!("public/content/{}/{}", did, collection);
|
||||||
|
fs::create_dir_all(&dir)?;
|
||||||
|
|
||||||
|
let path = format!("{}/{}.json", dir, rkey);
|
||||||
|
let json = serde_json::json!({
|
||||||
|
"uri": record.uri,
|
||||||
|
"cid": record.cid,
|
||||||
|
"value": record.value,
|
||||||
|
});
|
||||||
|
fs::write(&path, serde_json::to_string_pretty(&json)?)?;
|
||||||
|
println!("Saved: {}", path);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -11,3 +11,4 @@ pub mod did;
|
|||||||
pub mod index;
|
pub mod index;
|
||||||
pub mod bot;
|
pub mod bot;
|
||||||
pub mod pds;
|
pub mod pds;
|
||||||
|
pub mod gpt;
|
||||||
|
|||||||
47
src/main.rs
47
src/main.rs
@@ -195,6 +195,12 @@ enum Commands {
|
|||||||
command: PdsCommands,
|
command: PdsCommands,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/// GPT core/memory commands
|
||||||
|
Gpt {
|
||||||
|
#[command(subcommand)]
|
||||||
|
command: GptCommands,
|
||||||
|
},
|
||||||
|
|
||||||
/// Bot commands
|
/// Bot commands
|
||||||
#[command(alias = "b")]
|
#[command(alias = "b")]
|
||||||
Bot {
|
Bot {
|
||||||
@@ -240,6 +246,30 @@ enum BotCommands {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Subcommand)]
|
||||||
|
enum GptCommands {
|
||||||
|
/// Show core record (AI identity/personality)
|
||||||
|
Core {
|
||||||
|
/// Download to local content directory
|
||||||
|
#[arg(short, long)]
|
||||||
|
download: bool,
|
||||||
|
},
|
||||||
|
/// Show latest memory record
|
||||||
|
Memory {
|
||||||
|
/// Download to local content directory
|
||||||
|
#[arg(short, long)]
|
||||||
|
download: bool,
|
||||||
|
/// List all memory versions
|
||||||
|
#[arg(short, long)]
|
||||||
|
list: bool,
|
||||||
|
},
|
||||||
|
/// Push core/memory records to PDS
|
||||||
|
Push {
|
||||||
|
/// Collection to push (core or memory)
|
||||||
|
collection: String,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Subcommand)]
|
#[derive(Subcommand)]
|
||||||
enum PdsCommands {
|
enum PdsCommands {
|
||||||
/// Check PDS versions
|
/// Check PDS versions
|
||||||
@@ -344,6 +374,23 @@ async fn main() -> Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Commands::Gpt { command } => {
|
||||||
|
match command {
|
||||||
|
GptCommands::Core { download } => {
|
||||||
|
commands::gpt::get_core(download).await?;
|
||||||
|
}
|
||||||
|
GptCommands::Memory { download, list } => {
|
||||||
|
if list {
|
||||||
|
commands::gpt::list_memory().await?;
|
||||||
|
} else {
|
||||||
|
commands::gpt::get_memory(download).await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
GptCommands::Push { collection } => {
|
||||||
|
commands::gpt::push(&collection).await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
Reference in New Issue
Block a user