Native detz entry + libcurl HTTP/SSE client; drop python execvp shim

This commit is contained in:
2026-10-24 07:02:48 +02:00
parent 8090638119
commit fc8ae44d96
4 changed files with 73 additions and 5 deletions
+3 -3
View File
@@ -27,7 +27,7 @@ $(BUILD)/main.o: main.cpp $(VERSION_HPP)
$(CXX) $(CXXFLAGS) -I$(GEN_INC) $(foreach d,$(shell find src -type d),-I$d) -c main.cpp -o $@
$(BIN): $(MAIN_OBJ) $(ALL_OBJS)
$(CXX) $(CXXFLAGS) $(MAIN_OBJ) $(ALL_OBJS) -lsqlite3 -o $@
$(CXX) $(CXXFLAGS) $(MAIN_OBJ) $(ALL_OBJS) -lsqlite3 -lcurl -o $@
test: $(BIN) $(BUILD)/detz_test $(BUILD)/store_test
./$(BUILD)/detz_test
@@ -36,11 +36,11 @@ test: $(BIN) $(BUILD)/detz_test $(BUILD)/store_test
$(BUILD)/store_test: tests/store_test.cpp src/store/store.cpp
@mkdir -p $(BUILD)
$(CXX) $(CXXFLAGS) -Isrc/store tests/store_test.cpp src/store/store.cpp -lsqlite3 -o $@
$(CXX) $(CXXFLAGS) -Isrc/store tests/store_test.cpp src/store/store.cpp -lsqlite3 -lcurl -o $@
$(BUILD)/detz_test: tests/test_main.cpp $(ALL_SRCS) $(VERSION_HPP)
@mkdir -p $(BUILD)
$(CXX) $(CXXFLAGS) -I$(GEN_INC) $(foreach d,$(shell find src -type d),-I$d) tests/test_main.cpp $(ALL_SRCS) -lsqlite3 -o $@
$(CXX) $(CXXFLAGS) -I$(GEN_INC) $(foreach d,$(shell find src -type d),-I$d) tests/test_main.cpp $(ALL_SRCS) -lsqlite3 -lcurl -o $@
lint:
$(CXX) $(CXXSTD) $(WARN) -Werror -fsyntax-only main.cpp -I$(GEN_INC) $(foreach d,$(shell find src -type d),-I$d) || true
+10 -2
View File
@@ -1,2 +1,10 @@
#include "src/cli/cli.h"
int main(int argc, char** argv) { return detz::cli::DetzCli{}.run(argc, argv); }
#include "cli/cli.h"
#include "version.hpp"
#include <cstdio>
#include <string>
int main(int argc, char** argv){
detz::cli::Parser p; auto cmd = p.parse(argc, argv);
if(cmd.opts.version){ std::puts(std::string(detz::kVersion).c_str()); return 0; }
if(cmd.opts.cap_manifest){ std::fputs("# tai CAP agent manifest\n", stdout); return 0; }
return cmd.run();
}
+40
View File
@@ -0,0 +1,40 @@
#include "util/http.h"
#include <curl/curl.h>
#include <functional>
#include <sstream>
namespace detz {
namespace {
struct Buf { std::string data; std::map<std::string,std::string>* hdrs{nullptr}; std::string* ctype{nullptr}; std::string sse; std::function<void(const SseEvent&)>* cb{nullptr}; std::string ev; std::string dt; };
size_t wbody(char* p, size_t n, size_t m, void* u){ ((Buf*)u)->data.append(p,n*m); return n*m; }
size_t whdr(char* p, size_t n, size_t m, void* u){ Buf* b=(Buf*)u; std::string l(p,n*m); auto c=l.find(':'); if(c!=std::string::npos){ std::string k=l.substr(0,c),v=l.substr(c+1); while(!v.empty()&&(v.back()=='\r'||v.back()=='\n'))v.pop_back(); while(!v.empty()&&v[0]==' ')v.erase(v.begin()); if(b->hdrs)(*b->hdrs)[k]=v; if(k=="Content-Type"&&b->ctype)*b->ctype=v; } return n*m; }
size_t wsse(char* p, size_t n, size_t m, void* u){ Buf* b=(Buf*)u; b->sse.append(p,n*m); size_t pos; while((pos=b->sse.find("\n\n"))!=std::string::npos){ std::string blk=b->sse.substr(0,pos); b->sse.erase(0,pos+2); SseEvent e; std::istringstream s(blk); std::string ln; while(std::getline(s,ln)){ if(!ln.empty()&&ln.back()=='\r')ln.pop_back(); if(ln.rfind("event:",0)==0)e.event=ln.substr(6); else if(ln.rfind("data:",0)==0){ if(!e.data.empty())e.data+="\n"; e.data+=ln.substr(5);} } if(e.event.empty())e.event="message"; if((*b->cb)&&!e.data.empty())(*b->cb)(e); } b->data+=std::string(p,n*m); return n*m; }
}
HttpResponse HttpRequest(const std::string& method, const std::string& url, const std::string& body, const std::map<std::string,std::string>& headers, long timeout_s){
HttpResponse r; CURL* c=curl_easy_init(); if(!c) return r; Buf b; b.data=""; b.hdrs=&r.headers; b.ctype=&r.content_type;
curl_easy_setopt(c,CURLOPT_URL,url.c_str()); curl_easy_setopt(c,CURLOPT_WRITEFUNCTION,wbody); curl_easy_setopt(c,CURLOPT_WRITEDATA,&b);
curl_easy_setopt(c,CURLOPT_HEADERFUNCTION,whdr); curl_easy_setopt(c,CURLOPT_HEADERDATA,&b);
curl_easy_setopt(c,CURLOPT_TIMEOUT,timeout_s); curl_easy_setopt(c,CURLOPT_FOLLOWLOCATION,1L);
struct curl_slist* hs=nullptr; for(auto&[k,v]:headers) hs=curl_slist_append(hs,(k+": "+v).c_str());
if(hs) curl_easy_setopt(c,CURLOPT_HTTPHEADER,hs);
if(method=="POST"){ curl_easy_setopt(c,CURLOPT_POST,1L); curl_easy_setopt(c,CURLOPT_POSTFIELDS,body.c_str()); curl_easy_setopt(c,CURLOPT_POSTFIELDSIZE,(long)body.size()); }
else if(method!="GET"){ curl_easy_setopt(c,CURLOPT_CUSTOMREQUEST,method.c_str()); if(!body.empty()){curl_easy_setopt(c,CURLOPT_POSTFIELDS,body.c_str());curl_easy_setopt(c,CURLOPT_POSTFIELDSIZE,(long)body.size());} }
CURLcode rc=curl_easy_perform(c); if(rc==CURLE_OK){ curl_easy_getinfo(c,CURLINFO_RESPONSE_CODE,&r.status); r.body=b.data; }
curl_slist_free_all(hs); curl_easy_cleanup(c); return r;
}
HttpResponse HttpGet(const std::string& u,const std::map<std::string,std::string>& h,long t){ return HttpRequest("GET",u,"",h,t); }
HttpResponse HttpPost(const std::string& u,const std::string& b,const std::map<std::string,std::string>& h,long t){ return HttpRequest("POST",u,b,h,t); }
long HttpPostSse(const std::string& url,const std::string& body,const std::map<std::string,std::string>& headers,long timeout_s,const std::function<void(const SseEvent&)>& cb){
CURL* c=curl_easy_init(); if(!c) return 0; Buf b; b.cb=(std::function<void(const SseEvent&)>*)&cb;
curl_easy_setopt(c,CURLOPT_URL,url.c_str()); curl_easy_setopt(c,CURLOPT_POST,1L);
curl_easy_setopt(c,CURLOPT_POSTFIELDS,body.c_str()); curl_easy_setopt(c,CURLOPT_POSTFIELDSIZE,(long)body.size());
curl_easy_setopt(c,CURLOPT_WRITEFUNCTION,wsse); curl_easy_setopt(c,CURLOPT_WRITEDATA,&b);
curl_easy_setopt(c,CURLOPT_TIMEOUT,timeout_s);
struct curl_slist* hs=nullptr; for(auto&[k,v]:headers) hs=curl_slist_append(hs,(k+": "+v).c_str());
if(hs) curl_easy_setopt(c,CURLOPT_HTTPHEADER,hs);
long st=0; if(curl_easy_perform(c)==CURLE_OK) curl_easy_getinfo(c,CURLINFO_RESPONSE_CODE,&st);
curl_slist_free_all(hs); curl_easy_cleanup(c); return st;
}
std::string JsonEscape(const std::string& s){ std::string o; for(char c:s){ switch(c){case '"':o+="\\\"";break;case '\\':o+="\\\\";break;case '\n':o+="\\n";break;case '\r':o+="\\r";break;case '\t':o+="\\t";break;default: if((unsigned char)c<0x20){char b[8];snprintf(b,8,"\\u%04x",c);o+=b;} else o+=c;} } return o; }
std::string JsonGetStr(const std::string& o,const std::string& k){ std::string p="\""+k+"\""; auto i=o.find(p); if(i==std::string::npos)return ""; i=o.find(':',i); if(i==std::string::npos)return ""; ++i; while(i<o.size()&&(o[i]==' '||o[i]=='"')){if(o[i]=='"'){++i;std::string v;while(i<o.size()&&o[i]!='"'){if(o[i]=='\\'&&i+1<o.size()){v+=o[i+1];i+=2;}else v+=o[i++];}return v;}++i;} std::string v; while(i<o.size()&&o[i]!=','&&o[i]!='}'&&o[i]!='"')v+=o[i++]; return v; }
long JsonGetInt(const std::string& o,const std::string& k,long d){ std::string v=JsonGetStr(o,k); if(v.empty())return d; try{return std::stol(v);}catch(...){return d;} }
}
+20
View File
@@ -0,0 +1,20 @@
#pragma once
#include <functional>
#include <map>
#include <string>
#include <vector>
namespace detz {
// Minimal HTTP client over libcurl (mirrors tai.py urllib flows: GET/POST,
// headers, timeouts, SSE streaming). No dependency beyond libcurl.
struct HttpResponse { long status{0}; std::string body; std::map<std::string,std::string> headers; std::string content_type; };
struct SseEvent { std::string event; std::string data; };
HttpResponse HttpGet(const std::string& url, const std::map<std::string,std::string>& headers = {}, long timeout_s = 60);
HttpResponse HttpPost(const std::string& url, const std::string& body, const std::map<std::string,std::string>& headers = {}, long timeout_s = 60);
HttpResponse HttpRequest(const std::string& method, const std::string& url, const std::string& body, const std::map<std::string,std::string>& headers, long timeout_s);
// Stream SSE: invokes cb per event, returns final HTTP status.
long HttpPostSse(const std::string& url, const std::string& body, const std::map<std::string,std::string>& headers, long timeout_s, const std::function<void(const SseEvent&)>& cb);
// Tiny JSON helpers (escape/extract) mirroring tai.py json usage.
std::string JsonEscape(const std::string& s);
std::string JsonGetStr(const std::string& obj, const std::string& key);
long JsonGetInt(const std::string& obj, const std::string& key, long dflt = 0);
}