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.
@@ -32854,47 +32854,120 @@ function renderToolbarHtml(ctx) {
32854
32854
  const method = escapeHtml(ctx.method);
32855
32855
  const path8 = escapeHtml(ctx.path);
32856
32856
  const matchedPattern = escapeHtml(ctx.matchedPattern);
32857
- return `<div id="tina4-dev-toolbar" style="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;">
32858
- <span id="tina4-ver-btn" style="color:#2e7d32;font-weight:bold;cursor:pointer;text-decoration:underline dotted;" onclick="tina4VersionModal()" title="Click to check for updates">Tina4 v${ctx.version}</span>
32859
- <div id="tina4-ver-modal" style="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,0.5);font-family:monospace;font-size:13px;color:#cdd6f4;">
32860
- <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;">
32861
- <strong style="color:#89b4fa;">Version Info</strong>
32862
- <span onclick="document.getElementById('tina4-ver-modal').style.display='none'" style="cursor:pointer;color:#888;">&times;</span>
32857
+ const reload = ctx.reload === false ? "0" : "1";
32858
+ return `<link rel="stylesheet" href="/__dev/toolbar.css">
32859
+ <div id="tina4-dev-toolbar" data-reload="${reload}">
32860
+ <span id="tina4-ver-btn" title="Click to check for updates">Tina4 v${ctx.version}</span>
32861
+ <div id="tina4-ver-modal">
32862
+ <div class="t4-modal-head">
32863
+ <strong class="t4-modal-title">Version Info</strong>
32864
+ <span id="tina4-ver-close" class="t4-x">&times;</span>
32863
32865
  </div>
32864
- <div id="tina4-ver-body" style="line-height:1.8;">
32865
- <div>Current: <strong style="color:#a6e3a1;">v${ctx.version}</strong></div>
32866
- <div id="tina4-ver-latest" style="color:#888;">Checking for updates...</div>
32866
+ <div id="tina4-ver-body">
32867
+ <div>Current: <strong class="t4-ok">v${ctx.version}</strong></div>
32868
+ <div id="tina4-ver-latest" class="t4-dim">Checking for updates...</div>
32867
32869
  </div>
32868
32870
  </div>
32869
- <span style="color:#4caf50;">${method}</span>
32871
+ <span class="t4-green">${method}</span>
32870
32872
  <span>${path8}</span>
32871
- <span style="color:#666;">&rarr; ${matchedPattern}</span>
32872
- <span style="color:#ffeb3b;">req:${ctx.requestId}</span>
32873
- <span style="color:#90caf9;">${ctx.routeCount} routes</span>
32874
- <span style="color:#888;">Node.js ${nodeVersion}</span>
32875
- <a href="#" onclick="window.__tina4ToggleOverlay(event)" style="color:#ef9a9a;margin-left:auto;text-decoration:none;cursor:pointer;">Dashboard &#8599;</a>
32876
- <span onclick="this.parentElement.style.display='none'" style="cursor:pointer;color:#888;margin-left:8px;">&#10005;</span>
32873
+ <span class="t4-arrow">&rarr; ${matchedPattern}</span>
32874
+ <span class="t4-yellow">req:${ctx.requestId}</span>
32875
+ <span class="t4-blue">${ctx.routeCount} routes</span>
32876
+ <span class="t4-dim">Node.js ${nodeVersion}</span>
32877
+ <a href="#" id="tina4-dash-link" class="t4-dash">Dashboard &#8599;</a>
32878
+ <span id="tina4-bar-close" class="t4-x t4-bar-close">&#10005;</span>
32877
32879
  </div>
32878
- <script>
32879
- // Overlay open/toggle helper + auto-restore. Persist the dev-admin
32880
- // iframe's open/closed state across parent reloads so saving a file
32881
- // doesn't lose the user's dev-admin context. Cross-framework parity
32882
- // with PHP / Python / Ruby \u2014 same localStorage key.
32883
- (function(){
32880
+ <script src="/__dev/toolbar.js"></script>`;
32881
+ }
32882
+ function toolbarCss() {
32883
+ 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}
32884
+ #tina4-dev-toolbar a{text-decoration:none}
32885
+ #tina4-ver-btn{color:#2e7d32;font-weight:bold;cursor:pointer;text-decoration:underline dotted}
32886
+ #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}
32887
+ .t4-modal-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:12px}
32888
+ .t4-modal-title{color:#89b4fa}
32889
+ #tina4-ver-body{line-height:1.8}
32890
+ .t4-x{cursor:pointer;color:#888}
32891
+ .t4-bar-close{margin-left:8px}
32892
+ .t4-green{color:#4caf50}
32893
+ .t4-dim{color:#888}
32894
+ .t4-arrow{color:#666}
32895
+ .t4-yellow{color:#ffeb3b}
32896
+ .t4-blue{color:#90caf9}
32897
+ .t4-ok{color:#a6e3a1}
32898
+ .t4-warn{color:#f9e2af}
32899
+ .t4-err{color:#f38ba8}
32900
+ .t4-purple{color:#cba6f7}
32901
+ .t4-link{color:#89b4fa}
32902
+ .t4-code{background:#313244;padding:2px 6px;border-radius:3px}
32903
+ .t4-note{margin-top:6px}
32904
+ .t4-dash{color:#ef9a9a;margin-left:auto;cursor:pointer}
32905
+ #tina4-dev-panel{position:fixed;top:3rem;left:0;right:0;bottom:2rem;z-index:99998;transition:all .2s}
32906
+ #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}`;
32907
+ }
32908
+ function toolbarJs() {
32909
+ return `(function () {
32910
+ var bar = document.getElementById('tina4-dev-toolbar');
32911
+ if (!bar) { return; }
32912
+
32913
+ var modal = document.getElementById('tina4-ver-modal');
32914
+ function upToDate(el, latest) {
32915
+ el.className = 't4-ok';
32916
+ el.innerHTML = 'Latest: <strong class="t4-ok">v' + latest + '</strong> &mdash; You are up to date!';
32917
+ }
32918
+ function checkVersion() {
32919
+ if (modal.style.display === 'block') { modal.style.display = 'none'; return; }
32920
+ modal.style.display = 'block';
32921
+ var el = document.getElementById('tina4-ver-latest');
32922
+ el.className = 't4-dim';
32923
+ el.textContent = 'Checking for updates...';
32924
+ fetch('/__dev/api/version-check').then(function (r) { return r.json(); }).then(function (d) {
32925
+ var latest = d.latest, current = d.current;
32926
+ if (latest === current) { upToDate(el, latest); return; }
32927
+ var cP = current.split('.').map(Number), lP = latest.split('.').map(Number);
32928
+ var isNewer = false, i, c, l;
32929
+ 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; } }
32930
+ var isAhead = false;
32931
+ 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; } } }
32932
+ if (isNewer) {
32933
+ var breaking = (lP[0] !== cP[0] || lP[1] !== cP[1]);
32934
+ el.className = '';
32935
+ el.innerHTML = 'Latest: <strong class="t4-warn">v' + latest + '</strong>';
32936
+ if (breaking) {
32937
+ el.innerHTML += '<div class="t4-err t4-note">&#9888; Major/minor version change &mdash; check the <a href="https://github.com/tina4stack/tina4-nodejs/releases" target="_blank" class="t4-link">changelog</a> for breaking changes before upgrading.</div>';
32938
+ } else {
32939
+ el.innerHTML += '<div class="t4-warn t4-note">Patch update available. Run: <code class="t4-code">npm install tina4-nodejs@latest</code></div>';
32940
+ }
32941
+ } else if (isAhead) {
32942
+ el.className = 't4-purple';
32943
+ el.innerHTML = 'You are running <strong class="t4-purple">v' + current + '</strong> (ahead of npm <strong>v' + latest + '</strong> &mdash; not yet published).';
32944
+ } else {
32945
+ upToDate(el, latest);
32946
+ }
32947
+ }).catch(function () {
32948
+ el.className = 't4-err';
32949
+ el.textContent = 'Could not check for updates (offline?)';
32950
+ });
32951
+ }
32952
+ var verBtn = document.getElementById('tina4-ver-btn');
32953
+ if (verBtn) { verBtn.addEventListener('click', checkVersion); }
32954
+ var verClose = document.getElementById('tina4-ver-close');
32955
+ if (verClose) { verClose.addEventListener('click', function () { modal.style.display = 'none'; }); }
32956
+ var barClose = document.getElementById('tina4-bar-close');
32957
+ if (barClose) { barClose.addEventListener('click', function () { bar.style.display = 'none'; }); }
32958
+
32884
32959
  var STATE_KEY = 'tina4_dev_overlay_open';
32885
32960
  function buildOverlay() {
32886
32961
  var c = document.createElement('div');
32887
32962
  c.id = 'tina4-dev-panel';
32888
- c.style.cssText = 'position:fixed;top:3rem;left:0;right:0;bottom:2rem;z-index:99998;transition:all 0.2s';
32889
32963
  var f = document.createElement('iframe');
32890
32964
  f.src = '/__dev';
32891
- 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';
32892
32965
  c.appendChild(f);
32893
32966
  document.body.appendChild(c);
32894
32967
  return c;
32895
32968
  }
32896
- window.__tina4ToggleOverlay = function(e) {
32897
- if (e) e.preventDefault();
32969
+ function toggleOverlay(e) {
32970
+ if (e) { e.preventDefault(); }
32898
32971
  var p = document.getElementById('tina4-dev-panel');
32899
32972
  if (p) {
32900
32973
  var hide = p.style.display !== 'none';
@@ -32904,137 +32977,61 @@ function renderToolbarHtml(ctx) {
32904
32977
  }
32905
32978
  buildOverlay();
32906
32979
  try { localStorage.setItem(STATE_KEY, '1'); } catch (_) {}
32907
- };
32908
- function restoreIfOpen() {
32909
- try {
32910
- if (location.pathname.indexOf('/__dev') === 0) return;
32911
- if (localStorage.getItem(STATE_KEY) === '1' && !document.getElementById('tina4-dev-panel')) {
32912
- buildOverlay();
32913
- }
32914
- } catch (_) {}
32915
32980
  }
32916
- if (document.readyState === 'loading') {
32917
- document.addEventListener('DOMContentLoaded', restoreIfOpen);
32918
- } else {
32919
- restoreIfOpen();
32920
- }
32921
- })();
32922
- </script>
32923
- <script>
32924
- function tina4VersionModal(){
32925
- var m=document.getElementById('tina4-ver-modal');
32926
- if(m.style.display==='block'){m.style.display='none';return;}
32927
- m.style.display='block';
32928
- var el=document.getElementById('tina4-ver-latest');
32929
- el.innerHTML='Checking for updates...';
32930
- el.style.color='#888';
32931
- fetch('/__dev/api/version-check')
32932
- .then(function(r){return r.json()})
32933
- .then(function(d){
32934
- var latest=d.latest;
32935
- var current=d.current;
32936
- if(latest===current){
32937
- el.innerHTML='Latest: <strong style="color:#a6e3a1;">v'+latest+'</strong> &mdash; You are up to date!';
32938
- el.style.color='#a6e3a1';
32939
- }else{
32940
- var cParts=current.split('.').map(Number);
32941
- var lParts=latest.split('.').map(Number);
32942
- var isNewer=false;
32943
- for(var i=0;i<Math.max(cParts.length,lParts.length);i++){
32944
- var c=cParts[i]||0,l=lParts[i]||0;
32945
- if(l>c){isNewer=true;break;}
32946
- if(l<c)break;
32947
- }
32948
- var isAhead=false;
32949
- 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;}}
32950
- if(isNewer){
32951
- var breaking=(lParts[0]!==cParts[0]||lParts[1]!==cParts[1]);
32952
- el.innerHTML='Latest: <strong style="color:#f9e2af;">v'+latest+'</strong>';
32953
- if(breaking){
32954
- el.innerHTML+='<div style="color:#f38ba8;margin-top:6px;">&#9888; Major/minor version change &mdash; check the <a href="https://github.com/tina4stack/tina4-nodejs/releases" target="_blank" style="color:#89b4fa;">changelog</a> for breaking changes before upgrading.</div>';
32955
- }else{
32956
- 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>';
32957
- }
32958
- }else if(isAhead){
32959
- el.innerHTML='You are running <strong style="color:#cba6f7;">v'+current+'</strong> (ahead of npm <strong>v'+latest+'</strong> &mdash; not yet published).';
32960
- el.style.color='#cba6f7';
32961
- }else{
32962
- el.innerHTML='Latest: <strong style="color:#a6e3a1;">v'+latest+'</strong> &mdash; You are up to date!';
32963
- el.style.color='#a6e3a1';
32964
- }
32965
- }
32966
- })
32967
- .catch(function(){
32968
- el.innerHTML='Could not check for updates (offline?)';
32969
- el.style.color='#f38ba8';
32970
- });
32971
- }
32972
- </script>
32973
- <script>
32974
- (function(){
32975
- // WebSocket-primary dev reloader. The running server re-imports changed
32976
- // src/ routes in-process and pushes a {type,file,mtime} message over
32977
- // /__dev_reload \u2014 no respawn, instant refresh. The mtime poll below is a
32978
- // FALLBACK only, started when the socket is down and stopped on connect.
32979
- var _t4_css_exts=['.css','.scss'],_t4_debounce=null;
32980
- var _t4_interval=3000;
32981
- var _t4_ws=null,_t4_poll_timer=null,_t4_mtime=null;
32982
- function _t4_apply(d){
32983
- d=d||{};
32984
- var f=d.file||'',t=d.type||'';
32985
- var isCss=t==='css'||_t4_css_exts.some(function(e){return f.endsWith(e)});
32986
- if(isCss){
32987
- var links=document.querySelectorAll('link[rel="stylesheet"]');
32988
- links.forEach(function(l){
32989
- var href=l.getAttribute('href');
32990
- if(href){l.setAttribute('href',href.split('?')[0]+'?_t4='+(d.mtime||Date.now()))}
32981
+ var dash = document.getElementById('tina4-dash-link');
32982
+ if (dash) { dash.addEventListener('click', toggleOverlay); }
32983
+ try {
32984
+ if (location.pathname.indexOf('/__dev') !== 0
32985
+ && localStorage.getItem(STATE_KEY) === '1'
32986
+ && !document.getElementById('tina4-dev-panel')) {
32987
+ buildOverlay();
32988
+ }
32989
+ } catch (_) {}
32990
+
32991
+ if (bar.getAttribute('data-reload') !== '1') { return; }
32992
+ var cssExts = ['.css', '.scss'], debounce = null, interval = 3000;
32993
+ var ws = null, pollTimer = null, mtime = null;
32994
+ function apply(d) {
32995
+ d = d || {};
32996
+ var f = d.file || '', t = d.type || '';
32997
+ var isCss = t === 'css' || cssExts.some(function (e) { return f.endsWith(e); });
32998
+ if (isCss) {
32999
+ document.querySelectorAll('link[rel="stylesheet"]').forEach(function (l) {
33000
+ var href = l.getAttribute('href');
33001
+ if (href) { l.setAttribute('href', href.split('?')[0] + '?_t4=' + (d.mtime || Date.now())); }
32991
33002
  });
32992
- }else{
33003
+ } else {
32993
33004
  location.reload();
32994
33005
  }
32995
33006
  }
32996
- function _t4_poll(){
32997
- fetch('/__dev/api/mtime').then(function(r){return r.json()}).then(function(d){
32998
- // Sentinel: first poll only records the baseline. Use !== (not >) so
32999
- // the first change after load is not swallowed and a counter reset on
33000
- // server restart still triggers a reload.
33001
- if(_t4_mtime===null){_t4_mtime=d.mtime;return;}
33002
- if(d.mtime!==_t4_mtime){
33003
- _t4_mtime=d.mtime;
33004
- if(_t4_debounce)clearTimeout(_t4_debounce);
33005
- _t4_debounce=setTimeout(function(){_t4_apply(d);},500);
33006
- }
33007
- }).catch(function(){});
33008
- }
33009
- function _t4_startPoll(){
33010
- if(_t4_poll_timer)return;
33011
- _t4_mtime=null;
33012
- _t4_poll_timer=setInterval(_t4_poll,_t4_interval);
33013
- }
33014
- function _t4_stopPoll(){
33015
- if(_t4_poll_timer){clearInterval(_t4_poll_timer);_t4_poll_timer=null;}
33016
- }
33017
- function _t4_connect(){
33018
- var url=(location.protocol==='https:'?'wss':'ws')+'://'+location.host+'/__dev_reload';
33019
- try{_t4_ws=new WebSocket(url);}catch(_){_t4_startPoll();return;}
33020
- _t4_ws.addEventListener('open',function(){_t4_stopPoll();});
33021
- _t4_ws.addEventListener('message',function(ev){
33022
- var d=null;
33023
- try{d=typeof ev.data==='string'?JSON.parse(ev.data):null;}catch(_){}
33024
- if(!d)return;
33025
- if(d.type==='reload'||d.type==='change'||d.type==='css'){
33026
- if(_t4_debounce)clearTimeout(_t4_debounce);
33027
- _t4_debounce=setTimeout(function(){_t4_apply(d);},150);
33007
+ function poll() {
33008
+ fetch('/__dev/api/mtime').then(function (r) { return r.json(); }).then(function (d) {
33009
+ if (mtime === null) { mtime = d.mtime; return; }
33010
+ if (d.mtime !== mtime) { mtime = d.mtime; if (debounce) { clearTimeout(debounce); } debounce = setTimeout(function () { apply(d); }, 500); }
33011
+ }).catch(function () {});
33012
+ }
33013
+ function startPoll() { if (pollTimer) { return; } mtime = null; pollTimer = setInterval(poll, interval); }
33014
+ function stopPoll() { if (pollTimer) { clearInterval(pollTimer); pollTimer = null; } }
33015
+ function connect() {
33016
+ var url = (location.protocol === 'https:' ? 'wss' : 'ws') + '://' + location.host + '/__dev_reload';
33017
+ try { ws = new WebSocket(url); } catch (_) { startPoll(); return; }
33018
+ ws.addEventListener('open', function () { stopPoll(); });
33019
+ ws.addEventListener('message', function (ev) {
33020
+ var d = null;
33021
+ try { d = typeof ev.data === 'string' ? JSON.parse(ev.data) : null; } catch (_) {}
33022
+ if (!d) { return; }
33023
+ if (d.type === 'reload' || d.type === 'change' || d.type === 'css') {
33024
+ if (debounce) { clearTimeout(debounce); }
33025
+ debounce = setTimeout(function () { apply(d); }, 150);
33028
33026
  }
33029
33027
  });
33030
- _t4_ws.addEventListener('close',function(){_t4_ws=null;_t4_startPoll();setTimeout(_t4_connect,2000);});
33031
- _t4_ws.addEventListener('error',function(){try{_t4_ws&&_t4_ws.close();}catch(_){}});
33028
+ ws.addEventListener('close', function () { ws = null; startPoll(); setTimeout(connect, 2000); });
33029
+ ws.addEventListener('error', function () { try { ws && ws.close(); } catch (_) {} });
33032
33030
  }
33033
- _t4_connect();
33034
- })();
33035
- </script>`;
33031
+ connect();
33032
+ })();`;
33036
33033
  }
33037
- 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;
33034
+ 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;
33038
33035
  var init_devAdmin = __esm({
33039
33036
  "../core/src/devAdmin.ts"() {
33040
33037
  "use strict";
@@ -33500,7 +33497,12 @@ var init_devAdmin = __esm({
33500
33497
  { method: "GET", pattern: "/__dev/api/docs/index", handler: handleDocsIndex },
33501
33498
  { method: "GET", pattern: "/__dev/api/docs/.well-known.json", handler: handleDocsWellKnown },
33502
33499
  // JS asset
33503
- { method: "GET", pattern: "/__dev/js/tina4-dev-admin.min.js", handler: handleDevAdminJs }
33500
+ { method: "GET", pattern: "/__dev/js/tina4-dev-admin.min.js", handler: handleDevAdminJs },
33501
+ // Dev-toolbar assets — served as external CSS/JS so the injected toolbar is
33502
+ // CSP-clean (no inline style=, onclick=, or <script>) under a strict
33503
+ // `default-src 'self'`. Parity with PHP's /__dev/toolbar.css + toolbar.js.
33504
+ { method: "GET", pattern: "/__dev/toolbar.css", handler: handleToolbarCss },
33505
+ { method: "GET", pattern: "/__dev/toolbar.js", handler: handleToolbarJs }
33504
33506
  ];
33505
33507
  for (const route of routes) {
33506
33508
  router.addRoute({
@@ -34936,6 +34938,14 @@ var init_devAdmin = __esm({
34936
34938
  res.raw.writeHead(404, { "Content-Type": "text/plain" });
34937
34939
  res.raw.end("tina4-dev-admin.min.js not found");
34938
34940
  };
34941
+ handleToolbarCss = (_req, res) => {
34942
+ res.raw.writeHead(200, { "Content-Type": "text/css; charset=utf-8", "Cache-Control": "no-cache" });
34943
+ res.raw.end(toolbarCss());
34944
+ };
34945
+ handleToolbarJs = (_req, res) => {
34946
+ res.raw.writeHead(200, { "Content-Type": "application/javascript; charset=utf-8", "Cache-Control": "no-cache" });
34947
+ res.raw.end(toolbarJs());
34948
+ };
34939
34949
  }
34940
34950
  });
34941
34951
 
@@ -36797,7 +36807,11 @@ function injectIntoHtml(ctx, devToolbar, html) {
36797
36807
  path: ctx.pathname,
36798
36808
  matchedPattern: ctx.matchedPattern.value || ctx.pathname,
36799
36809
  requestId: ctx.requestId,
36800
- routeCount: ctx.router.getRoutes().length
36810
+ routeCount: ctx.router.getRoutes().length,
36811
+ // Suppress the live reloader on the AI/stable port (data-reload="0"); the
36812
+ // toolbar JS early-returns when data-reload !== "1". Mirrors PHP's
36813
+ // suppressReload flag.
36814
+ reload: !ctx.isAiPortRequest
36801
36815
  };
36802
36816
  return injectFeedbackWidget(ctx.req, injectDevToolbar(html, toolbarCtx));
36803
36817
  }
@@ -38791,11 +38805,100 @@ function buildMultipartBody(boundary, fieldName, filename, fileContent, contentT
38791
38805
  parts.push(Buffer.from(delimiter4 + "--" + crlf, "utf-8"));
38792
38806
  return Buffer.concat(parts);
38793
38807
  }
38794
- var RETRY_STATUSES, DOWNLOAD_CHUNK_SIZE, MAX_REDIRECTS, STRIP_ON_CROSS_ORIGIN, MIME_BY_EXT, Api;
38808
+ async function* parseLineStream(chunks) {
38809
+ const decoder = new TextDecoder("utf-8");
38810
+ let buffer = "";
38811
+ for await (const chunk of chunks) {
38812
+ buffer += decoder.decode(chunk, { stream: true });
38813
+ let idx;
38814
+ while ((idx = buffer.indexOf("\n")) >= 0) {
38815
+ let line = buffer.slice(0, idx);
38816
+ buffer = buffer.slice(idx + 1);
38817
+ if (line.endsWith("\r")) {
38818
+ line = line.slice(0, -1);
38819
+ }
38820
+ yield line;
38821
+ }
38822
+ }
38823
+ buffer += decoder.decode();
38824
+ if (buffer.length > 0) {
38825
+ if (buffer.endsWith("\r")) {
38826
+ buffer = buffer.slice(0, -1);
38827
+ }
38828
+ yield buffer;
38829
+ }
38830
+ }
38831
+ async function* parseSseStream(lines) {
38832
+ let dataParts = [];
38833
+ let event;
38834
+ let id;
38835
+ let retry;
38836
+ let has = false;
38837
+ const emit = () => {
38838
+ if (!has) return null;
38839
+ const ev = { data: dataParts.join("\n") };
38840
+ if (event !== void 0) ev.event = event;
38841
+ if (id !== void 0) ev.id = id;
38842
+ if (retry !== void 0) ev.retry = retry;
38843
+ return ev;
38844
+ };
38845
+ const reset2 = () => {
38846
+ dataParts = [];
38847
+ event = void 0;
38848
+ id = void 0;
38849
+ retry = void 0;
38850
+ has = false;
38851
+ };
38852
+ for await (const line of lines) {
38853
+ if (line === "") {
38854
+ const ev = emit();
38855
+ if (ev) yield ev;
38856
+ reset2();
38857
+ continue;
38858
+ }
38859
+ if (line.startsWith(":")) continue;
38860
+ const colon = line.indexOf(":");
38861
+ const field = colon < 0 ? line : line.slice(0, colon);
38862
+ let value = colon < 0 ? "" : line.slice(colon + 1);
38863
+ if (value.startsWith(" ")) value = value.slice(1);
38864
+ switch (field) {
38865
+ case "data":
38866
+ dataParts.push(value);
38867
+ has = true;
38868
+ break;
38869
+ case "event":
38870
+ event = value;
38871
+ has = true;
38872
+ break;
38873
+ case "id":
38874
+ id = value;
38875
+ has = true;
38876
+ break;
38877
+ case "retry": {
38878
+ const parsed = Number(value);
38879
+ if (Number.isFinite(parsed) && parsed >= 0) {
38880
+ retry = parsed;
38881
+ has = true;
38882
+ }
38883
+ break;
38884
+ }
38885
+ }
38886
+ }
38887
+ const trailing = emit();
38888
+ if (trailing) yield trailing;
38889
+ }
38890
+ var ApiStreamError, RETRY_STATUSES, DOWNLOAD_CHUNK_SIZE, MAX_REDIRECTS, STRIP_ON_CROSS_ORIGIN, MIME_BY_EXT, Api;
38795
38891
  var init_api = __esm({
38796
38892
  "../core/src/api.ts"() {
38797
38893
  "use strict";
38798
38894
  init_version();
38895
+ ApiStreamError = class extends Error {
38896
+ constructor(message, status2 = null) {
38897
+ super(message);
38898
+ this.status = status2;
38899
+ this.name = "ApiStreamError";
38900
+ }
38901
+ };
38799
38902
  RETRY_STATUSES = /* @__PURE__ */ new Set([429, 500, 502, 503, 504]);
38800
38903
  DOWNLOAD_CHUNK_SIZE = 64 * 1024;
38801
38904
  MAX_REDIRECTS = 10;
@@ -39114,7 +39217,130 @@ var init_api = __esm({
39114
39217
  }
39115
39218
  return { http_code: code, headers: respHeaders, error: null, path: destPath };
39116
39219
  }
39220
+ /**
39221
+ * Stream a response body as raw bytes. Yields the chunks the transport
39222
+ * delivers, in order, never buffered whole. Ends cleanly on EOF and
39223
+ * throws on a transport failure or a non-2xx status (body drained
39224
+ * first). No JSON decoding, no line splitting, no framing —
39225
+ * {@link streamLines} and {@link streamSse} build on this primitive.
39226
+ *
39227
+ * Closing the iterator before EOF (a `break` out of a `for await`)
39228
+ * destroys the underlying socket, so a caller who takes only the
39229
+ * first few chunks never leaks the connection.
39230
+ *
39231
+ * `opts.timeout` bounds the whole stream duration (default
39232
+ * `TINA4_API_TIMEOUT` or the client `timeout`); `opts.connectTimeout`
39233
+ * bounds just the connect + headers phase (default
39234
+ * `TINA4_API_CONNECT_TIMEOUT` or 10s).
39235
+ */
39236
+ async *streamBytes(path8, opts = {}) {
39237
+ const url = this.buildUrl(path8);
39238
+ const method = (opts.method ?? "GET").toUpperCase();
39239
+ const contentType = opts.contentType ?? "application/json";
39240
+ const { headers, data } = this.buildRequest(method, contentType, opts.body, opts.headers);
39241
+ const totalSec = this.streamSeconds(opts.timeout, "TINA4_API_TIMEOUT", this.timeout);
39242
+ const connectSec = this.streamSeconds(opts.connectTimeout, "TINA4_API_CONNECT_TIMEOUT", 10);
39243
+ const opened = await this.openStreamRequest(method, url, headers, data, connectSec);
39244
+ const res = opened.res;
39245
+ const status2 = res.statusCode ?? 0;
39246
+ this.storeCookies(res.headers["set-cookie"]);
39247
+ if (status2 < 200 || status2 >= 300) {
39248
+ res.resume();
39249
+ throw new ApiStreamError(`stream failed with HTTP ${status2}`, status2);
39250
+ }
39251
+ let totalTimer = null;
39252
+ if (totalSec > 0) {
39253
+ totalTimer = setTimeout(() => {
39254
+ res.destroy(new ApiStreamError(`stream total timeout after ${totalSec}s`, null));
39255
+ }, totalSec * 1e3);
39256
+ }
39257
+ try {
39258
+ for await (const chunk of res) {
39259
+ yield chunk;
39260
+ }
39261
+ } finally {
39262
+ if (totalTimer) clearTimeout(totalTimer);
39263
+ if (!res.destroyed) res.destroy();
39264
+ }
39265
+ }
39266
+ /**
39267
+ * Stream the response body as UTF-8 lines. Splits on LF or CRLF;
39268
+ * buffers a multibyte codepoint that lands across a chunk boundary;
39269
+ * yields a trailing line without a terminator on EOF. Built on
39270
+ * {@link streamBytes} plus the shared {@link parseLineStream}.
39271
+ */
39272
+ async *streamLines(path8, opts = {}) {
39273
+ yield* parseLineStream(this.streamBytes(path8, opts));
39274
+ }
39275
+ /**
39276
+ * Stream the response as SSE (Server-Sent Events). Yields one
39277
+ * {@link SseEvent} per event boundary (blank line) or on EOF for a
39278
+ * trailing event. `data:[DONE]` is delivered as an ordinary event
39279
+ * with `data === "[DONE]"` and the iterator ends on the next EOF.
39280
+ * Built on {@link streamLines} plus the shared {@link parseSseStream}.
39281
+ */
39282
+ async *streamSse(path8, opts = {}) {
39283
+ yield* parseSseStream(this.streamLines(path8, opts));
39284
+ }
39117
39285
  // ── Internal helpers ──────────────────────────────────────────────
39286
+ /**
39287
+ * Resolve a stream duration from (in order): explicit `opts` field,
39288
+ * the named env var, then the fallback. Zero disables. A non-numeric
39289
+ * or negative env value warns via a fallback rather than throwing —
39290
+ * a bad env var must not brick every streaming call.
39291
+ */
39292
+ streamSeconds(explicit, envName, fallback) {
39293
+ if (explicit !== void 0) {
39294
+ return Number.isFinite(explicit) && explicit >= 0 ? Number(explicit) : fallback;
39295
+ }
39296
+ const raw = process.env[envName];
39297
+ if (raw === void 0) return fallback;
39298
+ const n = Number(raw);
39299
+ return Number.isFinite(n) && n >= 0 ? n : fallback;
39300
+ }
39301
+ /**
39302
+ * Open a streaming HTTP request. Returns the raw
39303
+ * {@link http.IncomingMessage} once headers arrive. Redirects are NOT
39304
+ * followed on streams (a caller who needs a redirect should do a
39305
+ * regular GET first). Connect phase is bounded by `connectSec`;
39306
+ * body-phase timeout is applied by the caller (streamBytes) via
39307
+ * `res.destroy()`.
39308
+ */
39309
+ openStreamRequest(method, url, headers, data, connectSec) {
39310
+ return new Promise((resolve30, reject) => {
39311
+ let parsed;
39312
+ try {
39313
+ parsed = new URL2(url);
39314
+ } catch (err) {
39315
+ reject(err instanceof Error ? err : new Error(String(err)));
39316
+ return;
39317
+ }
39318
+ const isHttps = parsed.protocol === "https:";
39319
+ const protocolModule = isHttps ? https : http;
39320
+ const options = {
39321
+ hostname: parsed.hostname,
39322
+ port: parsed.port || (isHttps ? 443 : 80),
39323
+ path: parsed.pathname + parsed.search,
39324
+ method,
39325
+ headers,
39326
+ timeout: connectSec > 0 ? connectSec * 1e3 : void 0
39327
+ };
39328
+ if (isHttps && this.ignoreSsl) {
39329
+ options.rejectUnauthorized = false;
39330
+ }
39331
+ const req2 = protocolModule.request(options, (res) => {
39332
+ resolve30({ res });
39333
+ });
39334
+ req2.on("timeout", () => {
39335
+ req2.destroy(new ApiStreamError(`stream connect timeout after ${connectSec}s`, null));
39336
+ });
39337
+ req2.on("error", (err) => {
39338
+ reject(err);
39339
+ });
39340
+ if (data) req2.write(data);
39341
+ req2.end();
39342
+ });
39343
+ }
39118
39344
  buildUrl(path8) {
39119
39345
  if (path8.startsWith("http://") || path8.startsWith("https://")) {
39120
39346
  return path8;
@@ -41795,10 +42021,11 @@ export default class User {
41795
42021
  // ../core/src/aiClient.ts
41796
42022
  import http2 from "node:http";
41797
42023
  import https2 from "node:https";
41798
- var AiError, AiConfigError, AiTimeoutError, AiParseError, AiHTTPError, Ai;
42024
+ var AiError, AiConfigError, AiTimeoutError, AiParseError, AiHTTPError, Ai, AggregateState;
41799
42025
  var init_aiClient = __esm({
41800
42026
  "../core/src/aiClient.ts"() {
41801
42027
  "use strict";
42028
+ init_api();
41802
42029
  AiError = class extends Error {
41803
42030
  };
41804
42031
  AiConfigError = class extends AiError {
@@ -41843,10 +42070,51 @@ var init_aiClient = __esm({
41843
42070
  throw new AiParseError("AI provider returned a malformed embedding response");
41844
42071
  }
41845
42072
  }
42073
+ /**
42074
+ * Validate role + content shape. Content may be a string OR a non-empty
42075
+ * list of {type:'text'|'image', ...} parts (ADR-0060). Malformed parts
42076
+ * fail fast with AiConfigError, never reaching the wire.
42077
+ */
41846
42078
  static validateMessages(messages) {
41847
- if (!Array.isArray(messages) || messages.length === 0 || !messages.every((message) => message && ["system", "user", "assistant"].includes(message.role) && typeof message.content === "string")) {
42079
+ if (!Array.isArray(messages) || messages.length === 0) {
41848
42080
  throw new AiConfigError("AI messages must contain supported roles and string content");
41849
42081
  }
42082
+ for (const message of messages) {
42083
+ if (!message || !["system", "user", "assistant"].includes(message.role)) {
42084
+ throw new AiConfigError("AI messages must contain supported roles and string content");
42085
+ }
42086
+ this.validateContent(message.content);
42087
+ }
42088
+ }
42089
+ static validateContent(content) {
42090
+ if (typeof content === "string") return;
42091
+ if (!Array.isArray(content) || content.length === 0) {
42092
+ throw new AiConfigError("AI message content must be a string or a non-empty list of parts");
42093
+ }
42094
+ for (const part of content) {
42095
+ if (!part || typeof part !== "object" || Array.isArray(part)) {
42096
+ throw new AiConfigError("AI content part must be an object with type and text/source");
42097
+ }
42098
+ const record = part;
42099
+ const partType = record.type;
42100
+ if (partType === "text") {
42101
+ if (typeof record.text !== "string") {
42102
+ throw new AiConfigError("AI text content part requires a string 'text' field");
42103
+ }
42104
+ } else if (partType === "image") {
42105
+ if (typeof record.source !== "string" || record.source.length === 0) {
42106
+ throw new AiConfigError("AI image content part requires a non-empty string 'source' field");
42107
+ }
42108
+ if (!record.source.startsWith("data:") && !record.source.startsWith("https://")) {
42109
+ throw new AiConfigError("AI image source must be a data: URI or an https:// URL");
42110
+ }
42111
+ if (record.source.startsWith("data:") && !/^data:[^;,\s]+;base64,[A-Za-z0-9+/=]+$/.test(record.source)) {
42112
+ throw new AiConfigError("AI image data URI must be data:<media_type>;base64,<payload>");
42113
+ }
42114
+ } else {
42115
+ throw new AiConfigError(`AI content part has unknown type '${String(partType)}'`);
42116
+ }
42117
+ }
41850
42118
  }
41851
42119
  static number(name, fallback, minimum) {
41852
42120
  const value = process.env[name] === void 0 ? fallback : Number(process.env[name]);
@@ -41894,18 +42162,57 @@ var init_aiClient = __esm({
41894
42162
  }
41895
42163
  return headers;
41896
42164
  }
42165
+ /**
42166
+ * Build the provider-specific request body from a Tina4-shaped message
42167
+ * list. Multimodal parts are translated per provider (ADR-0060):
42168
+ * - OpenAI/local: {type:'image_url', image_url:{url}}
42169
+ * - Anthropic: {type:'image', source:{type:'base64'|'url', ...}}
42170
+ * String content is preserved verbatim in the OpenAI/local shape and
42171
+ * likewise for Anthropic (both accept a bare string).
42172
+ */
41897
42173
  static chatBody(config, messages, options) {
41898
- const body = { model: config.model, messages, stream: options.stream ?? false };
42174
+ const translate = (list) => list.map((message) => ({ role: message.role, content: this.translateContent(message.content, config.provider) }));
42175
+ const body = { model: config.model, messages: translate(messages), stream: options.stream ?? false };
41899
42176
  if (options.temperature !== void 0) body.temperature = options.temperature;
41900
42177
  if (options.maxTokens !== void 0) body.max_tokens = options.maxTokens;
41901
42178
  if (config.provider === "anthropic") {
41902
- const system = messages.filter((message) => message.role === "system").map((message) => message.content);
41903
- body.messages = messages.filter((message) => message.role !== "system");
42179
+ const systemParts = messages.filter((message) => message.role === "system").map((message) => typeof message.content === "string" ? message.content : this.contentToPlainText(message.content));
42180
+ body.messages = translate(messages.filter((message) => message.role !== "system"));
41904
42181
  body.max_tokens = options.maxTokens ?? 1024;
41905
- if (system.length) body.system = system.join("\n\n");
42182
+ if (systemParts.length) body.system = systemParts.join("\n\n");
41906
42183
  }
41907
42184
  return body;
41908
42185
  }
42186
+ /**
42187
+ * Translate one message content value into the provider's on-wire shape.
42188
+ * A plain string is passed through (both providers accept a string
42189
+ * content). A parts array becomes provider-native content blocks.
42190
+ */
42191
+ static translateContent(content, provider) {
42192
+ if (typeof content === "string") return content;
42193
+ if (provider === "anthropic") {
42194
+ return content.map((part) => {
42195
+ if (part.type === "text") return { type: "text", text: part.text };
42196
+ if (part.source.startsWith("data:")) {
42197
+ const parsed = this.parseDataUri(part.source);
42198
+ return { type: "image", source: { type: "base64", media_type: parsed.mediaType, data: parsed.data } };
42199
+ }
42200
+ return { type: "image", source: { type: "url", url: part.source } };
42201
+ });
42202
+ }
42203
+ return content.map((part) => {
42204
+ if (part.type === "text") return { type: "text", text: part.text };
42205
+ return { type: "image_url", image_url: { url: part.source } };
42206
+ });
42207
+ }
42208
+ static parseDataUri(source) {
42209
+ const match = /^data:([^;,\s]+);base64,([A-Za-z0-9+/=]+)$/.exec(source);
42210
+ if (!match) throw new AiConfigError("AI image data URI must be data:<media_type>;base64,<payload>");
42211
+ return { mediaType: match[1], data: match[2] };
42212
+ }
42213
+ static contentToPlainText(parts) {
42214
+ return parts.filter((part) => part.type === "text").map((part) => part.text).join("\n\n");
42215
+ }
41909
42216
  static open(config, deadline, headers, body) {
41910
42217
  const remainingMs = deadline - performance.now();
41911
42218
  if (remainingMs <= 0) return Promise.reject(new AiTimeoutError("AI total request timeout expired"));
@@ -42005,35 +42312,12 @@ var init_aiClient = __esm({
42005
42312
  static async chatResponse(config, headers, body) {
42006
42313
  return this.normalizeChat(config.provider, await this.requestJson(config, headers, body));
42007
42314
  }
42008
- static streamDelta(provider, data) {
42009
- if (data === "[DONE]") return { completed: true };
42010
- let event;
42011
- try {
42012
- event = JSON.parse(data);
42013
- } catch {
42014
- throw new AiParseError("AI provider returned malformed stream data");
42015
- }
42016
- const text = provider === "anthropic" ? event.type === "content_block_delta" ? event.delta?.text : void 0 : event.choices?.[0]?.delta?.content;
42017
- if (text !== void 0 && text !== null && typeof text !== "string") throw new AiParseError("AI provider returned malformed stream data");
42018
- return { completed: false, text };
42019
- }
42020
- static async *streamData(response) {
42021
- let buffer = "";
42022
- for await (const chunk of response) {
42023
- buffer += Buffer.from(chunk).toString("utf8");
42024
- let newline;
42025
- while ((newline = buffer.indexOf("\n")) >= 0) {
42026
- const line = buffer.slice(0, newline).trim();
42027
- buffer = buffer.slice(newline + 1);
42028
- if (line.startsWith("data:")) yield line.slice(5).trim();
42029
- }
42030
- }
42031
- }
42032
- static streamError(error) {
42033
- if (error instanceof AiError) return error;
42034
- if (error instanceof Error && error.name === "AbortError") return new AiTimeoutError("AI total request timeout expired");
42035
- return new AiHTTPError(`AI transport failed (${error instanceof Error ? error.name : "Error"})`);
42036
- }
42315
+ /**
42316
+ * Stream the response through the shared {@link parseSseStream} framer
42317
+ * (ADR-0060 rule 5). Translates each SSE data payload into 0..N
42318
+ * {@link AiEvent}s: text_delta per chunk, tool_call aggregated per
42319
+ * index / block, exactly one done (or error) at the end.
42320
+ */
42037
42321
  static async *streamRequest(config, headers, body) {
42038
42322
  const deadline = performance.now() + config.totalTimeout * 1e3;
42039
42323
  let yielded = false;
@@ -42052,27 +42336,239 @@ var init_aiClient = __esm({
42052
42336
  }
42053
42337
  throw new AiHTTPError(`AI provider returned HTTP ${status2}`, status2);
42054
42338
  }
42055
- let completed = false;
42056
- for await (const data of this.streamData(opened.response)) {
42057
- const delta = this.streamDelta(config.provider, data);
42058
- if (delta.completed) {
42059
- completed = true;
42060
- break;
42339
+ const response = opened.response;
42340
+ const chunks = this.responseChunks(response);
42341
+ const events = parseSseStream(parseLineStream(chunks));
42342
+ const aggregator = new AggregateState(config.provider);
42343
+ let done = false;
42344
+ try {
42345
+ for await (const sseEvent of events) {
42346
+ for (const emitted of aggregator.consume(sseEvent)) {
42347
+ yielded = true;
42348
+ yield emitted;
42349
+ if (emitted.type === "done" || emitted.type === "error") {
42350
+ done = true;
42351
+ break;
42352
+ }
42353
+ }
42354
+ if (done) break;
42355
+ }
42356
+ } catch (error) {
42357
+ if (yielded) {
42358
+ yielded = true;
42359
+ yield { type: "error", message: error instanceof AiParseError ? "AI provider returned malformed stream data" : `AI transport failed (${error instanceof Error ? error.name : "Error"})` };
42360
+ opened.cleanup();
42361
+ opened = null;
42362
+ return;
42061
42363
  }
42062
- if (delta.text === void 0) continue;
42063
- yielded = true;
42064
- yield delta.text;
42364
+ throw error;
42065
42365
  }
42066
42366
  opened.cleanup();
42067
42367
  opened = null;
42068
- if (completed) return;
42069
- throw new AiParseError("AI provider stream ended before [DONE]");
42368
+ if (done) return;
42369
+ if (yielded) {
42370
+ yield { type: "error", message: "AI provider stream ended before completion" };
42371
+ return;
42372
+ }
42373
+ throw new AiParseError("AI provider stream ended before completion");
42070
42374
  } catch (error) {
42071
42375
  opened?.cleanup();
42072
42376
  const failure = this.streamError(error);
42073
- if (failure instanceof AiParseError || failure instanceof AiHTTPError && failure.status !== null || yielded || attempt >= config.maxRetries) throw failure;
42377
+ if (yielded) {
42378
+ yield { type: "error", message: failure.message };
42379
+ return;
42380
+ }
42381
+ if (failure instanceof AiParseError || failure instanceof AiHTTPError && failure.status !== null || attempt >= config.maxRetries) throw failure;
42382
+ }
42383
+ }
42384
+ }
42385
+ static async *responseChunks(response) {
42386
+ for await (const chunk of response) {
42387
+ yield chunk;
42388
+ }
42389
+ }
42390
+ static streamError(error) {
42391
+ if (error instanceof AiError) return error;
42392
+ if (error instanceof Error && error.name === "AbortError") return new AiTimeoutError("AI total request timeout expired");
42393
+ return new AiHTTPError(`AI transport failed (${error instanceof Error ? error.name : "Error"})`);
42394
+ }
42395
+ };
42396
+ AggregateState = class {
42397
+ constructor(provider) {
42398
+ this.provider = provider;
42399
+ }
42400
+ toolBuffers = /* @__PURE__ */ new Map();
42401
+ lastFinishReason = null;
42402
+ lastUsage = null;
42403
+ doneEmitted = false;
42404
+ *consume(event) {
42405
+ const data = event.data;
42406
+ if (data === "[DONE]") {
42407
+ if (this.doneEmitted) return;
42408
+ yield* this.flushRemainingToolCalls();
42409
+ this.doneEmitted = true;
42410
+ yield {
42411
+ type: "done",
42412
+ finishReason: this.lastFinishReason ?? "stop",
42413
+ ...this.lastUsage ? { usage: this.lastUsage } : {}
42414
+ };
42415
+ return;
42416
+ }
42417
+ let payload;
42418
+ try {
42419
+ payload = JSON.parse(data);
42420
+ } catch {
42421
+ throw new AiParseError("AI provider returned malformed stream data");
42422
+ }
42423
+ if (this.provider === "anthropic") {
42424
+ yield* this.consumeAnthropic(payload);
42425
+ } else {
42426
+ yield* this.consumeOpenAi(payload);
42427
+ }
42428
+ }
42429
+ *consumeOpenAi(payload) {
42430
+ const choices = payload.choices;
42431
+ if (!Array.isArray(choices) || choices.length === 0) return;
42432
+ const choice = choices[0];
42433
+ const delta = choice.delta ?? {};
42434
+ const content = delta.content;
42435
+ if (typeof content === "string" && content.length > 0) {
42436
+ yield { type: "text_delta", text: content };
42437
+ }
42438
+ const toolCalls = delta.tool_calls;
42439
+ if (Array.isArray(toolCalls)) {
42440
+ for (const call of toolCalls) {
42441
+ const index = typeof call.index === "number" ? String(call.index) : String(this.toolBuffers.size);
42442
+ const idFromCall = typeof call.id === "string" ? call.id : "";
42443
+ const fn = call.function ?? {};
42444
+ const nameFromCall = typeof fn.name === "string" ? fn.name : "";
42445
+ const argsFragment = typeof fn.arguments === "string" ? fn.arguments : "";
42446
+ const existing = this.toolBuffers.get(index) ?? { id: "", name: "", args: "" };
42447
+ if (idFromCall) existing.id = idFromCall;
42448
+ if (nameFromCall) existing.name = nameFromCall;
42449
+ existing.args += argsFragment;
42450
+ this.toolBuffers.set(index, existing);
42451
+ if (existing.name && existing.args) {
42452
+ try {
42453
+ const parsed = JSON.parse(existing.args);
42454
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
42455
+ this.toolBuffers.delete(index);
42456
+ yield { type: "tool_call", id: existing.id || `call_${index}`, name: existing.name, args: parsed };
42457
+ }
42458
+ } catch {
42459
+ }
42460
+ }
42461
+ }
42462
+ }
42463
+ if (typeof choice.finish_reason === "string" && choice.finish_reason.length > 0) {
42464
+ this.lastFinishReason = choice.finish_reason;
42465
+ }
42466
+ const usage = payload.usage;
42467
+ if (usage && typeof usage === "object") {
42468
+ const promptTokens = Number(usage.prompt_tokens ?? 0);
42469
+ const completionTokens = Number(usage.completion_tokens ?? 0);
42470
+ const totalTokens = Number(usage.total_tokens ?? promptTokens + completionTokens);
42471
+ if (Number.isFinite(promptTokens) && Number.isFinite(completionTokens)) {
42472
+ this.lastUsage = { promptTokens, completionTokens, totalTokens };
42473
+ }
42474
+ }
42475
+ }
42476
+ *consumeAnthropic(payload) {
42477
+ const type2 = payload.type;
42478
+ if (type2 === "content_block_start") {
42479
+ const block = payload.content_block ?? {};
42480
+ if (block.type === "tool_use") {
42481
+ const index = String(payload.index ?? this.toolBuffers.size);
42482
+ const id = typeof block.id === "string" ? block.id : `call_${index}`;
42483
+ const name = typeof block.name === "string" ? block.name : "";
42484
+ this.toolBuffers.set(index, { id, name, args: "" });
42485
+ }
42486
+ return;
42487
+ }
42488
+ if (type2 === "content_block_delta") {
42489
+ const index = String(payload.index ?? 0);
42490
+ const delta = payload.delta ?? {};
42491
+ if (delta.type === "text_delta" && typeof delta.text === "string" && delta.text.length > 0) {
42492
+ yield { type: "text_delta", text: delta.text };
42493
+ return;
42494
+ }
42495
+ if (delta.type === "input_json_delta" && typeof delta.partial_json === "string") {
42496
+ const existing = this.toolBuffers.get(index);
42497
+ if (existing) existing.args += delta.partial_json;
42498
+ }
42499
+ return;
42500
+ }
42501
+ if (type2 === "content_block_stop") {
42502
+ const index = String(payload.index ?? 0);
42503
+ const existing = this.toolBuffers.get(index);
42504
+ if (existing && existing.name) {
42505
+ this.toolBuffers.delete(index);
42506
+ try {
42507
+ const parsed = existing.args ? JSON.parse(existing.args) : {};
42508
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
42509
+ yield { type: "tool_call", id: existing.id, name: existing.name, args: parsed };
42510
+ return;
42511
+ }
42512
+ throw new Error();
42513
+ } catch {
42514
+ throw new AiParseError("AI provider returned malformed tool-call JSON");
42515
+ }
42516
+ }
42517
+ return;
42518
+ }
42519
+ if (type2 === "message_delta") {
42520
+ const delta = payload.delta ?? {};
42521
+ if (typeof delta.stop_reason === "string" && delta.stop_reason.length > 0) {
42522
+ this.lastFinishReason = delta.stop_reason;
42523
+ }
42524
+ const usage = payload.usage ?? {};
42525
+ if (usage.output_tokens !== void 0 || usage.input_tokens !== void 0) {
42526
+ const promptTokens = Number(usage.input_tokens ?? this.lastUsage?.promptTokens ?? 0);
42527
+ const completionTokens = Number(usage.output_tokens ?? this.lastUsage?.completionTokens ?? 0);
42528
+ this.lastUsage = { promptTokens, completionTokens, totalTokens: promptTokens + completionTokens };
42529
+ }
42530
+ return;
42531
+ }
42532
+ if (type2 === "message_stop") {
42533
+ if (this.doneEmitted) return;
42534
+ this.doneEmitted = true;
42535
+ yield {
42536
+ type: "done",
42537
+ finishReason: this.lastFinishReason ?? "end_turn",
42538
+ ...this.lastUsage ? { usage: this.lastUsage } : {}
42539
+ };
42540
+ return;
42541
+ }
42542
+ if (type2 === "message_start") {
42543
+ const message = payload.message ?? {};
42544
+ const usage = message.usage ?? {};
42545
+ if (usage.input_tokens !== void 0 || usage.output_tokens !== void 0) {
42546
+ const promptTokens = Number(usage.input_tokens ?? 0);
42547
+ const completionTokens = Number(usage.output_tokens ?? 0);
42548
+ this.lastUsage = { promptTokens, completionTokens, totalTokens: promptTokens + completionTokens };
42549
+ }
42550
+ return;
42551
+ }
42552
+ if (type2 === "error") {
42553
+ const err = payload.error ?? {};
42554
+ throw new AiParseError(typeof err.message === "string" ? err.message : "AI provider signalled a stream error");
42555
+ }
42556
+ }
42557
+ *flushRemainingToolCalls() {
42558
+ for (const [index, buffered] of this.toolBuffers) {
42559
+ if (buffered.name && buffered.args) {
42560
+ try {
42561
+ const parsed = JSON.parse(buffered.args);
42562
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
42563
+ yield { type: "tool_call", id: buffered.id || `call_${index}`, name: buffered.name, args: parsed };
42564
+ continue;
42565
+ }
42566
+ } catch {
42567
+ }
42568
+ throw new AiParseError("AI provider returned malformed tool-call JSON");
42074
42569
  }
42075
42570
  }
42571
+ this.toolBuffers.clear();
42076
42572
  }
42077
42573
  };
42078
42574
  }
@@ -43788,6 +44284,7 @@ __export(src_exports3, {
43788
44284
  AiParseError: () => AiParseError,
43789
44285
  AiTimeoutError: () => AiTimeoutError,
43790
44286
  Api: () => Api,
44287
+ ApiStreamError: () => ApiStreamError,
43791
44288
  Auth: () => Auth,
43792
44289
  CANONICAL_SESSION_BACKENDS: () => CANONICAL_SESSION_BACKENDS,
43793
44290
  CLOSE_GOING_AWAY: () => CLOSE_GOING_AWAY,
@@ -44017,7 +44514,9 @@ __export(src_exports3, {
44017
44514
  originAllowed: () => originAllowed,
44018
44515
  parseAmqpUrl: () => parseAmqpUrl,
44019
44516
  parseFrame: () => parseFrame,
44517
+ parseLineStream: () => parseLineStream,
44020
44518
  parseMultipart: () => parseMultipart,
44519
+ parseSseStream: () => parseSseStream,
44021
44520
  parseUpgradeHeaders: () => parseUpgradeHeaders,
44022
44521
  patch: () => patch,
44023
44522
  pidfilePath: () => pidfilePath,