tina4-nodejs 3.13.111 → 3.13.113
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +2 -2
- package/package.json +1 -1
- package/packages/cli/dist/bin.js +697 -198
- package/packages/core/dist/index.js +700 -198
- package/packages/core/src/aiClient.ts +368 -48
- package/packages/core/src/api.ts +269 -0
- package/packages/core/src/devAdmin.ts +181 -145
- package/packages/core/src/index.ts +3 -3
- package/packages/core/src/server.ts +6 -0
- package/packages/orm/dist/index.js +697 -198
- package/types/core/src/aiClient.d.ts +74 -5
- package/types/core/src/api.d.ts +102 -0
- package/types/core/src/devAdmin.d.ts +1 -0
- package/types/core/src/index.d.ts +3 -3
|
@@ -32833,47 +32833,120 @@ function renderToolbarHtml(ctx) {
|
|
|
32833
32833
|
const method = escapeHtml(ctx.method);
|
|
32834
32834
|
const path8 = escapeHtml(ctx.path);
|
|
32835
32835
|
const matchedPattern = escapeHtml(ctx.matchedPattern);
|
|
32836
|
-
|
|
32837
|
-
|
|
32838
|
-
|
|
32839
|
-
|
|
32840
|
-
|
|
32841
|
-
|
|
32836
|
+
const reload = ctx.reload === false ? "0" : "1";
|
|
32837
|
+
return `<link rel="stylesheet" href="/__dev/toolbar.css">
|
|
32838
|
+
<div id="tina4-dev-toolbar" data-reload="${reload}">
|
|
32839
|
+
<span id="tina4-ver-btn" title="Click to check for updates">Tina4 v${ctx.version}</span>
|
|
32840
|
+
<div id="tina4-ver-modal">
|
|
32841
|
+
<div class="t4-modal-head">
|
|
32842
|
+
<strong class="t4-modal-title">Version Info</strong>
|
|
32843
|
+
<span id="tina4-ver-close" class="t4-x">×</span>
|
|
32842
32844
|
</div>
|
|
32843
|
-
<div id="tina4-ver-body"
|
|
32844
|
-
<div>Current: <strong
|
|
32845
|
-
<div id="tina4-ver-latest"
|
|
32845
|
+
<div id="tina4-ver-body">
|
|
32846
|
+
<div>Current: <strong class="t4-ok">v${ctx.version}</strong></div>
|
|
32847
|
+
<div id="tina4-ver-latest" class="t4-dim">Checking for updates...</div>
|
|
32846
32848
|
</div>
|
|
32847
32849
|
</div>
|
|
32848
|
-
<span
|
|
32850
|
+
<span class="t4-green">${method}</span>
|
|
32849
32851
|
<span>${path8}</span>
|
|
32850
|
-
<span
|
|
32851
|
-
<span
|
|
32852
|
-
<span
|
|
32853
|
-
<span
|
|
32854
|
-
<a href="#"
|
|
32855
|
-
<span
|
|
32852
|
+
<span class="t4-arrow">→ ${matchedPattern}</span>
|
|
32853
|
+
<span class="t4-yellow">req:${ctx.requestId}</span>
|
|
32854
|
+
<span class="t4-blue">${ctx.routeCount} routes</span>
|
|
32855
|
+
<span class="t4-dim">Node.js ${nodeVersion}</span>
|
|
32856
|
+
<a href="#" id="tina4-dash-link" class="t4-dash">Dashboard ↗</a>
|
|
32857
|
+
<span id="tina4-bar-close" class="t4-x t4-bar-close">✕</span>
|
|
32856
32858
|
</div>
|
|
32857
|
-
<script
|
|
32858
|
-
|
|
32859
|
-
|
|
32860
|
-
|
|
32861
|
-
|
|
32862
|
-
|
|
32859
|
+
<script src="/__dev/toolbar.js"></script>`;
|
|
32860
|
+
}
|
|
32861
|
+
function toolbarCss() {
|
|
32862
|
+
return `#tina4-dev-toolbar{position:fixed;bottom:0;left:0;right:0;background:#333;color:#fff;font-family:monospace;font-size:12px;padding:6px 16px;z-index:99999;display:flex;align-items:center;gap:16px}
|
|
32863
|
+
#tina4-dev-toolbar a{text-decoration:none}
|
|
32864
|
+
#tina4-ver-btn{color:#2e7d32;font-weight:bold;cursor:pointer;text-decoration:underline dotted}
|
|
32865
|
+
#tina4-ver-modal{display:none;position:fixed;bottom:3rem;left:1rem;background:#1e1e2e;border:1px solid #2e7d32;border-radius:8px;padding:16px 20px;z-index:100000;min-width:320px;box-shadow:0 8px 32px rgba(0,0,0,.5);font-family:monospace;font-size:13px;color:#cdd6f4}
|
|
32866
|
+
.t4-modal-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:12px}
|
|
32867
|
+
.t4-modal-title{color:#89b4fa}
|
|
32868
|
+
#tina4-ver-body{line-height:1.8}
|
|
32869
|
+
.t4-x{cursor:pointer;color:#888}
|
|
32870
|
+
.t4-bar-close{margin-left:8px}
|
|
32871
|
+
.t4-green{color:#4caf50}
|
|
32872
|
+
.t4-dim{color:#888}
|
|
32873
|
+
.t4-arrow{color:#666}
|
|
32874
|
+
.t4-yellow{color:#ffeb3b}
|
|
32875
|
+
.t4-blue{color:#90caf9}
|
|
32876
|
+
.t4-ok{color:#a6e3a1}
|
|
32877
|
+
.t4-warn{color:#f9e2af}
|
|
32878
|
+
.t4-err{color:#f38ba8}
|
|
32879
|
+
.t4-purple{color:#cba6f7}
|
|
32880
|
+
.t4-link{color:#89b4fa}
|
|
32881
|
+
.t4-code{background:#313244;padding:2px 6px;border-radius:3px}
|
|
32882
|
+
.t4-note{margin-top:6px}
|
|
32883
|
+
.t4-dash{color:#ef9a9a;margin-left:auto;cursor:pointer}
|
|
32884
|
+
#tina4-dev-panel{position:fixed;top:3rem;left:0;right:0;bottom:2rem;z-index:99998;transition:all .2s}
|
|
32885
|
+
#tina4-dev-panel iframe{width:100%;height:100%;border:1px solid #2e7d32;border-radius:.5rem;box-shadow:0 8px 32px rgba(0,0,0,.5);background:#0f172a}`;
|
|
32886
|
+
}
|
|
32887
|
+
function toolbarJs() {
|
|
32888
|
+
return `(function () {
|
|
32889
|
+
var bar = document.getElementById('tina4-dev-toolbar');
|
|
32890
|
+
if (!bar) { return; }
|
|
32891
|
+
|
|
32892
|
+
var modal = document.getElementById('tina4-ver-modal');
|
|
32893
|
+
function upToDate(el, latest) {
|
|
32894
|
+
el.className = 't4-ok';
|
|
32895
|
+
el.innerHTML = 'Latest: <strong class="t4-ok">v' + latest + '</strong> — You are up to date!';
|
|
32896
|
+
}
|
|
32897
|
+
function checkVersion() {
|
|
32898
|
+
if (modal.style.display === 'block') { modal.style.display = 'none'; return; }
|
|
32899
|
+
modal.style.display = 'block';
|
|
32900
|
+
var el = document.getElementById('tina4-ver-latest');
|
|
32901
|
+
el.className = 't4-dim';
|
|
32902
|
+
el.textContent = 'Checking for updates...';
|
|
32903
|
+
fetch('/__dev/api/version-check').then(function (r) { return r.json(); }).then(function (d) {
|
|
32904
|
+
var latest = d.latest, current = d.current;
|
|
32905
|
+
if (latest === current) { upToDate(el, latest); return; }
|
|
32906
|
+
var cP = current.split('.').map(Number), lP = latest.split('.').map(Number);
|
|
32907
|
+
var isNewer = false, i, c, l;
|
|
32908
|
+
for (i = 0; i < Math.max(cP.length, lP.length); i++) { c = cP[i] || 0; l = lP[i] || 0; if (l > c) { isNewer = true; break; } if (l < c) { break; } }
|
|
32909
|
+
var isAhead = false;
|
|
32910
|
+
if (!isNewer) { for (i = 0; i < Math.max(cP.length, lP.length); i++) { var c2 = cP[i] || 0, l2 = lP[i] || 0; if (c2 > l2) { isAhead = true; break; } if (c2 < l2) { break; } } }
|
|
32911
|
+
if (isNewer) {
|
|
32912
|
+
var breaking = (lP[0] !== cP[0] || lP[1] !== cP[1]);
|
|
32913
|
+
el.className = '';
|
|
32914
|
+
el.innerHTML = 'Latest: <strong class="t4-warn">v' + latest + '</strong>';
|
|
32915
|
+
if (breaking) {
|
|
32916
|
+
el.innerHTML += '<div class="t4-err t4-note">⚠ Major/minor version change — check the <a href="https://github.com/tina4stack/tina4-nodejs/releases" target="_blank" class="t4-link">changelog</a> for breaking changes before upgrading.</div>';
|
|
32917
|
+
} else {
|
|
32918
|
+
el.innerHTML += '<div class="t4-warn t4-note">Patch update available. Run: <code class="t4-code">npm install tina4-nodejs@latest</code></div>';
|
|
32919
|
+
}
|
|
32920
|
+
} else if (isAhead) {
|
|
32921
|
+
el.className = 't4-purple';
|
|
32922
|
+
el.innerHTML = 'You are running <strong class="t4-purple">v' + current + '</strong> (ahead of npm <strong>v' + latest + '</strong> — not yet published).';
|
|
32923
|
+
} else {
|
|
32924
|
+
upToDate(el, latest);
|
|
32925
|
+
}
|
|
32926
|
+
}).catch(function () {
|
|
32927
|
+
el.className = 't4-err';
|
|
32928
|
+
el.textContent = 'Could not check for updates (offline?)';
|
|
32929
|
+
});
|
|
32930
|
+
}
|
|
32931
|
+
var verBtn = document.getElementById('tina4-ver-btn');
|
|
32932
|
+
if (verBtn) { verBtn.addEventListener('click', checkVersion); }
|
|
32933
|
+
var verClose = document.getElementById('tina4-ver-close');
|
|
32934
|
+
if (verClose) { verClose.addEventListener('click', function () { modal.style.display = 'none'; }); }
|
|
32935
|
+
var barClose = document.getElementById('tina4-bar-close');
|
|
32936
|
+
if (barClose) { barClose.addEventListener('click', function () { bar.style.display = 'none'; }); }
|
|
32937
|
+
|
|
32863
32938
|
var STATE_KEY = 'tina4_dev_overlay_open';
|
|
32864
32939
|
function buildOverlay() {
|
|
32865
32940
|
var c = document.createElement('div');
|
|
32866
32941
|
c.id = 'tina4-dev-panel';
|
|
32867
|
-
c.style.cssText = 'position:fixed;top:3rem;left:0;right:0;bottom:2rem;z-index:99998;transition:all 0.2s';
|
|
32868
32942
|
var f = document.createElement('iframe');
|
|
32869
32943
|
f.src = '/__dev';
|
|
32870
|
-
f.style.cssText = 'width:100%;height:100%;border:1px solid #2e7d32;border-radius:0.5rem;box-shadow:0 8px 32px rgba(0,0,0,0.5);background:#0f172a';
|
|
32871
32944
|
c.appendChild(f);
|
|
32872
32945
|
document.body.appendChild(c);
|
|
32873
32946
|
return c;
|
|
32874
32947
|
}
|
|
32875
|
-
|
|
32876
|
-
if (e) e.preventDefault();
|
|
32948
|
+
function toggleOverlay(e) {
|
|
32949
|
+
if (e) { e.preventDefault(); }
|
|
32877
32950
|
var p = document.getElementById('tina4-dev-panel');
|
|
32878
32951
|
if (p) {
|
|
32879
32952
|
var hide = p.style.display !== 'none';
|
|
@@ -32883,137 +32956,61 @@ function renderToolbarHtml(ctx) {
|
|
|
32883
32956
|
}
|
|
32884
32957
|
buildOverlay();
|
|
32885
32958
|
try { localStorage.setItem(STATE_KEY, '1'); } catch (_) {}
|
|
32886
|
-
};
|
|
32887
|
-
function restoreIfOpen() {
|
|
32888
|
-
try {
|
|
32889
|
-
if (location.pathname.indexOf('/__dev') === 0) return;
|
|
32890
|
-
if (localStorage.getItem(STATE_KEY) === '1' && !document.getElementById('tina4-dev-panel')) {
|
|
32891
|
-
buildOverlay();
|
|
32892
|
-
}
|
|
32893
|
-
} catch (_) {}
|
|
32894
32959
|
}
|
|
32895
|
-
|
|
32896
|
-
|
|
32897
|
-
|
|
32898
|
-
|
|
32899
|
-
|
|
32900
|
-
|
|
32901
|
-
|
|
32902
|
-
|
|
32903
|
-
|
|
32904
|
-
|
|
32905
|
-
if(
|
|
32906
|
-
|
|
32907
|
-
var
|
|
32908
|
-
|
|
32909
|
-
|
|
32910
|
-
|
|
32911
|
-
|
|
32912
|
-
|
|
32913
|
-
|
|
32914
|
-
|
|
32915
|
-
|
|
32916
|
-
el.innerHTML='Latest: <strong style="color:#a6e3a1;">v'+latest+'</strong> — You are up to date!';
|
|
32917
|
-
el.style.color='#a6e3a1';
|
|
32918
|
-
}else{
|
|
32919
|
-
var cParts=current.split('.').map(Number);
|
|
32920
|
-
var lParts=latest.split('.').map(Number);
|
|
32921
|
-
var isNewer=false;
|
|
32922
|
-
for(var i=0;i<Math.max(cParts.length,lParts.length);i++){
|
|
32923
|
-
var c=cParts[i]||0,l=lParts[i]||0;
|
|
32924
|
-
if(l>c){isNewer=true;break;}
|
|
32925
|
-
if(l<c)break;
|
|
32926
|
-
}
|
|
32927
|
-
var isAhead=false;
|
|
32928
|
-
if(!isNewer){for(var i=0;i<Math.max(cParts.length,lParts.length);i++){var c2=cParts[i]||0,l2=lParts[i]||0;if(c2>l2){isAhead=true;break;}if(c2<l2)break;}}
|
|
32929
|
-
if(isNewer){
|
|
32930
|
-
var breaking=(lParts[0]!==cParts[0]||lParts[1]!==cParts[1]);
|
|
32931
|
-
el.innerHTML='Latest: <strong style="color:#f9e2af;">v'+latest+'</strong>';
|
|
32932
|
-
if(breaking){
|
|
32933
|
-
el.innerHTML+='<div style="color:#f38ba8;margin-top:6px;">⚠ Major/minor version change — check the <a href="https://github.com/tina4stack/tina4-nodejs/releases" target="_blank" style="color:#89b4fa;">changelog</a> for breaking changes before upgrading.</div>';
|
|
32934
|
-
}else{
|
|
32935
|
-
el.innerHTML+='<div style="color:#f9e2af;margin-top:6px;">Patch update available. Run: <code style="background:#313244;padding:2px 6px;border-radius:3px;">npm install tina4-nodejs@latest</code></div>';
|
|
32936
|
-
}
|
|
32937
|
-
}else if(isAhead){
|
|
32938
|
-
el.innerHTML='You are running <strong style="color:#cba6f7;">v'+current+'</strong> (ahead of npm <strong>v'+latest+'</strong> — not yet published).';
|
|
32939
|
-
el.style.color='#cba6f7';
|
|
32940
|
-
}else{
|
|
32941
|
-
el.innerHTML='Latest: <strong style="color:#a6e3a1;">v'+latest+'</strong> — You are up to date!';
|
|
32942
|
-
el.style.color='#a6e3a1';
|
|
32943
|
-
}
|
|
32944
|
-
}
|
|
32945
|
-
})
|
|
32946
|
-
.catch(function(){
|
|
32947
|
-
el.innerHTML='Could not check for updates (offline?)';
|
|
32948
|
-
el.style.color='#f38ba8';
|
|
32949
|
-
});
|
|
32950
|
-
}
|
|
32951
|
-
</script>
|
|
32952
|
-
<script>
|
|
32953
|
-
(function(){
|
|
32954
|
-
// WebSocket-primary dev reloader. The running server re-imports changed
|
|
32955
|
-
// src/ routes in-process and pushes a {type,file,mtime} message over
|
|
32956
|
-
// /__dev_reload \u2014 no respawn, instant refresh. The mtime poll below is a
|
|
32957
|
-
// FALLBACK only, started when the socket is down and stopped on connect.
|
|
32958
|
-
var _t4_css_exts=['.css','.scss'],_t4_debounce=null;
|
|
32959
|
-
var _t4_interval=3000;
|
|
32960
|
-
var _t4_ws=null,_t4_poll_timer=null,_t4_mtime=null;
|
|
32961
|
-
function _t4_apply(d){
|
|
32962
|
-
d=d||{};
|
|
32963
|
-
var f=d.file||'',t=d.type||'';
|
|
32964
|
-
var isCss=t==='css'||_t4_css_exts.some(function(e){return f.endsWith(e)});
|
|
32965
|
-
if(isCss){
|
|
32966
|
-
var links=document.querySelectorAll('link[rel="stylesheet"]');
|
|
32967
|
-
links.forEach(function(l){
|
|
32968
|
-
var href=l.getAttribute('href');
|
|
32969
|
-
if(href){l.setAttribute('href',href.split('?')[0]+'?_t4='+(d.mtime||Date.now()))}
|
|
32960
|
+
var dash = document.getElementById('tina4-dash-link');
|
|
32961
|
+
if (dash) { dash.addEventListener('click', toggleOverlay); }
|
|
32962
|
+
try {
|
|
32963
|
+
if (location.pathname.indexOf('/__dev') !== 0
|
|
32964
|
+
&& localStorage.getItem(STATE_KEY) === '1'
|
|
32965
|
+
&& !document.getElementById('tina4-dev-panel')) {
|
|
32966
|
+
buildOverlay();
|
|
32967
|
+
}
|
|
32968
|
+
} catch (_) {}
|
|
32969
|
+
|
|
32970
|
+
if (bar.getAttribute('data-reload') !== '1') { return; }
|
|
32971
|
+
var cssExts = ['.css', '.scss'], debounce = null, interval = 3000;
|
|
32972
|
+
var ws = null, pollTimer = null, mtime = null;
|
|
32973
|
+
function apply(d) {
|
|
32974
|
+
d = d || {};
|
|
32975
|
+
var f = d.file || '', t = d.type || '';
|
|
32976
|
+
var isCss = t === 'css' || cssExts.some(function (e) { return f.endsWith(e); });
|
|
32977
|
+
if (isCss) {
|
|
32978
|
+
document.querySelectorAll('link[rel="stylesheet"]').forEach(function (l) {
|
|
32979
|
+
var href = l.getAttribute('href');
|
|
32980
|
+
if (href) { l.setAttribute('href', href.split('?')[0] + '?_t4=' + (d.mtime || Date.now())); }
|
|
32970
32981
|
});
|
|
32971
|
-
}else{
|
|
32982
|
+
} else {
|
|
32972
32983
|
location.reload();
|
|
32973
32984
|
}
|
|
32974
32985
|
}
|
|
32975
|
-
function
|
|
32976
|
-
fetch('/__dev/api/mtime').then(function(r){return r.json()}).then(function(d){
|
|
32977
|
-
|
|
32978
|
-
|
|
32979
|
-
|
|
32980
|
-
|
|
32981
|
-
|
|
32982
|
-
|
|
32983
|
-
|
|
32984
|
-
|
|
32985
|
-
|
|
32986
|
-
|
|
32987
|
-
|
|
32988
|
-
|
|
32989
|
-
|
|
32990
|
-
|
|
32991
|
-
|
|
32992
|
-
|
|
32993
|
-
|
|
32994
|
-
if(_t4_poll_timer){clearInterval(_t4_poll_timer);_t4_poll_timer=null;}
|
|
32995
|
-
}
|
|
32996
|
-
function _t4_connect(){
|
|
32997
|
-
var url=(location.protocol==='https:'?'wss':'ws')+'://'+location.host+'/__dev_reload';
|
|
32998
|
-
try{_t4_ws=new WebSocket(url);}catch(_){_t4_startPoll();return;}
|
|
32999
|
-
_t4_ws.addEventListener('open',function(){_t4_stopPoll();});
|
|
33000
|
-
_t4_ws.addEventListener('message',function(ev){
|
|
33001
|
-
var d=null;
|
|
33002
|
-
try{d=typeof ev.data==='string'?JSON.parse(ev.data):null;}catch(_){}
|
|
33003
|
-
if(!d)return;
|
|
33004
|
-
if(d.type==='reload'||d.type==='change'||d.type==='css'){
|
|
33005
|
-
if(_t4_debounce)clearTimeout(_t4_debounce);
|
|
33006
|
-
_t4_debounce=setTimeout(function(){_t4_apply(d);},150);
|
|
32986
|
+
function poll() {
|
|
32987
|
+
fetch('/__dev/api/mtime').then(function (r) { return r.json(); }).then(function (d) {
|
|
32988
|
+
if (mtime === null) { mtime = d.mtime; return; }
|
|
32989
|
+
if (d.mtime !== mtime) { mtime = d.mtime; if (debounce) { clearTimeout(debounce); } debounce = setTimeout(function () { apply(d); }, 500); }
|
|
32990
|
+
}).catch(function () {});
|
|
32991
|
+
}
|
|
32992
|
+
function startPoll() { if (pollTimer) { return; } mtime = null; pollTimer = setInterval(poll, interval); }
|
|
32993
|
+
function stopPoll() { if (pollTimer) { clearInterval(pollTimer); pollTimer = null; } }
|
|
32994
|
+
function connect() {
|
|
32995
|
+
var url = (location.protocol === 'https:' ? 'wss' : 'ws') + '://' + location.host + '/__dev_reload';
|
|
32996
|
+
try { ws = new WebSocket(url); } catch (_) { startPoll(); return; }
|
|
32997
|
+
ws.addEventListener('open', function () { stopPoll(); });
|
|
32998
|
+
ws.addEventListener('message', function (ev) {
|
|
32999
|
+
var d = null;
|
|
33000
|
+
try { d = typeof ev.data === 'string' ? JSON.parse(ev.data) : null; } catch (_) {}
|
|
33001
|
+
if (!d) { return; }
|
|
33002
|
+
if (d.type === 'reload' || d.type === 'change' || d.type === 'css') {
|
|
33003
|
+
if (debounce) { clearTimeout(debounce); }
|
|
33004
|
+
debounce = setTimeout(function () { apply(d); }, 150);
|
|
33007
33005
|
}
|
|
33008
33006
|
});
|
|
33009
|
-
|
|
33010
|
-
|
|
33007
|
+
ws.addEventListener('close', function () { ws = null; startPoll(); setTimeout(connect, 2000); });
|
|
33008
|
+
ws.addEventListener('error', function () { try { ws && ws.close(); } catch (_) {} });
|
|
33011
33009
|
}
|
|
33012
|
-
|
|
33013
|
-
})()
|
|
33014
|
-
</script>`;
|
|
33010
|
+
connect();
|
|
33011
|
+
})();`;
|
|
33015
33012
|
}
|
|
33016
|
-
var cpuCount, DEV_SAFE_METHODS, DEV_MCP_PREFIXES, DEV_SECRET_BASENAMES, DEV_SECRET_SUFFIXES, MessageLog, RequestInspector, ErrorTracker, DevMailboxStore, DevQueue, WsTracker, DevAdmin, handleDashboard, _reloadMtime, _reloadFile, handleMtime, handleReload, handleMessages, handleMessagesClear, handleRequests, handleRequestsClear, handleSystem, handleMessagesSearch, handleQueue, handleQueueTopics, handleQueueDeadLetters, handleQueueRetry, handleQueuePurge, handleQueueReplay, handleMailbox, handleMailboxRead, handleMailboxSeed, handleMailboxClear, handleTable, handleTables, handleSeed, handleQuery, handleBroken, handleBrokenResolve, handleBrokenClear, handleWebsockets, handleWebsocketsDisconnect, handleTool, handleChat, DEFAULT_MCP_URL, handleGroundingStatus, handleGroundingToken, handleMigrate, handleSeedRun, handleTest, handleThreads, handleThreadsSub, handleConnections, handleConnectionsTest, handleConnectionsSave, __devAdminFilename, __devAdminDirname, handleGalleryList, handleVersionCheck, handleThoughts, handleSuperviseStub, handleExecute, DEV_FILES_IGNORED, handleFiles, DEV_ADMIN_LANG_MAP, handleFileRead, handleFileSave, handleFileRaw, handleFileRename, handleFileDelete, handleDepsSearch, handleDepsInstall, handleGitStatus, handleMcpTools, handleMcpCall, handleMcpStreamable, handleMcpDelete, handleMcpGet405, handleMcpLegacyMessage, handleMcpSse, handleScaffoldList, handleScaffoldRun, handlePlanCurrent, handlePlanList, handlePlanCreate, handlePlanSwitch, handlePlanCompleteStep, handlePlanAddStep, handlePlanNote, handlePlanArchive, handlePlanRead, handlePlanFlesh, handleIndexRebuild, handleIndexSearch, handleIndexFile, handleIndexOverview, handleDocsSearch, handleDocsClass, handleDocsMethod, handleDocsIndex, handleDocsWellKnown, handleDevAdminJs;
|
|
33013
|
+
var cpuCount, DEV_SAFE_METHODS, DEV_MCP_PREFIXES, DEV_SECRET_BASENAMES, DEV_SECRET_SUFFIXES, MessageLog, RequestInspector, ErrorTracker, DevMailboxStore, DevQueue, WsTracker, DevAdmin, handleDashboard, _reloadMtime, _reloadFile, handleMtime, handleReload, handleMessages, handleMessagesClear, handleRequests, handleRequestsClear, handleSystem, handleMessagesSearch, handleQueue, handleQueueTopics, handleQueueDeadLetters, handleQueueRetry, handleQueuePurge, handleQueueReplay, handleMailbox, handleMailboxRead, handleMailboxSeed, handleMailboxClear, handleTable, handleTables, handleSeed, handleQuery, handleBroken, handleBrokenResolve, handleBrokenClear, handleWebsockets, handleWebsocketsDisconnect, handleTool, handleChat, DEFAULT_MCP_URL, handleGroundingStatus, handleGroundingToken, handleMigrate, handleSeedRun, handleTest, handleThreads, handleThreadsSub, handleConnections, handleConnectionsTest, handleConnectionsSave, __devAdminFilename, __devAdminDirname, handleGalleryList, handleVersionCheck, handleThoughts, handleSuperviseStub, handleExecute, DEV_FILES_IGNORED, handleFiles, DEV_ADMIN_LANG_MAP, handleFileRead, handleFileSave, handleFileRaw, handleFileRename, handleFileDelete, handleDepsSearch, handleDepsInstall, handleGitStatus, handleMcpTools, handleMcpCall, handleMcpStreamable, handleMcpDelete, handleMcpGet405, handleMcpLegacyMessage, handleMcpSse, handleScaffoldList, handleScaffoldRun, handlePlanCurrent, handlePlanList, handlePlanCreate, handlePlanSwitch, handlePlanCompleteStep, handlePlanAddStep, handlePlanNote, handlePlanArchive, handlePlanRead, handlePlanFlesh, handleIndexRebuild, handleIndexSearch, handleIndexFile, handleIndexOverview, handleDocsSearch, handleDocsClass, handleDocsMethod, handleDocsIndex, handleDocsWellKnown, handleDevAdminJs, handleToolbarCss, handleToolbarJs;
|
|
33017
33014
|
var init_devAdmin = __esm({
|
|
33018
33015
|
"src/devAdmin.ts"() {
|
|
33019
33016
|
"use strict";
|
|
@@ -33479,7 +33476,12 @@ var init_devAdmin = __esm({
|
|
|
33479
33476
|
{ method: "GET", pattern: "/__dev/api/docs/index", handler: handleDocsIndex },
|
|
33480
33477
|
{ method: "GET", pattern: "/__dev/api/docs/.well-known.json", handler: handleDocsWellKnown },
|
|
33481
33478
|
// JS asset
|
|
33482
|
-
{ method: "GET", pattern: "/__dev/js/tina4-dev-admin.min.js", handler: handleDevAdminJs }
|
|
33479
|
+
{ method: "GET", pattern: "/__dev/js/tina4-dev-admin.min.js", handler: handleDevAdminJs },
|
|
33480
|
+
// Dev-toolbar assets — served as external CSS/JS so the injected toolbar is
|
|
33481
|
+
// CSP-clean (no inline style=, onclick=, or <script>) under a strict
|
|
33482
|
+
// `default-src 'self'`. Parity with PHP's /__dev/toolbar.css + toolbar.js.
|
|
33483
|
+
{ method: "GET", pattern: "/__dev/toolbar.css", handler: handleToolbarCss },
|
|
33484
|
+
{ method: "GET", pattern: "/__dev/toolbar.js", handler: handleToolbarJs }
|
|
33483
33485
|
];
|
|
33484
33486
|
for (const route of routes) {
|
|
33485
33487
|
router.addRoute({
|
|
@@ -34915,6 +34917,14 @@ var init_devAdmin = __esm({
|
|
|
34915
34917
|
res.raw.writeHead(404, { "Content-Type": "text/plain" });
|
|
34916
34918
|
res.raw.end("tina4-dev-admin.min.js not found");
|
|
34917
34919
|
};
|
|
34920
|
+
handleToolbarCss = (_req, res) => {
|
|
34921
|
+
res.raw.writeHead(200, { "Content-Type": "text/css; charset=utf-8", "Cache-Control": "no-cache" });
|
|
34922
|
+
res.raw.end(toolbarCss());
|
|
34923
|
+
};
|
|
34924
|
+
handleToolbarJs = (_req, res) => {
|
|
34925
|
+
res.raw.writeHead(200, { "Content-Type": "application/javascript; charset=utf-8", "Cache-Control": "no-cache" });
|
|
34926
|
+
res.raw.end(toolbarJs());
|
|
34927
|
+
};
|
|
34918
34928
|
}
|
|
34919
34929
|
});
|
|
34920
34930
|
|
|
@@ -36776,7 +36786,11 @@ function injectIntoHtml(ctx, devToolbar, html) {
|
|
|
36776
36786
|
path: ctx.pathname,
|
|
36777
36787
|
matchedPattern: ctx.matchedPattern.value || ctx.pathname,
|
|
36778
36788
|
requestId: ctx.requestId,
|
|
36779
|
-
routeCount: ctx.router.getRoutes().length
|
|
36789
|
+
routeCount: ctx.router.getRoutes().length,
|
|
36790
|
+
// Suppress the live reloader on the AI/stable port (data-reload="0"); the
|
|
36791
|
+
// toolbar JS early-returns when data-reload !== "1". Mirrors PHP's
|
|
36792
|
+
// suppressReload flag.
|
|
36793
|
+
reload: !ctx.isAiPortRequest
|
|
36780
36794
|
};
|
|
36781
36795
|
return injectFeedbackWidget(ctx.req, injectDevToolbar(html, toolbarCtx));
|
|
36782
36796
|
}
|
|
@@ -38770,11 +38784,100 @@ function buildMultipartBody(boundary, fieldName, filename, fileContent, contentT
|
|
|
38770
38784
|
parts.push(Buffer.from(delimiter2 + "--" + crlf, "utf-8"));
|
|
38771
38785
|
return Buffer.concat(parts);
|
|
38772
38786
|
}
|
|
38773
|
-
|
|
38787
|
+
async function* parseLineStream(chunks) {
|
|
38788
|
+
const decoder = new TextDecoder("utf-8");
|
|
38789
|
+
let buffer = "";
|
|
38790
|
+
for await (const chunk of chunks) {
|
|
38791
|
+
buffer += decoder.decode(chunk, { stream: true });
|
|
38792
|
+
let idx;
|
|
38793
|
+
while ((idx = buffer.indexOf("\n")) >= 0) {
|
|
38794
|
+
let line = buffer.slice(0, idx);
|
|
38795
|
+
buffer = buffer.slice(idx + 1);
|
|
38796
|
+
if (line.endsWith("\r")) {
|
|
38797
|
+
line = line.slice(0, -1);
|
|
38798
|
+
}
|
|
38799
|
+
yield line;
|
|
38800
|
+
}
|
|
38801
|
+
}
|
|
38802
|
+
buffer += decoder.decode();
|
|
38803
|
+
if (buffer.length > 0) {
|
|
38804
|
+
if (buffer.endsWith("\r")) {
|
|
38805
|
+
buffer = buffer.slice(0, -1);
|
|
38806
|
+
}
|
|
38807
|
+
yield buffer;
|
|
38808
|
+
}
|
|
38809
|
+
}
|
|
38810
|
+
async function* parseSseStream(lines) {
|
|
38811
|
+
let dataParts = [];
|
|
38812
|
+
let event;
|
|
38813
|
+
let id;
|
|
38814
|
+
let retry;
|
|
38815
|
+
let has = false;
|
|
38816
|
+
const emit = () => {
|
|
38817
|
+
if (!has) return null;
|
|
38818
|
+
const ev = { data: dataParts.join("\n") };
|
|
38819
|
+
if (event !== void 0) ev.event = event;
|
|
38820
|
+
if (id !== void 0) ev.id = id;
|
|
38821
|
+
if (retry !== void 0) ev.retry = retry;
|
|
38822
|
+
return ev;
|
|
38823
|
+
};
|
|
38824
|
+
const reset2 = () => {
|
|
38825
|
+
dataParts = [];
|
|
38826
|
+
event = void 0;
|
|
38827
|
+
id = void 0;
|
|
38828
|
+
retry = void 0;
|
|
38829
|
+
has = false;
|
|
38830
|
+
};
|
|
38831
|
+
for await (const line of lines) {
|
|
38832
|
+
if (line === "") {
|
|
38833
|
+
const ev = emit();
|
|
38834
|
+
if (ev) yield ev;
|
|
38835
|
+
reset2();
|
|
38836
|
+
continue;
|
|
38837
|
+
}
|
|
38838
|
+
if (line.startsWith(":")) continue;
|
|
38839
|
+
const colon = line.indexOf(":");
|
|
38840
|
+
const field = colon < 0 ? line : line.slice(0, colon);
|
|
38841
|
+
let value = colon < 0 ? "" : line.slice(colon + 1);
|
|
38842
|
+
if (value.startsWith(" ")) value = value.slice(1);
|
|
38843
|
+
switch (field) {
|
|
38844
|
+
case "data":
|
|
38845
|
+
dataParts.push(value);
|
|
38846
|
+
has = true;
|
|
38847
|
+
break;
|
|
38848
|
+
case "event":
|
|
38849
|
+
event = value;
|
|
38850
|
+
has = true;
|
|
38851
|
+
break;
|
|
38852
|
+
case "id":
|
|
38853
|
+
id = value;
|
|
38854
|
+
has = true;
|
|
38855
|
+
break;
|
|
38856
|
+
case "retry": {
|
|
38857
|
+
const parsed = Number(value);
|
|
38858
|
+
if (Number.isFinite(parsed) && parsed >= 0) {
|
|
38859
|
+
retry = parsed;
|
|
38860
|
+
has = true;
|
|
38861
|
+
}
|
|
38862
|
+
break;
|
|
38863
|
+
}
|
|
38864
|
+
}
|
|
38865
|
+
}
|
|
38866
|
+
const trailing = emit();
|
|
38867
|
+
if (trailing) yield trailing;
|
|
38868
|
+
}
|
|
38869
|
+
var ApiStreamError, RETRY_STATUSES, DOWNLOAD_CHUNK_SIZE, MAX_REDIRECTS, STRIP_ON_CROSS_ORIGIN, MIME_BY_EXT, Api;
|
|
38774
38870
|
var init_api = __esm({
|
|
38775
38871
|
"src/api.ts"() {
|
|
38776
38872
|
"use strict";
|
|
38777
38873
|
init_version();
|
|
38874
|
+
ApiStreamError = class extends Error {
|
|
38875
|
+
constructor(message, status2 = null) {
|
|
38876
|
+
super(message);
|
|
38877
|
+
this.status = status2;
|
|
38878
|
+
this.name = "ApiStreamError";
|
|
38879
|
+
}
|
|
38880
|
+
};
|
|
38778
38881
|
RETRY_STATUSES = /* @__PURE__ */ new Set([429, 500, 502, 503, 504]);
|
|
38779
38882
|
DOWNLOAD_CHUNK_SIZE = 64 * 1024;
|
|
38780
38883
|
MAX_REDIRECTS = 10;
|
|
@@ -39093,7 +39196,130 @@ var init_api = __esm({
|
|
|
39093
39196
|
}
|
|
39094
39197
|
return { http_code: code, headers: respHeaders, error: null, path: destPath };
|
|
39095
39198
|
}
|
|
39199
|
+
/**
|
|
39200
|
+
* Stream a response body as raw bytes. Yields the chunks the transport
|
|
39201
|
+
* delivers, in order, never buffered whole. Ends cleanly on EOF and
|
|
39202
|
+
* throws on a transport failure or a non-2xx status (body drained
|
|
39203
|
+
* first). No JSON decoding, no line splitting, no framing —
|
|
39204
|
+
* {@link streamLines} and {@link streamSse} build on this primitive.
|
|
39205
|
+
*
|
|
39206
|
+
* Closing the iterator before EOF (a `break` out of a `for await`)
|
|
39207
|
+
* destroys the underlying socket, so a caller who takes only the
|
|
39208
|
+
* first few chunks never leaks the connection.
|
|
39209
|
+
*
|
|
39210
|
+
* `opts.timeout` bounds the whole stream duration (default
|
|
39211
|
+
* `TINA4_API_TIMEOUT` or the client `timeout`); `opts.connectTimeout`
|
|
39212
|
+
* bounds just the connect + headers phase (default
|
|
39213
|
+
* `TINA4_API_CONNECT_TIMEOUT` or 10s).
|
|
39214
|
+
*/
|
|
39215
|
+
async *streamBytes(path8, opts = {}) {
|
|
39216
|
+
const url = this.buildUrl(path8);
|
|
39217
|
+
const method = (opts.method ?? "GET").toUpperCase();
|
|
39218
|
+
const contentType = opts.contentType ?? "application/json";
|
|
39219
|
+
const { headers, data } = this.buildRequest(method, contentType, opts.body, opts.headers);
|
|
39220
|
+
const totalSec = this.streamSeconds(opts.timeout, "TINA4_API_TIMEOUT", this.timeout);
|
|
39221
|
+
const connectSec = this.streamSeconds(opts.connectTimeout, "TINA4_API_CONNECT_TIMEOUT", 10);
|
|
39222
|
+
const opened = await this.openStreamRequest(method, url, headers, data, connectSec);
|
|
39223
|
+
const res = opened.res;
|
|
39224
|
+
const status2 = res.statusCode ?? 0;
|
|
39225
|
+
this.storeCookies(res.headers["set-cookie"]);
|
|
39226
|
+
if (status2 < 200 || status2 >= 300) {
|
|
39227
|
+
res.resume();
|
|
39228
|
+
throw new ApiStreamError(`stream failed with HTTP ${status2}`, status2);
|
|
39229
|
+
}
|
|
39230
|
+
let totalTimer = null;
|
|
39231
|
+
if (totalSec > 0) {
|
|
39232
|
+
totalTimer = setTimeout(() => {
|
|
39233
|
+
res.destroy(new ApiStreamError(`stream total timeout after ${totalSec}s`, null));
|
|
39234
|
+
}, totalSec * 1e3);
|
|
39235
|
+
}
|
|
39236
|
+
try {
|
|
39237
|
+
for await (const chunk of res) {
|
|
39238
|
+
yield chunk;
|
|
39239
|
+
}
|
|
39240
|
+
} finally {
|
|
39241
|
+
if (totalTimer) clearTimeout(totalTimer);
|
|
39242
|
+
if (!res.destroyed) res.destroy();
|
|
39243
|
+
}
|
|
39244
|
+
}
|
|
39245
|
+
/**
|
|
39246
|
+
* Stream the response body as UTF-8 lines. Splits on LF or CRLF;
|
|
39247
|
+
* buffers a multibyte codepoint that lands across a chunk boundary;
|
|
39248
|
+
* yields a trailing line without a terminator on EOF. Built on
|
|
39249
|
+
* {@link streamBytes} plus the shared {@link parseLineStream}.
|
|
39250
|
+
*/
|
|
39251
|
+
async *streamLines(path8, opts = {}) {
|
|
39252
|
+
yield* parseLineStream(this.streamBytes(path8, opts));
|
|
39253
|
+
}
|
|
39254
|
+
/**
|
|
39255
|
+
* Stream the response as SSE (Server-Sent Events). Yields one
|
|
39256
|
+
* {@link SseEvent} per event boundary (blank line) or on EOF for a
|
|
39257
|
+
* trailing event. `data:[DONE]` is delivered as an ordinary event
|
|
39258
|
+
* with `data === "[DONE]"` and the iterator ends on the next EOF.
|
|
39259
|
+
* Built on {@link streamLines} plus the shared {@link parseSseStream}.
|
|
39260
|
+
*/
|
|
39261
|
+
async *streamSse(path8, opts = {}) {
|
|
39262
|
+
yield* parseSseStream(this.streamLines(path8, opts));
|
|
39263
|
+
}
|
|
39096
39264
|
// ── Internal helpers ──────────────────────────────────────────────
|
|
39265
|
+
/**
|
|
39266
|
+
* Resolve a stream duration from (in order): explicit `opts` field,
|
|
39267
|
+
* the named env var, then the fallback. Zero disables. A non-numeric
|
|
39268
|
+
* or negative env value warns via a fallback rather than throwing —
|
|
39269
|
+
* a bad env var must not brick every streaming call.
|
|
39270
|
+
*/
|
|
39271
|
+
streamSeconds(explicit, envName, fallback) {
|
|
39272
|
+
if (explicit !== void 0) {
|
|
39273
|
+
return Number.isFinite(explicit) && explicit >= 0 ? Number(explicit) : fallback;
|
|
39274
|
+
}
|
|
39275
|
+
const raw = process.env[envName];
|
|
39276
|
+
if (raw === void 0) return fallback;
|
|
39277
|
+
const n = Number(raw);
|
|
39278
|
+
return Number.isFinite(n) && n >= 0 ? n : fallback;
|
|
39279
|
+
}
|
|
39280
|
+
/**
|
|
39281
|
+
* Open a streaming HTTP request. Returns the raw
|
|
39282
|
+
* {@link http.IncomingMessage} once headers arrive. Redirects are NOT
|
|
39283
|
+
* followed on streams (a caller who needs a redirect should do a
|
|
39284
|
+
* regular GET first). Connect phase is bounded by `connectSec`;
|
|
39285
|
+
* body-phase timeout is applied by the caller (streamBytes) via
|
|
39286
|
+
* `res.destroy()`.
|
|
39287
|
+
*/
|
|
39288
|
+
openStreamRequest(method, url, headers, data, connectSec) {
|
|
39289
|
+
return new Promise((resolve20, reject) => {
|
|
39290
|
+
let parsed;
|
|
39291
|
+
try {
|
|
39292
|
+
parsed = new URL2(url);
|
|
39293
|
+
} catch (err) {
|
|
39294
|
+
reject(err instanceof Error ? err : new Error(String(err)));
|
|
39295
|
+
return;
|
|
39296
|
+
}
|
|
39297
|
+
const isHttps = parsed.protocol === "https:";
|
|
39298
|
+
const protocolModule = isHttps ? https : http;
|
|
39299
|
+
const options = {
|
|
39300
|
+
hostname: parsed.hostname,
|
|
39301
|
+
port: parsed.port || (isHttps ? 443 : 80),
|
|
39302
|
+
path: parsed.pathname + parsed.search,
|
|
39303
|
+
method,
|
|
39304
|
+
headers,
|
|
39305
|
+
timeout: connectSec > 0 ? connectSec * 1e3 : void 0
|
|
39306
|
+
};
|
|
39307
|
+
if (isHttps && this.ignoreSsl) {
|
|
39308
|
+
options.rejectUnauthorized = false;
|
|
39309
|
+
}
|
|
39310
|
+
const req2 = protocolModule.request(options, (res) => {
|
|
39311
|
+
resolve20({ res });
|
|
39312
|
+
});
|
|
39313
|
+
req2.on("timeout", () => {
|
|
39314
|
+
req2.destroy(new ApiStreamError(`stream connect timeout after ${connectSec}s`, null));
|
|
39315
|
+
});
|
|
39316
|
+
req2.on("error", (err) => {
|
|
39317
|
+
reject(err);
|
|
39318
|
+
});
|
|
39319
|
+
if (data) req2.write(data);
|
|
39320
|
+
req2.end();
|
|
39321
|
+
});
|
|
39322
|
+
}
|
|
39097
39323
|
buildUrl(path8) {
|
|
39098
39324
|
if (path8.startsWith("http://") || path8.startsWith("https://")) {
|
|
39099
39325
|
return path8;
|
|
@@ -41756,10 +41982,11 @@ export default class User {
|
|
|
41756
41982
|
// src/aiClient.ts
|
|
41757
41983
|
import http2 from "node:http";
|
|
41758
41984
|
import https2 from "node:https";
|
|
41759
|
-
var AiError, AiConfigError, AiTimeoutError, AiParseError, AiHTTPError, Ai;
|
|
41985
|
+
var AiError, AiConfigError, AiTimeoutError, AiParseError, AiHTTPError, Ai, AggregateState;
|
|
41760
41986
|
var init_aiClient = __esm({
|
|
41761
41987
|
"src/aiClient.ts"() {
|
|
41762
41988
|
"use strict";
|
|
41989
|
+
init_api();
|
|
41763
41990
|
AiError = class extends Error {
|
|
41764
41991
|
};
|
|
41765
41992
|
AiConfigError = class extends AiError {
|
|
@@ -41804,10 +42031,51 @@ var init_aiClient = __esm({
|
|
|
41804
42031
|
throw new AiParseError("AI provider returned a malformed embedding response");
|
|
41805
42032
|
}
|
|
41806
42033
|
}
|
|
42034
|
+
/**
|
|
42035
|
+
* Validate role + content shape. Content may be a string OR a non-empty
|
|
42036
|
+
* list of {type:'text'|'image', ...} parts (ADR-0060). Malformed parts
|
|
42037
|
+
* fail fast with AiConfigError, never reaching the wire.
|
|
42038
|
+
*/
|
|
41807
42039
|
static validateMessages(messages) {
|
|
41808
|
-
if (!Array.isArray(messages) || messages.length === 0
|
|
42040
|
+
if (!Array.isArray(messages) || messages.length === 0) {
|
|
41809
42041
|
throw new AiConfigError("AI messages must contain supported roles and string content");
|
|
41810
42042
|
}
|
|
42043
|
+
for (const message of messages) {
|
|
42044
|
+
if (!message || !["system", "user", "assistant"].includes(message.role)) {
|
|
42045
|
+
throw new AiConfigError("AI messages must contain supported roles and string content");
|
|
42046
|
+
}
|
|
42047
|
+
this.validateContent(message.content);
|
|
42048
|
+
}
|
|
42049
|
+
}
|
|
42050
|
+
static validateContent(content) {
|
|
42051
|
+
if (typeof content === "string") return;
|
|
42052
|
+
if (!Array.isArray(content) || content.length === 0) {
|
|
42053
|
+
throw new AiConfigError("AI message content must be a string or a non-empty list of parts");
|
|
42054
|
+
}
|
|
42055
|
+
for (const part of content) {
|
|
42056
|
+
if (!part || typeof part !== "object" || Array.isArray(part)) {
|
|
42057
|
+
throw new AiConfigError("AI content part must be an object with type and text/source");
|
|
42058
|
+
}
|
|
42059
|
+
const record = part;
|
|
42060
|
+
const partType = record.type;
|
|
42061
|
+
if (partType === "text") {
|
|
42062
|
+
if (typeof record.text !== "string") {
|
|
42063
|
+
throw new AiConfigError("AI text content part requires a string 'text' field");
|
|
42064
|
+
}
|
|
42065
|
+
} else if (partType === "image") {
|
|
42066
|
+
if (typeof record.source !== "string" || record.source.length === 0) {
|
|
42067
|
+
throw new AiConfigError("AI image content part requires a non-empty string 'source' field");
|
|
42068
|
+
}
|
|
42069
|
+
if (!record.source.startsWith("data:") && !record.source.startsWith("https://")) {
|
|
42070
|
+
throw new AiConfigError("AI image source must be a data: URI or an https:// URL");
|
|
42071
|
+
}
|
|
42072
|
+
if (record.source.startsWith("data:") && !/^data:[^;,\s]+;base64,[A-Za-z0-9+/=]+$/.test(record.source)) {
|
|
42073
|
+
throw new AiConfigError("AI image data URI must be data:<media_type>;base64,<payload>");
|
|
42074
|
+
}
|
|
42075
|
+
} else {
|
|
42076
|
+
throw new AiConfigError(`AI content part has unknown type '${String(partType)}'`);
|
|
42077
|
+
}
|
|
42078
|
+
}
|
|
41811
42079
|
}
|
|
41812
42080
|
static number(name, fallback, minimum) {
|
|
41813
42081
|
const value = process.env[name] === void 0 ? fallback : Number(process.env[name]);
|
|
@@ -41855,18 +42123,57 @@ var init_aiClient = __esm({
|
|
|
41855
42123
|
}
|
|
41856
42124
|
return headers;
|
|
41857
42125
|
}
|
|
42126
|
+
/**
|
|
42127
|
+
* Build the provider-specific request body from a Tina4-shaped message
|
|
42128
|
+
* list. Multimodal parts are translated per provider (ADR-0060):
|
|
42129
|
+
* - OpenAI/local: {type:'image_url', image_url:{url}}
|
|
42130
|
+
* - Anthropic: {type:'image', source:{type:'base64'|'url', ...}}
|
|
42131
|
+
* String content is preserved verbatim in the OpenAI/local shape and
|
|
42132
|
+
* likewise for Anthropic (both accept a bare string).
|
|
42133
|
+
*/
|
|
41858
42134
|
static chatBody(config, messages, options) {
|
|
41859
|
-
const
|
|
42135
|
+
const translate = (list) => list.map((message) => ({ role: message.role, content: this.translateContent(message.content, config.provider) }));
|
|
42136
|
+
const body = { model: config.model, messages: translate(messages), stream: options.stream ?? false };
|
|
41860
42137
|
if (options.temperature !== void 0) body.temperature = options.temperature;
|
|
41861
42138
|
if (options.maxTokens !== void 0) body.max_tokens = options.maxTokens;
|
|
41862
42139
|
if (config.provider === "anthropic") {
|
|
41863
|
-
const
|
|
41864
|
-
body.messages = messages.filter((message) => message.role !== "system");
|
|
42140
|
+
const systemParts = messages.filter((message) => message.role === "system").map((message) => typeof message.content === "string" ? message.content : this.contentToPlainText(message.content));
|
|
42141
|
+
body.messages = translate(messages.filter((message) => message.role !== "system"));
|
|
41865
42142
|
body.max_tokens = options.maxTokens ?? 1024;
|
|
41866
|
-
if (
|
|
42143
|
+
if (systemParts.length) body.system = systemParts.join("\n\n");
|
|
41867
42144
|
}
|
|
41868
42145
|
return body;
|
|
41869
42146
|
}
|
|
42147
|
+
/**
|
|
42148
|
+
* Translate one message content value into the provider's on-wire shape.
|
|
42149
|
+
* A plain string is passed through (both providers accept a string
|
|
42150
|
+
* content). A parts array becomes provider-native content blocks.
|
|
42151
|
+
*/
|
|
42152
|
+
static translateContent(content, provider) {
|
|
42153
|
+
if (typeof content === "string") return content;
|
|
42154
|
+
if (provider === "anthropic") {
|
|
42155
|
+
return content.map((part) => {
|
|
42156
|
+
if (part.type === "text") return { type: "text", text: part.text };
|
|
42157
|
+
if (part.source.startsWith("data:")) {
|
|
42158
|
+
const parsed = this.parseDataUri(part.source);
|
|
42159
|
+
return { type: "image", source: { type: "base64", media_type: parsed.mediaType, data: parsed.data } };
|
|
42160
|
+
}
|
|
42161
|
+
return { type: "image", source: { type: "url", url: part.source } };
|
|
42162
|
+
});
|
|
42163
|
+
}
|
|
42164
|
+
return content.map((part) => {
|
|
42165
|
+
if (part.type === "text") return { type: "text", text: part.text };
|
|
42166
|
+
return { type: "image_url", image_url: { url: part.source } };
|
|
42167
|
+
});
|
|
42168
|
+
}
|
|
42169
|
+
static parseDataUri(source) {
|
|
42170
|
+
const match = /^data:([^;,\s]+);base64,([A-Za-z0-9+/=]+)$/.exec(source);
|
|
42171
|
+
if (!match) throw new AiConfigError("AI image data URI must be data:<media_type>;base64,<payload>");
|
|
42172
|
+
return { mediaType: match[1], data: match[2] };
|
|
42173
|
+
}
|
|
42174
|
+
static contentToPlainText(parts) {
|
|
42175
|
+
return parts.filter((part) => part.type === "text").map((part) => part.text).join("\n\n");
|
|
42176
|
+
}
|
|
41870
42177
|
static open(config, deadline, headers, body) {
|
|
41871
42178
|
const remainingMs = deadline - performance.now();
|
|
41872
42179
|
if (remainingMs <= 0) return Promise.reject(new AiTimeoutError("AI total request timeout expired"));
|
|
@@ -41966,35 +42273,12 @@ var init_aiClient = __esm({
|
|
|
41966
42273
|
static async chatResponse(config, headers, body) {
|
|
41967
42274
|
return this.normalizeChat(config.provider, await this.requestJson(config, headers, body));
|
|
41968
42275
|
}
|
|
41969
|
-
|
|
41970
|
-
|
|
41971
|
-
|
|
41972
|
-
|
|
41973
|
-
|
|
41974
|
-
|
|
41975
|
-
throw new AiParseError("AI provider returned malformed stream data");
|
|
41976
|
-
}
|
|
41977
|
-
const text = provider === "anthropic" ? event.type === "content_block_delta" ? event.delta?.text : void 0 : event.choices?.[0]?.delta?.content;
|
|
41978
|
-
if (text !== void 0 && text !== null && typeof text !== "string") throw new AiParseError("AI provider returned malformed stream data");
|
|
41979
|
-
return { completed: false, text };
|
|
41980
|
-
}
|
|
41981
|
-
static async *streamData(response) {
|
|
41982
|
-
let buffer = "";
|
|
41983
|
-
for await (const chunk of response) {
|
|
41984
|
-
buffer += Buffer.from(chunk).toString("utf8");
|
|
41985
|
-
let newline;
|
|
41986
|
-
while ((newline = buffer.indexOf("\n")) >= 0) {
|
|
41987
|
-
const line = buffer.slice(0, newline).trim();
|
|
41988
|
-
buffer = buffer.slice(newline + 1);
|
|
41989
|
-
if (line.startsWith("data:")) yield line.slice(5).trim();
|
|
41990
|
-
}
|
|
41991
|
-
}
|
|
41992
|
-
}
|
|
41993
|
-
static streamError(error) {
|
|
41994
|
-
if (error instanceof AiError) return error;
|
|
41995
|
-
if (error instanceof Error && error.name === "AbortError") return new AiTimeoutError("AI total request timeout expired");
|
|
41996
|
-
return new AiHTTPError(`AI transport failed (${error instanceof Error ? error.name : "Error"})`);
|
|
41997
|
-
}
|
|
42276
|
+
/**
|
|
42277
|
+
* Stream the response through the shared {@link parseSseStream} framer
|
|
42278
|
+
* (ADR-0060 rule 5). Translates each SSE data payload into 0..N
|
|
42279
|
+
* {@link AiEvent}s: text_delta per chunk, tool_call aggregated per
|
|
42280
|
+
* index / block, exactly one done (or error) at the end.
|
|
42281
|
+
*/
|
|
41998
42282
|
static async *streamRequest(config, headers, body) {
|
|
41999
42283
|
const deadline = performance.now() + config.totalTimeout * 1e3;
|
|
42000
42284
|
let yielded = false;
|
|
@@ -42013,27 +42297,239 @@ var init_aiClient = __esm({
|
|
|
42013
42297
|
}
|
|
42014
42298
|
throw new AiHTTPError(`AI provider returned HTTP ${status2}`, status2);
|
|
42015
42299
|
}
|
|
42016
|
-
|
|
42017
|
-
|
|
42018
|
-
|
|
42019
|
-
|
|
42020
|
-
|
|
42021
|
-
|
|
42300
|
+
const response = opened.response;
|
|
42301
|
+
const chunks = this.responseChunks(response);
|
|
42302
|
+
const events = parseSseStream(parseLineStream(chunks));
|
|
42303
|
+
const aggregator = new AggregateState(config.provider);
|
|
42304
|
+
let done = false;
|
|
42305
|
+
try {
|
|
42306
|
+
for await (const sseEvent of events) {
|
|
42307
|
+
for (const emitted of aggregator.consume(sseEvent)) {
|
|
42308
|
+
yielded = true;
|
|
42309
|
+
yield emitted;
|
|
42310
|
+
if (emitted.type === "done" || emitted.type === "error") {
|
|
42311
|
+
done = true;
|
|
42312
|
+
break;
|
|
42313
|
+
}
|
|
42314
|
+
}
|
|
42315
|
+
if (done) break;
|
|
42316
|
+
}
|
|
42317
|
+
} catch (error) {
|
|
42318
|
+
if (yielded) {
|
|
42319
|
+
yielded = true;
|
|
42320
|
+
yield { type: "error", message: error instanceof AiParseError ? "AI provider returned malformed stream data" : `AI transport failed (${error instanceof Error ? error.name : "Error"})` };
|
|
42321
|
+
opened.cleanup();
|
|
42322
|
+
opened = null;
|
|
42323
|
+
return;
|
|
42022
42324
|
}
|
|
42023
|
-
|
|
42024
|
-
yielded = true;
|
|
42025
|
-
yield delta.text;
|
|
42325
|
+
throw error;
|
|
42026
42326
|
}
|
|
42027
42327
|
opened.cleanup();
|
|
42028
42328
|
opened = null;
|
|
42029
|
-
if (
|
|
42030
|
-
|
|
42329
|
+
if (done) return;
|
|
42330
|
+
if (yielded) {
|
|
42331
|
+
yield { type: "error", message: "AI provider stream ended before completion" };
|
|
42332
|
+
return;
|
|
42333
|
+
}
|
|
42334
|
+
throw new AiParseError("AI provider stream ended before completion");
|
|
42031
42335
|
} catch (error) {
|
|
42032
42336
|
opened?.cleanup();
|
|
42033
42337
|
const failure = this.streamError(error);
|
|
42034
|
-
if (
|
|
42338
|
+
if (yielded) {
|
|
42339
|
+
yield { type: "error", message: failure.message };
|
|
42340
|
+
return;
|
|
42341
|
+
}
|
|
42342
|
+
if (failure instanceof AiParseError || failure instanceof AiHTTPError && failure.status !== null || attempt >= config.maxRetries) throw failure;
|
|
42343
|
+
}
|
|
42344
|
+
}
|
|
42345
|
+
}
|
|
42346
|
+
static async *responseChunks(response) {
|
|
42347
|
+
for await (const chunk of response) {
|
|
42348
|
+
yield chunk;
|
|
42349
|
+
}
|
|
42350
|
+
}
|
|
42351
|
+
static streamError(error) {
|
|
42352
|
+
if (error instanceof AiError) return error;
|
|
42353
|
+
if (error instanceof Error && error.name === "AbortError") return new AiTimeoutError("AI total request timeout expired");
|
|
42354
|
+
return new AiHTTPError(`AI transport failed (${error instanceof Error ? error.name : "Error"})`);
|
|
42355
|
+
}
|
|
42356
|
+
};
|
|
42357
|
+
AggregateState = class {
|
|
42358
|
+
constructor(provider) {
|
|
42359
|
+
this.provider = provider;
|
|
42360
|
+
}
|
|
42361
|
+
toolBuffers = /* @__PURE__ */ new Map();
|
|
42362
|
+
lastFinishReason = null;
|
|
42363
|
+
lastUsage = null;
|
|
42364
|
+
doneEmitted = false;
|
|
42365
|
+
*consume(event) {
|
|
42366
|
+
const data = event.data;
|
|
42367
|
+
if (data === "[DONE]") {
|
|
42368
|
+
if (this.doneEmitted) return;
|
|
42369
|
+
yield* this.flushRemainingToolCalls();
|
|
42370
|
+
this.doneEmitted = true;
|
|
42371
|
+
yield {
|
|
42372
|
+
type: "done",
|
|
42373
|
+
finishReason: this.lastFinishReason ?? "stop",
|
|
42374
|
+
...this.lastUsage ? { usage: this.lastUsage } : {}
|
|
42375
|
+
};
|
|
42376
|
+
return;
|
|
42377
|
+
}
|
|
42378
|
+
let payload;
|
|
42379
|
+
try {
|
|
42380
|
+
payload = JSON.parse(data);
|
|
42381
|
+
} catch {
|
|
42382
|
+
throw new AiParseError("AI provider returned malformed stream data");
|
|
42383
|
+
}
|
|
42384
|
+
if (this.provider === "anthropic") {
|
|
42385
|
+
yield* this.consumeAnthropic(payload);
|
|
42386
|
+
} else {
|
|
42387
|
+
yield* this.consumeOpenAi(payload);
|
|
42388
|
+
}
|
|
42389
|
+
}
|
|
42390
|
+
*consumeOpenAi(payload) {
|
|
42391
|
+
const choices = payload.choices;
|
|
42392
|
+
if (!Array.isArray(choices) || choices.length === 0) return;
|
|
42393
|
+
const choice = choices[0];
|
|
42394
|
+
const delta = choice.delta ?? {};
|
|
42395
|
+
const content = delta.content;
|
|
42396
|
+
if (typeof content === "string" && content.length > 0) {
|
|
42397
|
+
yield { type: "text_delta", text: content };
|
|
42398
|
+
}
|
|
42399
|
+
const toolCalls = delta.tool_calls;
|
|
42400
|
+
if (Array.isArray(toolCalls)) {
|
|
42401
|
+
for (const call of toolCalls) {
|
|
42402
|
+
const index = typeof call.index === "number" ? String(call.index) : String(this.toolBuffers.size);
|
|
42403
|
+
const idFromCall = typeof call.id === "string" ? call.id : "";
|
|
42404
|
+
const fn = call.function ?? {};
|
|
42405
|
+
const nameFromCall = typeof fn.name === "string" ? fn.name : "";
|
|
42406
|
+
const argsFragment = typeof fn.arguments === "string" ? fn.arguments : "";
|
|
42407
|
+
const existing = this.toolBuffers.get(index) ?? { id: "", name: "", args: "" };
|
|
42408
|
+
if (idFromCall) existing.id = idFromCall;
|
|
42409
|
+
if (nameFromCall) existing.name = nameFromCall;
|
|
42410
|
+
existing.args += argsFragment;
|
|
42411
|
+
this.toolBuffers.set(index, existing);
|
|
42412
|
+
if (existing.name && existing.args) {
|
|
42413
|
+
try {
|
|
42414
|
+
const parsed = JSON.parse(existing.args);
|
|
42415
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
42416
|
+
this.toolBuffers.delete(index);
|
|
42417
|
+
yield { type: "tool_call", id: existing.id || `call_${index}`, name: existing.name, args: parsed };
|
|
42418
|
+
}
|
|
42419
|
+
} catch {
|
|
42420
|
+
}
|
|
42421
|
+
}
|
|
42422
|
+
}
|
|
42423
|
+
}
|
|
42424
|
+
if (typeof choice.finish_reason === "string" && choice.finish_reason.length > 0) {
|
|
42425
|
+
this.lastFinishReason = choice.finish_reason;
|
|
42426
|
+
}
|
|
42427
|
+
const usage = payload.usage;
|
|
42428
|
+
if (usage && typeof usage === "object") {
|
|
42429
|
+
const promptTokens = Number(usage.prompt_tokens ?? 0);
|
|
42430
|
+
const completionTokens = Number(usage.completion_tokens ?? 0);
|
|
42431
|
+
const totalTokens = Number(usage.total_tokens ?? promptTokens + completionTokens);
|
|
42432
|
+
if (Number.isFinite(promptTokens) && Number.isFinite(completionTokens)) {
|
|
42433
|
+
this.lastUsage = { promptTokens, completionTokens, totalTokens };
|
|
42434
|
+
}
|
|
42435
|
+
}
|
|
42436
|
+
}
|
|
42437
|
+
*consumeAnthropic(payload) {
|
|
42438
|
+
const type2 = payload.type;
|
|
42439
|
+
if (type2 === "content_block_start") {
|
|
42440
|
+
const block = payload.content_block ?? {};
|
|
42441
|
+
if (block.type === "tool_use") {
|
|
42442
|
+
const index = String(payload.index ?? this.toolBuffers.size);
|
|
42443
|
+
const id = typeof block.id === "string" ? block.id : `call_${index}`;
|
|
42444
|
+
const name = typeof block.name === "string" ? block.name : "";
|
|
42445
|
+
this.toolBuffers.set(index, { id, name, args: "" });
|
|
42446
|
+
}
|
|
42447
|
+
return;
|
|
42448
|
+
}
|
|
42449
|
+
if (type2 === "content_block_delta") {
|
|
42450
|
+
const index = String(payload.index ?? 0);
|
|
42451
|
+
const delta = payload.delta ?? {};
|
|
42452
|
+
if (delta.type === "text_delta" && typeof delta.text === "string" && delta.text.length > 0) {
|
|
42453
|
+
yield { type: "text_delta", text: delta.text };
|
|
42454
|
+
return;
|
|
42455
|
+
}
|
|
42456
|
+
if (delta.type === "input_json_delta" && typeof delta.partial_json === "string") {
|
|
42457
|
+
const existing = this.toolBuffers.get(index);
|
|
42458
|
+
if (existing) existing.args += delta.partial_json;
|
|
42459
|
+
}
|
|
42460
|
+
return;
|
|
42461
|
+
}
|
|
42462
|
+
if (type2 === "content_block_stop") {
|
|
42463
|
+
const index = String(payload.index ?? 0);
|
|
42464
|
+
const existing = this.toolBuffers.get(index);
|
|
42465
|
+
if (existing && existing.name) {
|
|
42466
|
+
this.toolBuffers.delete(index);
|
|
42467
|
+
try {
|
|
42468
|
+
const parsed = existing.args ? JSON.parse(existing.args) : {};
|
|
42469
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
42470
|
+
yield { type: "tool_call", id: existing.id, name: existing.name, args: parsed };
|
|
42471
|
+
return;
|
|
42472
|
+
}
|
|
42473
|
+
throw new Error();
|
|
42474
|
+
} catch {
|
|
42475
|
+
throw new AiParseError("AI provider returned malformed tool-call JSON");
|
|
42476
|
+
}
|
|
42477
|
+
}
|
|
42478
|
+
return;
|
|
42479
|
+
}
|
|
42480
|
+
if (type2 === "message_delta") {
|
|
42481
|
+
const delta = payload.delta ?? {};
|
|
42482
|
+
if (typeof delta.stop_reason === "string" && delta.stop_reason.length > 0) {
|
|
42483
|
+
this.lastFinishReason = delta.stop_reason;
|
|
42484
|
+
}
|
|
42485
|
+
const usage = payload.usage ?? {};
|
|
42486
|
+
if (usage.output_tokens !== void 0 || usage.input_tokens !== void 0) {
|
|
42487
|
+
const promptTokens = Number(usage.input_tokens ?? this.lastUsage?.promptTokens ?? 0);
|
|
42488
|
+
const completionTokens = Number(usage.output_tokens ?? this.lastUsage?.completionTokens ?? 0);
|
|
42489
|
+
this.lastUsage = { promptTokens, completionTokens, totalTokens: promptTokens + completionTokens };
|
|
42490
|
+
}
|
|
42491
|
+
return;
|
|
42492
|
+
}
|
|
42493
|
+
if (type2 === "message_stop") {
|
|
42494
|
+
if (this.doneEmitted) return;
|
|
42495
|
+
this.doneEmitted = true;
|
|
42496
|
+
yield {
|
|
42497
|
+
type: "done",
|
|
42498
|
+
finishReason: this.lastFinishReason ?? "end_turn",
|
|
42499
|
+
...this.lastUsage ? { usage: this.lastUsage } : {}
|
|
42500
|
+
};
|
|
42501
|
+
return;
|
|
42502
|
+
}
|
|
42503
|
+
if (type2 === "message_start") {
|
|
42504
|
+
const message = payload.message ?? {};
|
|
42505
|
+
const usage = message.usage ?? {};
|
|
42506
|
+
if (usage.input_tokens !== void 0 || usage.output_tokens !== void 0) {
|
|
42507
|
+
const promptTokens = Number(usage.input_tokens ?? 0);
|
|
42508
|
+
const completionTokens = Number(usage.output_tokens ?? 0);
|
|
42509
|
+
this.lastUsage = { promptTokens, completionTokens, totalTokens: promptTokens + completionTokens };
|
|
42510
|
+
}
|
|
42511
|
+
return;
|
|
42512
|
+
}
|
|
42513
|
+
if (type2 === "error") {
|
|
42514
|
+
const err = payload.error ?? {};
|
|
42515
|
+
throw new AiParseError(typeof err.message === "string" ? err.message : "AI provider signalled a stream error");
|
|
42516
|
+
}
|
|
42517
|
+
}
|
|
42518
|
+
*flushRemainingToolCalls() {
|
|
42519
|
+
for (const [index, buffered] of this.toolBuffers) {
|
|
42520
|
+
if (buffered.name && buffered.args) {
|
|
42521
|
+
try {
|
|
42522
|
+
const parsed = JSON.parse(buffered.args);
|
|
42523
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
42524
|
+
yield { type: "tool_call", id: buffered.id || `call_${index}`, name: buffered.name, args: parsed };
|
|
42525
|
+
continue;
|
|
42526
|
+
}
|
|
42527
|
+
} catch {
|
|
42528
|
+
}
|
|
42529
|
+
throw new AiParseError("AI provider returned malformed tool-call JSON");
|
|
42035
42530
|
}
|
|
42036
42531
|
}
|
|
42532
|
+
this.toolBuffers.clear();
|
|
42037
42533
|
}
|
|
42038
42534
|
};
|
|
42039
42535
|
}
|
|
@@ -43749,6 +44245,7 @@ __export(index_exports, {
|
|
|
43749
44245
|
AiParseError: () => AiParseError,
|
|
43750
44246
|
AiTimeoutError: () => AiTimeoutError,
|
|
43751
44247
|
Api: () => Api,
|
|
44248
|
+
ApiStreamError: () => ApiStreamError,
|
|
43752
44249
|
Auth: () => Auth,
|
|
43753
44250
|
CANONICAL_SESSION_BACKENDS: () => CANONICAL_SESSION_BACKENDS,
|
|
43754
44251
|
CLOSE_GOING_AWAY: () => CLOSE_GOING_AWAY,
|
|
@@ -43978,7 +44475,9 @@ __export(index_exports, {
|
|
|
43978
44475
|
originAllowed: () => originAllowed,
|
|
43979
44476
|
parseAmqpUrl: () => parseAmqpUrl,
|
|
43980
44477
|
parseFrame: () => parseFrame,
|
|
44478
|
+
parseLineStream: () => parseLineStream,
|
|
43981
44479
|
parseMultipart: () => parseMultipart,
|
|
44480
|
+
parseSseStream: () => parseSseStream,
|
|
43982
44481
|
parseUpgradeHeaders: () => parseUpgradeHeaders,
|
|
43983
44482
|
patch: () => patch,
|
|
43984
44483
|
pidfilePath: () => pidfilePath,
|
|
@@ -44115,6 +44614,7 @@ export {
|
|
|
44115
44614
|
AiParseError,
|
|
44116
44615
|
AiTimeoutError,
|
|
44117
44616
|
Api,
|
|
44617
|
+
ApiStreamError,
|
|
44118
44618
|
Auth,
|
|
44119
44619
|
CANONICAL_SESSION_BACKENDS,
|
|
44120
44620
|
CLOSE_GOING_AWAY,
|
|
@@ -44344,7 +44844,9 @@ export {
|
|
|
44344
44844
|
originAllowed,
|
|
44345
44845
|
parseAmqpUrl,
|
|
44346
44846
|
parseFrame,
|
|
44847
|
+
parseLineStream,
|
|
44347
44848
|
parseMultipart,
|
|
44849
|
+
parseSseStream,
|
|
44348
44850
|
parseUpgradeHeaders,
|
|
44349
44851
|
patch,
|
|
44350
44852
|
pidfilePath,
|