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
|
@@ -21192,47 +21192,120 @@ function renderToolbarHtml(ctx) {
|
|
|
21192
21192
|
const method = escapeHtml(ctx.method);
|
|
21193
21193
|
const path8 = escapeHtml(ctx.path);
|
|
21194
21194
|
const matchedPattern = escapeHtml(ctx.matchedPattern);
|
|
21195
|
-
|
|
21196
|
-
|
|
21197
|
-
|
|
21198
|
-
|
|
21199
|
-
|
|
21200
|
-
|
|
21195
|
+
const reload = ctx.reload === false ? "0" : "1";
|
|
21196
|
+
return `<link rel="stylesheet" href="/__dev/toolbar.css">
|
|
21197
|
+
<div id="tina4-dev-toolbar" data-reload="${reload}">
|
|
21198
|
+
<span id="tina4-ver-btn" title="Click to check for updates">Tina4 v${ctx.version}</span>
|
|
21199
|
+
<div id="tina4-ver-modal">
|
|
21200
|
+
<div class="t4-modal-head">
|
|
21201
|
+
<strong class="t4-modal-title">Version Info</strong>
|
|
21202
|
+
<span id="tina4-ver-close" class="t4-x">×</span>
|
|
21201
21203
|
</div>
|
|
21202
|
-
<div id="tina4-ver-body"
|
|
21203
|
-
<div>Current: <strong
|
|
21204
|
-
<div id="tina4-ver-latest"
|
|
21204
|
+
<div id="tina4-ver-body">
|
|
21205
|
+
<div>Current: <strong class="t4-ok">v${ctx.version}</strong></div>
|
|
21206
|
+
<div id="tina4-ver-latest" class="t4-dim">Checking for updates...</div>
|
|
21205
21207
|
</div>
|
|
21206
21208
|
</div>
|
|
21207
|
-
<span
|
|
21209
|
+
<span class="t4-green">${method}</span>
|
|
21208
21210
|
<span>${path8}</span>
|
|
21209
|
-
<span
|
|
21210
|
-
<span
|
|
21211
|
-
<span
|
|
21212
|
-
<span
|
|
21213
|
-
<a href="#"
|
|
21214
|
-
<span
|
|
21211
|
+
<span class="t4-arrow">→ ${matchedPattern}</span>
|
|
21212
|
+
<span class="t4-yellow">req:${ctx.requestId}</span>
|
|
21213
|
+
<span class="t4-blue">${ctx.routeCount} routes</span>
|
|
21214
|
+
<span class="t4-dim">Node.js ${nodeVersion}</span>
|
|
21215
|
+
<a href="#" id="tina4-dash-link" class="t4-dash">Dashboard ↗</a>
|
|
21216
|
+
<span id="tina4-bar-close" class="t4-x t4-bar-close">✕</span>
|
|
21215
21217
|
</div>
|
|
21216
|
-
<script
|
|
21217
|
-
|
|
21218
|
-
|
|
21219
|
-
|
|
21220
|
-
|
|
21221
|
-
|
|
21218
|
+
<script src="/__dev/toolbar.js"></script>`;
|
|
21219
|
+
}
|
|
21220
|
+
function toolbarCss() {
|
|
21221
|
+
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}
|
|
21222
|
+
#tina4-dev-toolbar a{text-decoration:none}
|
|
21223
|
+
#tina4-ver-btn{color:#2e7d32;font-weight:bold;cursor:pointer;text-decoration:underline dotted}
|
|
21224
|
+
#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}
|
|
21225
|
+
.t4-modal-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:12px}
|
|
21226
|
+
.t4-modal-title{color:#89b4fa}
|
|
21227
|
+
#tina4-ver-body{line-height:1.8}
|
|
21228
|
+
.t4-x{cursor:pointer;color:#888}
|
|
21229
|
+
.t4-bar-close{margin-left:8px}
|
|
21230
|
+
.t4-green{color:#4caf50}
|
|
21231
|
+
.t4-dim{color:#888}
|
|
21232
|
+
.t4-arrow{color:#666}
|
|
21233
|
+
.t4-yellow{color:#ffeb3b}
|
|
21234
|
+
.t4-blue{color:#90caf9}
|
|
21235
|
+
.t4-ok{color:#a6e3a1}
|
|
21236
|
+
.t4-warn{color:#f9e2af}
|
|
21237
|
+
.t4-err{color:#f38ba8}
|
|
21238
|
+
.t4-purple{color:#cba6f7}
|
|
21239
|
+
.t4-link{color:#89b4fa}
|
|
21240
|
+
.t4-code{background:#313244;padding:2px 6px;border-radius:3px}
|
|
21241
|
+
.t4-note{margin-top:6px}
|
|
21242
|
+
.t4-dash{color:#ef9a9a;margin-left:auto;cursor:pointer}
|
|
21243
|
+
#tina4-dev-panel{position:fixed;top:3rem;left:0;right:0;bottom:2rem;z-index:99998;transition:all .2s}
|
|
21244
|
+
#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}`;
|
|
21245
|
+
}
|
|
21246
|
+
function toolbarJs() {
|
|
21247
|
+
return `(function () {
|
|
21248
|
+
var bar = document.getElementById('tina4-dev-toolbar');
|
|
21249
|
+
if (!bar) { return; }
|
|
21250
|
+
|
|
21251
|
+
var modal = document.getElementById('tina4-ver-modal');
|
|
21252
|
+
function upToDate(el, latest) {
|
|
21253
|
+
el.className = 't4-ok';
|
|
21254
|
+
el.innerHTML = 'Latest: <strong class="t4-ok">v' + latest + '</strong> — You are up to date!';
|
|
21255
|
+
}
|
|
21256
|
+
function checkVersion() {
|
|
21257
|
+
if (modal.style.display === 'block') { modal.style.display = 'none'; return; }
|
|
21258
|
+
modal.style.display = 'block';
|
|
21259
|
+
var el = document.getElementById('tina4-ver-latest');
|
|
21260
|
+
el.className = 't4-dim';
|
|
21261
|
+
el.textContent = 'Checking for updates...';
|
|
21262
|
+
fetch('/__dev/api/version-check').then(function (r) { return r.json(); }).then(function (d) {
|
|
21263
|
+
var latest = d.latest, current = d.current;
|
|
21264
|
+
if (latest === current) { upToDate(el, latest); return; }
|
|
21265
|
+
var cP = current.split('.').map(Number), lP = latest.split('.').map(Number);
|
|
21266
|
+
var isNewer = false, i, c, l;
|
|
21267
|
+
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; } }
|
|
21268
|
+
var isAhead = false;
|
|
21269
|
+
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; } } }
|
|
21270
|
+
if (isNewer) {
|
|
21271
|
+
var breaking = (lP[0] !== cP[0] || lP[1] !== cP[1]);
|
|
21272
|
+
el.className = '';
|
|
21273
|
+
el.innerHTML = 'Latest: <strong class="t4-warn">v' + latest + '</strong>';
|
|
21274
|
+
if (breaking) {
|
|
21275
|
+
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>';
|
|
21276
|
+
} else {
|
|
21277
|
+
el.innerHTML += '<div class="t4-warn t4-note">Patch update available. Run: <code class="t4-code">npm install tina4-nodejs@latest</code></div>';
|
|
21278
|
+
}
|
|
21279
|
+
} else if (isAhead) {
|
|
21280
|
+
el.className = 't4-purple';
|
|
21281
|
+
el.innerHTML = 'You are running <strong class="t4-purple">v' + current + '</strong> (ahead of npm <strong>v' + latest + '</strong> — not yet published).';
|
|
21282
|
+
} else {
|
|
21283
|
+
upToDate(el, latest);
|
|
21284
|
+
}
|
|
21285
|
+
}).catch(function () {
|
|
21286
|
+
el.className = 't4-err';
|
|
21287
|
+
el.textContent = 'Could not check for updates (offline?)';
|
|
21288
|
+
});
|
|
21289
|
+
}
|
|
21290
|
+
var verBtn = document.getElementById('tina4-ver-btn');
|
|
21291
|
+
if (verBtn) { verBtn.addEventListener('click', checkVersion); }
|
|
21292
|
+
var verClose = document.getElementById('tina4-ver-close');
|
|
21293
|
+
if (verClose) { verClose.addEventListener('click', function () { modal.style.display = 'none'; }); }
|
|
21294
|
+
var barClose = document.getElementById('tina4-bar-close');
|
|
21295
|
+
if (barClose) { barClose.addEventListener('click', function () { bar.style.display = 'none'; }); }
|
|
21296
|
+
|
|
21222
21297
|
var STATE_KEY = 'tina4_dev_overlay_open';
|
|
21223
21298
|
function buildOverlay() {
|
|
21224
21299
|
var c = document.createElement('div');
|
|
21225
21300
|
c.id = 'tina4-dev-panel';
|
|
21226
|
-
c.style.cssText = 'position:fixed;top:3rem;left:0;right:0;bottom:2rem;z-index:99998;transition:all 0.2s';
|
|
21227
21301
|
var f = document.createElement('iframe');
|
|
21228
21302
|
f.src = '/__dev';
|
|
21229
|
-
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';
|
|
21230
21303
|
c.appendChild(f);
|
|
21231
21304
|
document.body.appendChild(c);
|
|
21232
21305
|
return c;
|
|
21233
21306
|
}
|
|
21234
|
-
|
|
21235
|
-
if (e) e.preventDefault();
|
|
21307
|
+
function toggleOverlay(e) {
|
|
21308
|
+
if (e) { e.preventDefault(); }
|
|
21236
21309
|
var p = document.getElementById('tina4-dev-panel');
|
|
21237
21310
|
if (p) {
|
|
21238
21311
|
var hide = p.style.display !== 'none';
|
|
@@ -21242,137 +21315,61 @@ function renderToolbarHtml(ctx) {
|
|
|
21242
21315
|
}
|
|
21243
21316
|
buildOverlay();
|
|
21244
21317
|
try { localStorage.setItem(STATE_KEY, '1'); } catch (_) {}
|
|
21245
|
-
};
|
|
21246
|
-
function restoreIfOpen() {
|
|
21247
|
-
try {
|
|
21248
|
-
if (location.pathname.indexOf('/__dev') === 0) return;
|
|
21249
|
-
if (localStorage.getItem(STATE_KEY) === '1' && !document.getElementById('tina4-dev-panel')) {
|
|
21250
|
-
buildOverlay();
|
|
21251
|
-
}
|
|
21252
|
-
} catch (_) {}
|
|
21253
21318
|
}
|
|
21254
|
-
|
|
21255
|
-
|
|
21256
|
-
|
|
21257
|
-
|
|
21258
|
-
|
|
21259
|
-
|
|
21260
|
-
|
|
21261
|
-
|
|
21262
|
-
|
|
21263
|
-
|
|
21264
|
-
if(
|
|
21265
|
-
|
|
21266
|
-
var
|
|
21267
|
-
|
|
21268
|
-
|
|
21269
|
-
|
|
21270
|
-
|
|
21271
|
-
|
|
21272
|
-
|
|
21273
|
-
|
|
21274
|
-
|
|
21275
|
-
el.innerHTML='Latest: <strong style="color:#a6e3a1;">v'+latest+'</strong> — You are up to date!';
|
|
21276
|
-
el.style.color='#a6e3a1';
|
|
21277
|
-
}else{
|
|
21278
|
-
var cParts=current.split('.').map(Number);
|
|
21279
|
-
var lParts=latest.split('.').map(Number);
|
|
21280
|
-
var isNewer=false;
|
|
21281
|
-
for(var i=0;i<Math.max(cParts.length,lParts.length);i++){
|
|
21282
|
-
var c=cParts[i]||0,l=lParts[i]||0;
|
|
21283
|
-
if(l>c){isNewer=true;break;}
|
|
21284
|
-
if(l<c)break;
|
|
21285
|
-
}
|
|
21286
|
-
var isAhead=false;
|
|
21287
|
-
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;}}
|
|
21288
|
-
if(isNewer){
|
|
21289
|
-
var breaking=(lParts[0]!==cParts[0]||lParts[1]!==cParts[1]);
|
|
21290
|
-
el.innerHTML='Latest: <strong style="color:#f9e2af;">v'+latest+'</strong>';
|
|
21291
|
-
if(breaking){
|
|
21292
|
-
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>';
|
|
21293
|
-
}else{
|
|
21294
|
-
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>';
|
|
21295
|
-
}
|
|
21296
|
-
}else if(isAhead){
|
|
21297
|
-
el.innerHTML='You are running <strong style="color:#cba6f7;">v'+current+'</strong> (ahead of npm <strong>v'+latest+'</strong> — not yet published).';
|
|
21298
|
-
el.style.color='#cba6f7';
|
|
21299
|
-
}else{
|
|
21300
|
-
el.innerHTML='Latest: <strong style="color:#a6e3a1;">v'+latest+'</strong> — You are up to date!';
|
|
21301
|
-
el.style.color='#a6e3a1';
|
|
21302
|
-
}
|
|
21303
|
-
}
|
|
21304
|
-
})
|
|
21305
|
-
.catch(function(){
|
|
21306
|
-
el.innerHTML='Could not check for updates (offline?)';
|
|
21307
|
-
el.style.color='#f38ba8';
|
|
21308
|
-
});
|
|
21309
|
-
}
|
|
21310
|
-
</script>
|
|
21311
|
-
<script>
|
|
21312
|
-
(function(){
|
|
21313
|
-
// WebSocket-primary dev reloader. The running server re-imports changed
|
|
21314
|
-
// src/ routes in-process and pushes a {type,file,mtime} message over
|
|
21315
|
-
// /__dev_reload \u2014 no respawn, instant refresh. The mtime poll below is a
|
|
21316
|
-
// FALLBACK only, started when the socket is down and stopped on connect.
|
|
21317
|
-
var _t4_css_exts=['.css','.scss'],_t4_debounce=null;
|
|
21318
|
-
var _t4_interval=3000;
|
|
21319
|
-
var _t4_ws=null,_t4_poll_timer=null,_t4_mtime=null;
|
|
21320
|
-
function _t4_apply(d){
|
|
21321
|
-
d=d||{};
|
|
21322
|
-
var f=d.file||'',t=d.type||'';
|
|
21323
|
-
var isCss=t==='css'||_t4_css_exts.some(function(e){return f.endsWith(e)});
|
|
21324
|
-
if(isCss){
|
|
21325
|
-
var links=document.querySelectorAll('link[rel="stylesheet"]');
|
|
21326
|
-
links.forEach(function(l){
|
|
21327
|
-
var href=l.getAttribute('href');
|
|
21328
|
-
if(href){l.setAttribute('href',href.split('?')[0]+'?_t4='+(d.mtime||Date.now()))}
|
|
21319
|
+
var dash = document.getElementById('tina4-dash-link');
|
|
21320
|
+
if (dash) { dash.addEventListener('click', toggleOverlay); }
|
|
21321
|
+
try {
|
|
21322
|
+
if (location.pathname.indexOf('/__dev') !== 0
|
|
21323
|
+
&& localStorage.getItem(STATE_KEY) === '1'
|
|
21324
|
+
&& !document.getElementById('tina4-dev-panel')) {
|
|
21325
|
+
buildOverlay();
|
|
21326
|
+
}
|
|
21327
|
+
} catch (_) {}
|
|
21328
|
+
|
|
21329
|
+
if (bar.getAttribute('data-reload') !== '1') { return; }
|
|
21330
|
+
var cssExts = ['.css', '.scss'], debounce = null, interval = 3000;
|
|
21331
|
+
var ws = null, pollTimer = null, mtime = null;
|
|
21332
|
+
function apply(d) {
|
|
21333
|
+
d = d || {};
|
|
21334
|
+
var f = d.file || '', t = d.type || '';
|
|
21335
|
+
var isCss = t === 'css' || cssExts.some(function (e) { return f.endsWith(e); });
|
|
21336
|
+
if (isCss) {
|
|
21337
|
+
document.querySelectorAll('link[rel="stylesheet"]').forEach(function (l) {
|
|
21338
|
+
var href = l.getAttribute('href');
|
|
21339
|
+
if (href) { l.setAttribute('href', href.split('?')[0] + '?_t4=' + (d.mtime || Date.now())); }
|
|
21329
21340
|
});
|
|
21330
|
-
}else{
|
|
21341
|
+
} else {
|
|
21331
21342
|
location.reload();
|
|
21332
21343
|
}
|
|
21333
21344
|
}
|
|
21334
|
-
function
|
|
21335
|
-
fetch('/__dev/api/mtime').then(function(r){return r.json()}).then(function(d){
|
|
21336
|
-
|
|
21337
|
-
|
|
21338
|
-
|
|
21339
|
-
|
|
21340
|
-
|
|
21341
|
-
|
|
21342
|
-
|
|
21343
|
-
|
|
21344
|
-
|
|
21345
|
-
|
|
21346
|
-
|
|
21347
|
-
|
|
21348
|
-
|
|
21349
|
-
|
|
21350
|
-
|
|
21351
|
-
|
|
21352
|
-
|
|
21353
|
-
if(_t4_poll_timer){clearInterval(_t4_poll_timer);_t4_poll_timer=null;}
|
|
21354
|
-
}
|
|
21355
|
-
function _t4_connect(){
|
|
21356
|
-
var url=(location.protocol==='https:'?'wss':'ws')+'://'+location.host+'/__dev_reload';
|
|
21357
|
-
try{_t4_ws=new WebSocket(url);}catch(_){_t4_startPoll();return;}
|
|
21358
|
-
_t4_ws.addEventListener('open',function(){_t4_stopPoll();});
|
|
21359
|
-
_t4_ws.addEventListener('message',function(ev){
|
|
21360
|
-
var d=null;
|
|
21361
|
-
try{d=typeof ev.data==='string'?JSON.parse(ev.data):null;}catch(_){}
|
|
21362
|
-
if(!d)return;
|
|
21363
|
-
if(d.type==='reload'||d.type==='change'||d.type==='css'){
|
|
21364
|
-
if(_t4_debounce)clearTimeout(_t4_debounce);
|
|
21365
|
-
_t4_debounce=setTimeout(function(){_t4_apply(d);},150);
|
|
21345
|
+
function poll() {
|
|
21346
|
+
fetch('/__dev/api/mtime').then(function (r) { return r.json(); }).then(function (d) {
|
|
21347
|
+
if (mtime === null) { mtime = d.mtime; return; }
|
|
21348
|
+
if (d.mtime !== mtime) { mtime = d.mtime; if (debounce) { clearTimeout(debounce); } debounce = setTimeout(function () { apply(d); }, 500); }
|
|
21349
|
+
}).catch(function () {});
|
|
21350
|
+
}
|
|
21351
|
+
function startPoll() { if (pollTimer) { return; } mtime = null; pollTimer = setInterval(poll, interval); }
|
|
21352
|
+
function stopPoll() { if (pollTimer) { clearInterval(pollTimer); pollTimer = null; } }
|
|
21353
|
+
function connect() {
|
|
21354
|
+
var url = (location.protocol === 'https:' ? 'wss' : 'ws') + '://' + location.host + '/__dev_reload';
|
|
21355
|
+
try { ws = new WebSocket(url); } catch (_) { startPoll(); return; }
|
|
21356
|
+
ws.addEventListener('open', function () { stopPoll(); });
|
|
21357
|
+
ws.addEventListener('message', function (ev) {
|
|
21358
|
+
var d = null;
|
|
21359
|
+
try { d = typeof ev.data === 'string' ? JSON.parse(ev.data) : null; } catch (_) {}
|
|
21360
|
+
if (!d) { return; }
|
|
21361
|
+
if (d.type === 'reload' || d.type === 'change' || d.type === 'css') {
|
|
21362
|
+
if (debounce) { clearTimeout(debounce); }
|
|
21363
|
+
debounce = setTimeout(function () { apply(d); }, 150);
|
|
21366
21364
|
}
|
|
21367
21365
|
});
|
|
21368
|
-
|
|
21369
|
-
|
|
21366
|
+
ws.addEventListener('close', function () { ws = null; startPoll(); setTimeout(connect, 2000); });
|
|
21367
|
+
ws.addEventListener('error', function () { try { ws && ws.close(); } catch (_) {} });
|
|
21370
21368
|
}
|
|
21371
|
-
|
|
21372
|
-
})()
|
|
21373
|
-
</script>`;
|
|
21369
|
+
connect();
|
|
21370
|
+
})();`;
|
|
21374
21371
|
}
|
|
21375
|
-
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;
|
|
21372
|
+
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;
|
|
21376
21373
|
var init_devAdmin = __esm({
|
|
21377
21374
|
"../core/src/devAdmin.ts"() {
|
|
21378
21375
|
"use strict";
|
|
@@ -21838,7 +21835,12 @@ var init_devAdmin = __esm({
|
|
|
21838
21835
|
{ method: "GET", pattern: "/__dev/api/docs/index", handler: handleDocsIndex },
|
|
21839
21836
|
{ method: "GET", pattern: "/__dev/api/docs/.well-known.json", handler: handleDocsWellKnown },
|
|
21840
21837
|
// JS asset
|
|
21841
|
-
{ method: "GET", pattern: "/__dev/js/tina4-dev-admin.min.js", handler: handleDevAdminJs }
|
|
21838
|
+
{ method: "GET", pattern: "/__dev/js/tina4-dev-admin.min.js", handler: handleDevAdminJs },
|
|
21839
|
+
// Dev-toolbar assets — served as external CSS/JS so the injected toolbar is
|
|
21840
|
+
// CSP-clean (no inline style=, onclick=, or <script>) under a strict
|
|
21841
|
+
// `default-src 'self'`. Parity with PHP's /__dev/toolbar.css + toolbar.js.
|
|
21842
|
+
{ method: "GET", pattern: "/__dev/toolbar.css", handler: handleToolbarCss },
|
|
21843
|
+
{ method: "GET", pattern: "/__dev/toolbar.js", handler: handleToolbarJs }
|
|
21842
21844
|
];
|
|
21843
21845
|
for (const route of routes) {
|
|
21844
21846
|
router.addRoute({
|
|
@@ -23274,6 +23276,14 @@ var init_devAdmin = __esm({
|
|
|
23274
23276
|
res.raw.writeHead(404, { "Content-Type": "text/plain" });
|
|
23275
23277
|
res.raw.end("tina4-dev-admin.min.js not found");
|
|
23276
23278
|
};
|
|
23279
|
+
handleToolbarCss = (_req, res) => {
|
|
23280
|
+
res.raw.writeHead(200, { "Content-Type": "text/css; charset=utf-8", "Cache-Control": "no-cache" });
|
|
23281
|
+
res.raw.end(toolbarCss());
|
|
23282
|
+
};
|
|
23283
|
+
handleToolbarJs = (_req, res) => {
|
|
23284
|
+
res.raw.writeHead(200, { "Content-Type": "application/javascript; charset=utf-8", "Cache-Control": "no-cache" });
|
|
23285
|
+
res.raw.end(toolbarJs());
|
|
23286
|
+
};
|
|
23277
23287
|
}
|
|
23278
23288
|
});
|
|
23279
23289
|
|
|
@@ -25135,7 +25145,11 @@ function injectIntoHtml(ctx, devToolbar, html) {
|
|
|
25135
25145
|
path: ctx.pathname,
|
|
25136
25146
|
matchedPattern: ctx.matchedPattern.value || ctx.pathname,
|
|
25137
25147
|
requestId: ctx.requestId,
|
|
25138
|
-
routeCount: ctx.router.getRoutes().length
|
|
25148
|
+
routeCount: ctx.router.getRoutes().length,
|
|
25149
|
+
// Suppress the live reloader on the AI/stable port (data-reload="0"); the
|
|
25150
|
+
// toolbar JS early-returns when data-reload !== "1". Mirrors PHP's
|
|
25151
|
+
// suppressReload flag.
|
|
25152
|
+
reload: !ctx.isAiPortRequest
|
|
25139
25153
|
};
|
|
25140
25154
|
return injectFeedbackWidget(ctx.req, injectDevToolbar(html, toolbarCtx));
|
|
25141
25155
|
}
|
|
@@ -27597,11 +27611,100 @@ function buildMultipartBody(boundary, fieldName, filename, fileContent, contentT
|
|
|
27597
27611
|
parts.push(Buffer.from(delimiter2 + "--" + crlf, "utf-8"));
|
|
27598
27612
|
return Buffer.concat(parts);
|
|
27599
27613
|
}
|
|
27600
|
-
|
|
27614
|
+
async function* parseLineStream(chunks) {
|
|
27615
|
+
const decoder = new TextDecoder("utf-8");
|
|
27616
|
+
let buffer = "";
|
|
27617
|
+
for await (const chunk of chunks) {
|
|
27618
|
+
buffer += decoder.decode(chunk, { stream: true });
|
|
27619
|
+
let idx;
|
|
27620
|
+
while ((idx = buffer.indexOf("\n")) >= 0) {
|
|
27621
|
+
let line = buffer.slice(0, idx);
|
|
27622
|
+
buffer = buffer.slice(idx + 1);
|
|
27623
|
+
if (line.endsWith("\r")) {
|
|
27624
|
+
line = line.slice(0, -1);
|
|
27625
|
+
}
|
|
27626
|
+
yield line;
|
|
27627
|
+
}
|
|
27628
|
+
}
|
|
27629
|
+
buffer += decoder.decode();
|
|
27630
|
+
if (buffer.length > 0) {
|
|
27631
|
+
if (buffer.endsWith("\r")) {
|
|
27632
|
+
buffer = buffer.slice(0, -1);
|
|
27633
|
+
}
|
|
27634
|
+
yield buffer;
|
|
27635
|
+
}
|
|
27636
|
+
}
|
|
27637
|
+
async function* parseSseStream(lines) {
|
|
27638
|
+
let dataParts = [];
|
|
27639
|
+
let event;
|
|
27640
|
+
let id;
|
|
27641
|
+
let retry;
|
|
27642
|
+
let has = false;
|
|
27643
|
+
const emit = () => {
|
|
27644
|
+
if (!has) return null;
|
|
27645
|
+
const ev = { data: dataParts.join("\n") };
|
|
27646
|
+
if (event !== void 0) ev.event = event;
|
|
27647
|
+
if (id !== void 0) ev.id = id;
|
|
27648
|
+
if (retry !== void 0) ev.retry = retry;
|
|
27649
|
+
return ev;
|
|
27650
|
+
};
|
|
27651
|
+
const reset2 = () => {
|
|
27652
|
+
dataParts = [];
|
|
27653
|
+
event = void 0;
|
|
27654
|
+
id = void 0;
|
|
27655
|
+
retry = void 0;
|
|
27656
|
+
has = false;
|
|
27657
|
+
};
|
|
27658
|
+
for await (const line of lines) {
|
|
27659
|
+
if (line === "") {
|
|
27660
|
+
const ev = emit();
|
|
27661
|
+
if (ev) yield ev;
|
|
27662
|
+
reset2();
|
|
27663
|
+
continue;
|
|
27664
|
+
}
|
|
27665
|
+
if (line.startsWith(":")) continue;
|
|
27666
|
+
const colon = line.indexOf(":");
|
|
27667
|
+
const field = colon < 0 ? line : line.slice(0, colon);
|
|
27668
|
+
let value = colon < 0 ? "" : line.slice(colon + 1);
|
|
27669
|
+
if (value.startsWith(" ")) value = value.slice(1);
|
|
27670
|
+
switch (field) {
|
|
27671
|
+
case "data":
|
|
27672
|
+
dataParts.push(value);
|
|
27673
|
+
has = true;
|
|
27674
|
+
break;
|
|
27675
|
+
case "event":
|
|
27676
|
+
event = value;
|
|
27677
|
+
has = true;
|
|
27678
|
+
break;
|
|
27679
|
+
case "id":
|
|
27680
|
+
id = value;
|
|
27681
|
+
has = true;
|
|
27682
|
+
break;
|
|
27683
|
+
case "retry": {
|
|
27684
|
+
const parsed = Number(value);
|
|
27685
|
+
if (Number.isFinite(parsed) && parsed >= 0) {
|
|
27686
|
+
retry = parsed;
|
|
27687
|
+
has = true;
|
|
27688
|
+
}
|
|
27689
|
+
break;
|
|
27690
|
+
}
|
|
27691
|
+
}
|
|
27692
|
+
}
|
|
27693
|
+
const trailing = emit();
|
|
27694
|
+
if (trailing) yield trailing;
|
|
27695
|
+
}
|
|
27696
|
+
var ApiStreamError, RETRY_STATUSES, DOWNLOAD_CHUNK_SIZE, MAX_REDIRECTS, STRIP_ON_CROSS_ORIGIN, MIME_BY_EXT, Api;
|
|
27601
27697
|
var init_api = __esm({
|
|
27602
27698
|
"../core/src/api.ts"() {
|
|
27603
27699
|
"use strict";
|
|
27604
27700
|
init_version();
|
|
27701
|
+
ApiStreamError = class extends Error {
|
|
27702
|
+
constructor(message, status2 = null) {
|
|
27703
|
+
super(message);
|
|
27704
|
+
this.status = status2;
|
|
27705
|
+
this.name = "ApiStreamError";
|
|
27706
|
+
}
|
|
27707
|
+
};
|
|
27605
27708
|
RETRY_STATUSES = /* @__PURE__ */ new Set([429, 500, 502, 503, 504]);
|
|
27606
27709
|
DOWNLOAD_CHUNK_SIZE = 64 * 1024;
|
|
27607
27710
|
MAX_REDIRECTS = 10;
|
|
@@ -27920,7 +28023,130 @@ var init_api = __esm({
|
|
|
27920
28023
|
}
|
|
27921
28024
|
return { http_code: code, headers: respHeaders, error: null, path: destPath };
|
|
27922
28025
|
}
|
|
28026
|
+
/**
|
|
28027
|
+
* Stream a response body as raw bytes. Yields the chunks the transport
|
|
28028
|
+
* delivers, in order, never buffered whole. Ends cleanly on EOF and
|
|
28029
|
+
* throws on a transport failure or a non-2xx status (body drained
|
|
28030
|
+
* first). No JSON decoding, no line splitting, no framing —
|
|
28031
|
+
* {@link streamLines} and {@link streamSse} build on this primitive.
|
|
28032
|
+
*
|
|
28033
|
+
* Closing the iterator before EOF (a `break` out of a `for await`)
|
|
28034
|
+
* destroys the underlying socket, so a caller who takes only the
|
|
28035
|
+
* first few chunks never leaks the connection.
|
|
28036
|
+
*
|
|
28037
|
+
* `opts.timeout` bounds the whole stream duration (default
|
|
28038
|
+
* `TINA4_API_TIMEOUT` or the client `timeout`); `opts.connectTimeout`
|
|
28039
|
+
* bounds just the connect + headers phase (default
|
|
28040
|
+
* `TINA4_API_CONNECT_TIMEOUT` or 10s).
|
|
28041
|
+
*/
|
|
28042
|
+
async *streamBytes(path8, opts = {}) {
|
|
28043
|
+
const url = this.buildUrl(path8);
|
|
28044
|
+
const method = (opts.method ?? "GET").toUpperCase();
|
|
28045
|
+
const contentType = opts.contentType ?? "application/json";
|
|
28046
|
+
const { headers, data } = this.buildRequest(method, contentType, opts.body, opts.headers);
|
|
28047
|
+
const totalSec = this.streamSeconds(opts.timeout, "TINA4_API_TIMEOUT", this.timeout);
|
|
28048
|
+
const connectSec = this.streamSeconds(opts.connectTimeout, "TINA4_API_CONNECT_TIMEOUT", 10);
|
|
28049
|
+
const opened = await this.openStreamRequest(method, url, headers, data, connectSec);
|
|
28050
|
+
const res = opened.res;
|
|
28051
|
+
const status2 = res.statusCode ?? 0;
|
|
28052
|
+
this.storeCookies(res.headers["set-cookie"]);
|
|
28053
|
+
if (status2 < 200 || status2 >= 300) {
|
|
28054
|
+
res.resume();
|
|
28055
|
+
throw new ApiStreamError(`stream failed with HTTP ${status2}`, status2);
|
|
28056
|
+
}
|
|
28057
|
+
let totalTimer = null;
|
|
28058
|
+
if (totalSec > 0) {
|
|
28059
|
+
totalTimer = setTimeout(() => {
|
|
28060
|
+
res.destroy(new ApiStreamError(`stream total timeout after ${totalSec}s`, null));
|
|
28061
|
+
}, totalSec * 1e3);
|
|
28062
|
+
}
|
|
28063
|
+
try {
|
|
28064
|
+
for await (const chunk of res) {
|
|
28065
|
+
yield chunk;
|
|
28066
|
+
}
|
|
28067
|
+
} finally {
|
|
28068
|
+
if (totalTimer) clearTimeout(totalTimer);
|
|
28069
|
+
if (!res.destroyed) res.destroy();
|
|
28070
|
+
}
|
|
28071
|
+
}
|
|
28072
|
+
/**
|
|
28073
|
+
* Stream the response body as UTF-8 lines. Splits on LF or CRLF;
|
|
28074
|
+
* buffers a multibyte codepoint that lands across a chunk boundary;
|
|
28075
|
+
* yields a trailing line without a terminator on EOF. Built on
|
|
28076
|
+
* {@link streamBytes} plus the shared {@link parseLineStream}.
|
|
28077
|
+
*/
|
|
28078
|
+
async *streamLines(path8, opts = {}) {
|
|
28079
|
+
yield* parseLineStream(this.streamBytes(path8, opts));
|
|
28080
|
+
}
|
|
28081
|
+
/**
|
|
28082
|
+
* Stream the response as SSE (Server-Sent Events). Yields one
|
|
28083
|
+
* {@link SseEvent} per event boundary (blank line) or on EOF for a
|
|
28084
|
+
* trailing event. `data:[DONE]` is delivered as an ordinary event
|
|
28085
|
+
* with `data === "[DONE]"` and the iterator ends on the next EOF.
|
|
28086
|
+
* Built on {@link streamLines} plus the shared {@link parseSseStream}.
|
|
28087
|
+
*/
|
|
28088
|
+
async *streamSse(path8, opts = {}) {
|
|
28089
|
+
yield* parseSseStream(this.streamLines(path8, opts));
|
|
28090
|
+
}
|
|
27923
28091
|
// ── Internal helpers ──────────────────────────────────────────────
|
|
28092
|
+
/**
|
|
28093
|
+
* Resolve a stream duration from (in order): explicit `opts` field,
|
|
28094
|
+
* the named env var, then the fallback. Zero disables. A non-numeric
|
|
28095
|
+
* or negative env value warns via a fallback rather than throwing —
|
|
28096
|
+
* a bad env var must not brick every streaming call.
|
|
28097
|
+
*/
|
|
28098
|
+
streamSeconds(explicit, envName, fallback) {
|
|
28099
|
+
if (explicit !== void 0) {
|
|
28100
|
+
return Number.isFinite(explicit) && explicit >= 0 ? Number(explicit) : fallback;
|
|
28101
|
+
}
|
|
28102
|
+
const raw = process.env[envName];
|
|
28103
|
+
if (raw === void 0) return fallback;
|
|
28104
|
+
const n = Number(raw);
|
|
28105
|
+
return Number.isFinite(n) && n >= 0 ? n : fallback;
|
|
28106
|
+
}
|
|
28107
|
+
/**
|
|
28108
|
+
* Open a streaming HTTP request. Returns the raw
|
|
28109
|
+
* {@link http.IncomingMessage} once headers arrive. Redirects are NOT
|
|
28110
|
+
* followed on streams (a caller who needs a redirect should do a
|
|
28111
|
+
* regular GET first). Connect phase is bounded by `connectSec`;
|
|
28112
|
+
* body-phase timeout is applied by the caller (streamBytes) via
|
|
28113
|
+
* `res.destroy()`.
|
|
28114
|
+
*/
|
|
28115
|
+
openStreamRequest(method, url, headers, data, connectSec) {
|
|
28116
|
+
return new Promise((resolve20, reject) => {
|
|
28117
|
+
let parsed;
|
|
28118
|
+
try {
|
|
28119
|
+
parsed = new URL2(url);
|
|
28120
|
+
} catch (err) {
|
|
28121
|
+
reject(err instanceof Error ? err : new Error(String(err)));
|
|
28122
|
+
return;
|
|
28123
|
+
}
|
|
28124
|
+
const isHttps = parsed.protocol === "https:";
|
|
28125
|
+
const protocolModule = isHttps ? https : http;
|
|
28126
|
+
const options = {
|
|
28127
|
+
hostname: parsed.hostname,
|
|
28128
|
+
port: parsed.port || (isHttps ? 443 : 80),
|
|
28129
|
+
path: parsed.pathname + parsed.search,
|
|
28130
|
+
method,
|
|
28131
|
+
headers,
|
|
28132
|
+
timeout: connectSec > 0 ? connectSec * 1e3 : void 0
|
|
28133
|
+
};
|
|
28134
|
+
if (isHttps && this.ignoreSsl) {
|
|
28135
|
+
options.rejectUnauthorized = false;
|
|
28136
|
+
}
|
|
28137
|
+
const req2 = protocolModule.request(options, (res) => {
|
|
28138
|
+
resolve20({ res });
|
|
28139
|
+
});
|
|
28140
|
+
req2.on("timeout", () => {
|
|
28141
|
+
req2.destroy(new ApiStreamError(`stream connect timeout after ${connectSec}s`, null));
|
|
28142
|
+
});
|
|
28143
|
+
req2.on("error", (err) => {
|
|
28144
|
+
reject(err);
|
|
28145
|
+
});
|
|
28146
|
+
if (data) req2.write(data);
|
|
28147
|
+
req2.end();
|
|
28148
|
+
});
|
|
28149
|
+
}
|
|
27924
28150
|
buildUrl(path8) {
|
|
27925
28151
|
if (path8.startsWith("http://") || path8.startsWith("https://")) {
|
|
27926
28152
|
return path8;
|
|
@@ -30583,10 +30809,11 @@ export default class User {
|
|
|
30583
30809
|
// ../core/src/aiClient.ts
|
|
30584
30810
|
import http2 from "node:http";
|
|
30585
30811
|
import https2 from "node:https";
|
|
30586
|
-
var AiError, AiConfigError, AiTimeoutError, AiParseError, AiHTTPError, Ai;
|
|
30812
|
+
var AiError, AiConfigError, AiTimeoutError, AiParseError, AiHTTPError, Ai, AggregateState;
|
|
30587
30813
|
var init_aiClient = __esm({
|
|
30588
30814
|
"../core/src/aiClient.ts"() {
|
|
30589
30815
|
"use strict";
|
|
30816
|
+
init_api();
|
|
30590
30817
|
AiError = class extends Error {
|
|
30591
30818
|
};
|
|
30592
30819
|
AiConfigError = class extends AiError {
|
|
@@ -30631,10 +30858,51 @@ var init_aiClient = __esm({
|
|
|
30631
30858
|
throw new AiParseError("AI provider returned a malformed embedding response");
|
|
30632
30859
|
}
|
|
30633
30860
|
}
|
|
30861
|
+
/**
|
|
30862
|
+
* Validate role + content shape. Content may be a string OR a non-empty
|
|
30863
|
+
* list of {type:'text'|'image', ...} parts (ADR-0060). Malformed parts
|
|
30864
|
+
* fail fast with AiConfigError, never reaching the wire.
|
|
30865
|
+
*/
|
|
30634
30866
|
static validateMessages(messages) {
|
|
30635
|
-
if (!Array.isArray(messages) || messages.length === 0
|
|
30867
|
+
if (!Array.isArray(messages) || messages.length === 0) {
|
|
30636
30868
|
throw new AiConfigError("AI messages must contain supported roles and string content");
|
|
30637
30869
|
}
|
|
30870
|
+
for (const message of messages) {
|
|
30871
|
+
if (!message || !["system", "user", "assistant"].includes(message.role)) {
|
|
30872
|
+
throw new AiConfigError("AI messages must contain supported roles and string content");
|
|
30873
|
+
}
|
|
30874
|
+
this.validateContent(message.content);
|
|
30875
|
+
}
|
|
30876
|
+
}
|
|
30877
|
+
static validateContent(content) {
|
|
30878
|
+
if (typeof content === "string") return;
|
|
30879
|
+
if (!Array.isArray(content) || content.length === 0) {
|
|
30880
|
+
throw new AiConfigError("AI message content must be a string or a non-empty list of parts");
|
|
30881
|
+
}
|
|
30882
|
+
for (const part of content) {
|
|
30883
|
+
if (!part || typeof part !== "object" || Array.isArray(part)) {
|
|
30884
|
+
throw new AiConfigError("AI content part must be an object with type and text/source");
|
|
30885
|
+
}
|
|
30886
|
+
const record = part;
|
|
30887
|
+
const partType = record.type;
|
|
30888
|
+
if (partType === "text") {
|
|
30889
|
+
if (typeof record.text !== "string") {
|
|
30890
|
+
throw new AiConfigError("AI text content part requires a string 'text' field");
|
|
30891
|
+
}
|
|
30892
|
+
} else if (partType === "image") {
|
|
30893
|
+
if (typeof record.source !== "string" || record.source.length === 0) {
|
|
30894
|
+
throw new AiConfigError("AI image content part requires a non-empty string 'source' field");
|
|
30895
|
+
}
|
|
30896
|
+
if (!record.source.startsWith("data:") && !record.source.startsWith("https://")) {
|
|
30897
|
+
throw new AiConfigError("AI image source must be a data: URI or an https:// URL");
|
|
30898
|
+
}
|
|
30899
|
+
if (record.source.startsWith("data:") && !/^data:[^;,\s]+;base64,[A-Za-z0-9+/=]+$/.test(record.source)) {
|
|
30900
|
+
throw new AiConfigError("AI image data URI must be data:<media_type>;base64,<payload>");
|
|
30901
|
+
}
|
|
30902
|
+
} else {
|
|
30903
|
+
throw new AiConfigError(`AI content part has unknown type '${String(partType)}'`);
|
|
30904
|
+
}
|
|
30905
|
+
}
|
|
30638
30906
|
}
|
|
30639
30907
|
static number(name, fallback, minimum) {
|
|
30640
30908
|
const value = process.env[name] === void 0 ? fallback : Number(process.env[name]);
|
|
@@ -30682,18 +30950,57 @@ var init_aiClient = __esm({
|
|
|
30682
30950
|
}
|
|
30683
30951
|
return headers;
|
|
30684
30952
|
}
|
|
30953
|
+
/**
|
|
30954
|
+
* Build the provider-specific request body from a Tina4-shaped message
|
|
30955
|
+
* list. Multimodal parts are translated per provider (ADR-0060):
|
|
30956
|
+
* - OpenAI/local: {type:'image_url', image_url:{url}}
|
|
30957
|
+
* - Anthropic: {type:'image', source:{type:'base64'|'url', ...}}
|
|
30958
|
+
* String content is preserved verbatim in the OpenAI/local shape and
|
|
30959
|
+
* likewise for Anthropic (both accept a bare string).
|
|
30960
|
+
*/
|
|
30685
30961
|
static chatBody(config, messages, options) {
|
|
30686
|
-
const
|
|
30962
|
+
const translate = (list) => list.map((message) => ({ role: message.role, content: this.translateContent(message.content, config.provider) }));
|
|
30963
|
+
const body = { model: config.model, messages: translate(messages), stream: options.stream ?? false };
|
|
30687
30964
|
if (options.temperature !== void 0) body.temperature = options.temperature;
|
|
30688
30965
|
if (options.maxTokens !== void 0) body.max_tokens = options.maxTokens;
|
|
30689
30966
|
if (config.provider === "anthropic") {
|
|
30690
|
-
const
|
|
30691
|
-
body.messages = messages.filter((message) => message.role !== "system");
|
|
30967
|
+
const systemParts = messages.filter((message) => message.role === "system").map((message) => typeof message.content === "string" ? message.content : this.contentToPlainText(message.content));
|
|
30968
|
+
body.messages = translate(messages.filter((message) => message.role !== "system"));
|
|
30692
30969
|
body.max_tokens = options.maxTokens ?? 1024;
|
|
30693
|
-
if (
|
|
30970
|
+
if (systemParts.length) body.system = systemParts.join("\n\n");
|
|
30694
30971
|
}
|
|
30695
30972
|
return body;
|
|
30696
30973
|
}
|
|
30974
|
+
/**
|
|
30975
|
+
* Translate one message content value into the provider's on-wire shape.
|
|
30976
|
+
* A plain string is passed through (both providers accept a string
|
|
30977
|
+
* content). A parts array becomes provider-native content blocks.
|
|
30978
|
+
*/
|
|
30979
|
+
static translateContent(content, provider) {
|
|
30980
|
+
if (typeof content === "string") return content;
|
|
30981
|
+
if (provider === "anthropic") {
|
|
30982
|
+
return content.map((part) => {
|
|
30983
|
+
if (part.type === "text") return { type: "text", text: part.text };
|
|
30984
|
+
if (part.source.startsWith("data:")) {
|
|
30985
|
+
const parsed = this.parseDataUri(part.source);
|
|
30986
|
+
return { type: "image", source: { type: "base64", media_type: parsed.mediaType, data: parsed.data } };
|
|
30987
|
+
}
|
|
30988
|
+
return { type: "image", source: { type: "url", url: part.source } };
|
|
30989
|
+
});
|
|
30990
|
+
}
|
|
30991
|
+
return content.map((part) => {
|
|
30992
|
+
if (part.type === "text") return { type: "text", text: part.text };
|
|
30993
|
+
return { type: "image_url", image_url: { url: part.source } };
|
|
30994
|
+
});
|
|
30995
|
+
}
|
|
30996
|
+
static parseDataUri(source) {
|
|
30997
|
+
const match = /^data:([^;,\s]+);base64,([A-Za-z0-9+/=]+)$/.exec(source);
|
|
30998
|
+
if (!match) throw new AiConfigError("AI image data URI must be data:<media_type>;base64,<payload>");
|
|
30999
|
+
return { mediaType: match[1], data: match[2] };
|
|
31000
|
+
}
|
|
31001
|
+
static contentToPlainText(parts) {
|
|
31002
|
+
return parts.filter((part) => part.type === "text").map((part) => part.text).join("\n\n");
|
|
31003
|
+
}
|
|
30697
31004
|
static open(config, deadline, headers, body) {
|
|
30698
31005
|
const remainingMs = deadline - performance.now();
|
|
30699
31006
|
if (remainingMs <= 0) return Promise.reject(new AiTimeoutError("AI total request timeout expired"));
|
|
@@ -30793,35 +31100,12 @@ var init_aiClient = __esm({
|
|
|
30793
31100
|
static async chatResponse(config, headers, body) {
|
|
30794
31101
|
return this.normalizeChat(config.provider, await this.requestJson(config, headers, body));
|
|
30795
31102
|
}
|
|
30796
|
-
|
|
30797
|
-
|
|
30798
|
-
|
|
30799
|
-
|
|
30800
|
-
|
|
30801
|
-
|
|
30802
|
-
throw new AiParseError("AI provider returned malformed stream data");
|
|
30803
|
-
}
|
|
30804
|
-
const text = provider === "anthropic" ? event.type === "content_block_delta" ? event.delta?.text : void 0 : event.choices?.[0]?.delta?.content;
|
|
30805
|
-
if (text !== void 0 && text !== null && typeof text !== "string") throw new AiParseError("AI provider returned malformed stream data");
|
|
30806
|
-
return { completed: false, text };
|
|
30807
|
-
}
|
|
30808
|
-
static async *streamData(response) {
|
|
30809
|
-
let buffer = "";
|
|
30810
|
-
for await (const chunk of response) {
|
|
30811
|
-
buffer += Buffer.from(chunk).toString("utf8");
|
|
30812
|
-
let newline;
|
|
30813
|
-
while ((newline = buffer.indexOf("\n")) >= 0) {
|
|
30814
|
-
const line = buffer.slice(0, newline).trim();
|
|
30815
|
-
buffer = buffer.slice(newline + 1);
|
|
30816
|
-
if (line.startsWith("data:")) yield line.slice(5).trim();
|
|
30817
|
-
}
|
|
30818
|
-
}
|
|
30819
|
-
}
|
|
30820
|
-
static streamError(error) {
|
|
30821
|
-
if (error instanceof AiError) return error;
|
|
30822
|
-
if (error instanceof Error && error.name === "AbortError") return new AiTimeoutError("AI total request timeout expired");
|
|
30823
|
-
return new AiHTTPError(`AI transport failed (${error instanceof Error ? error.name : "Error"})`);
|
|
30824
|
-
}
|
|
31103
|
+
/**
|
|
31104
|
+
* Stream the response through the shared {@link parseSseStream} framer
|
|
31105
|
+
* (ADR-0060 rule 5). Translates each SSE data payload into 0..N
|
|
31106
|
+
* {@link AiEvent}s: text_delta per chunk, tool_call aggregated per
|
|
31107
|
+
* index / block, exactly one done (or error) at the end.
|
|
31108
|
+
*/
|
|
30825
31109
|
static async *streamRequest(config, headers, body) {
|
|
30826
31110
|
const deadline = performance.now() + config.totalTimeout * 1e3;
|
|
30827
31111
|
let yielded = false;
|
|
@@ -30840,27 +31124,239 @@ var init_aiClient = __esm({
|
|
|
30840
31124
|
}
|
|
30841
31125
|
throw new AiHTTPError(`AI provider returned HTTP ${status2}`, status2);
|
|
30842
31126
|
}
|
|
30843
|
-
|
|
30844
|
-
|
|
30845
|
-
|
|
30846
|
-
|
|
30847
|
-
|
|
30848
|
-
|
|
31127
|
+
const response = opened.response;
|
|
31128
|
+
const chunks = this.responseChunks(response);
|
|
31129
|
+
const events = parseSseStream(parseLineStream(chunks));
|
|
31130
|
+
const aggregator = new AggregateState(config.provider);
|
|
31131
|
+
let done = false;
|
|
31132
|
+
try {
|
|
31133
|
+
for await (const sseEvent of events) {
|
|
31134
|
+
for (const emitted of aggregator.consume(sseEvent)) {
|
|
31135
|
+
yielded = true;
|
|
31136
|
+
yield emitted;
|
|
31137
|
+
if (emitted.type === "done" || emitted.type === "error") {
|
|
31138
|
+
done = true;
|
|
31139
|
+
break;
|
|
31140
|
+
}
|
|
31141
|
+
}
|
|
31142
|
+
if (done) break;
|
|
31143
|
+
}
|
|
31144
|
+
} catch (error) {
|
|
31145
|
+
if (yielded) {
|
|
31146
|
+
yielded = true;
|
|
31147
|
+
yield { type: "error", message: error instanceof AiParseError ? "AI provider returned malformed stream data" : `AI transport failed (${error instanceof Error ? error.name : "Error"})` };
|
|
31148
|
+
opened.cleanup();
|
|
31149
|
+
opened = null;
|
|
31150
|
+
return;
|
|
30849
31151
|
}
|
|
30850
|
-
|
|
30851
|
-
yielded = true;
|
|
30852
|
-
yield delta.text;
|
|
31152
|
+
throw error;
|
|
30853
31153
|
}
|
|
30854
31154
|
opened.cleanup();
|
|
30855
31155
|
opened = null;
|
|
30856
|
-
if (
|
|
30857
|
-
|
|
31156
|
+
if (done) return;
|
|
31157
|
+
if (yielded) {
|
|
31158
|
+
yield { type: "error", message: "AI provider stream ended before completion" };
|
|
31159
|
+
return;
|
|
31160
|
+
}
|
|
31161
|
+
throw new AiParseError("AI provider stream ended before completion");
|
|
30858
31162
|
} catch (error) {
|
|
30859
31163
|
opened?.cleanup();
|
|
30860
31164
|
const failure = this.streamError(error);
|
|
30861
|
-
if (
|
|
31165
|
+
if (yielded) {
|
|
31166
|
+
yield { type: "error", message: failure.message };
|
|
31167
|
+
return;
|
|
31168
|
+
}
|
|
31169
|
+
if (failure instanceof AiParseError || failure instanceof AiHTTPError && failure.status !== null || attempt >= config.maxRetries) throw failure;
|
|
31170
|
+
}
|
|
31171
|
+
}
|
|
31172
|
+
}
|
|
31173
|
+
static async *responseChunks(response) {
|
|
31174
|
+
for await (const chunk of response) {
|
|
31175
|
+
yield chunk;
|
|
31176
|
+
}
|
|
31177
|
+
}
|
|
31178
|
+
static streamError(error) {
|
|
31179
|
+
if (error instanceof AiError) return error;
|
|
31180
|
+
if (error instanceof Error && error.name === "AbortError") return new AiTimeoutError("AI total request timeout expired");
|
|
31181
|
+
return new AiHTTPError(`AI transport failed (${error instanceof Error ? error.name : "Error"})`);
|
|
31182
|
+
}
|
|
31183
|
+
};
|
|
31184
|
+
AggregateState = class {
|
|
31185
|
+
constructor(provider) {
|
|
31186
|
+
this.provider = provider;
|
|
31187
|
+
}
|
|
31188
|
+
toolBuffers = /* @__PURE__ */ new Map();
|
|
31189
|
+
lastFinishReason = null;
|
|
31190
|
+
lastUsage = null;
|
|
31191
|
+
doneEmitted = false;
|
|
31192
|
+
*consume(event) {
|
|
31193
|
+
const data = event.data;
|
|
31194
|
+
if (data === "[DONE]") {
|
|
31195
|
+
if (this.doneEmitted) return;
|
|
31196
|
+
yield* this.flushRemainingToolCalls();
|
|
31197
|
+
this.doneEmitted = true;
|
|
31198
|
+
yield {
|
|
31199
|
+
type: "done",
|
|
31200
|
+
finishReason: this.lastFinishReason ?? "stop",
|
|
31201
|
+
...this.lastUsage ? { usage: this.lastUsage } : {}
|
|
31202
|
+
};
|
|
31203
|
+
return;
|
|
31204
|
+
}
|
|
31205
|
+
let payload;
|
|
31206
|
+
try {
|
|
31207
|
+
payload = JSON.parse(data);
|
|
31208
|
+
} catch {
|
|
31209
|
+
throw new AiParseError("AI provider returned malformed stream data");
|
|
31210
|
+
}
|
|
31211
|
+
if (this.provider === "anthropic") {
|
|
31212
|
+
yield* this.consumeAnthropic(payload);
|
|
31213
|
+
} else {
|
|
31214
|
+
yield* this.consumeOpenAi(payload);
|
|
31215
|
+
}
|
|
31216
|
+
}
|
|
31217
|
+
*consumeOpenAi(payload) {
|
|
31218
|
+
const choices = payload.choices;
|
|
31219
|
+
if (!Array.isArray(choices) || choices.length === 0) return;
|
|
31220
|
+
const choice = choices[0];
|
|
31221
|
+
const delta = choice.delta ?? {};
|
|
31222
|
+
const content = delta.content;
|
|
31223
|
+
if (typeof content === "string" && content.length > 0) {
|
|
31224
|
+
yield { type: "text_delta", text: content };
|
|
31225
|
+
}
|
|
31226
|
+
const toolCalls = delta.tool_calls;
|
|
31227
|
+
if (Array.isArray(toolCalls)) {
|
|
31228
|
+
for (const call of toolCalls) {
|
|
31229
|
+
const index = typeof call.index === "number" ? String(call.index) : String(this.toolBuffers.size);
|
|
31230
|
+
const idFromCall = typeof call.id === "string" ? call.id : "";
|
|
31231
|
+
const fn = call.function ?? {};
|
|
31232
|
+
const nameFromCall = typeof fn.name === "string" ? fn.name : "";
|
|
31233
|
+
const argsFragment = typeof fn.arguments === "string" ? fn.arguments : "";
|
|
31234
|
+
const existing = this.toolBuffers.get(index) ?? { id: "", name: "", args: "" };
|
|
31235
|
+
if (idFromCall) existing.id = idFromCall;
|
|
31236
|
+
if (nameFromCall) existing.name = nameFromCall;
|
|
31237
|
+
existing.args += argsFragment;
|
|
31238
|
+
this.toolBuffers.set(index, existing);
|
|
31239
|
+
if (existing.name && existing.args) {
|
|
31240
|
+
try {
|
|
31241
|
+
const parsed = JSON.parse(existing.args);
|
|
31242
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
31243
|
+
this.toolBuffers.delete(index);
|
|
31244
|
+
yield { type: "tool_call", id: existing.id || `call_${index}`, name: existing.name, args: parsed };
|
|
31245
|
+
}
|
|
31246
|
+
} catch {
|
|
31247
|
+
}
|
|
31248
|
+
}
|
|
31249
|
+
}
|
|
31250
|
+
}
|
|
31251
|
+
if (typeof choice.finish_reason === "string" && choice.finish_reason.length > 0) {
|
|
31252
|
+
this.lastFinishReason = choice.finish_reason;
|
|
31253
|
+
}
|
|
31254
|
+
const usage = payload.usage;
|
|
31255
|
+
if (usage && typeof usage === "object") {
|
|
31256
|
+
const promptTokens = Number(usage.prompt_tokens ?? 0);
|
|
31257
|
+
const completionTokens = Number(usage.completion_tokens ?? 0);
|
|
31258
|
+
const totalTokens = Number(usage.total_tokens ?? promptTokens + completionTokens);
|
|
31259
|
+
if (Number.isFinite(promptTokens) && Number.isFinite(completionTokens)) {
|
|
31260
|
+
this.lastUsage = { promptTokens, completionTokens, totalTokens };
|
|
31261
|
+
}
|
|
31262
|
+
}
|
|
31263
|
+
}
|
|
31264
|
+
*consumeAnthropic(payload) {
|
|
31265
|
+
const type2 = payload.type;
|
|
31266
|
+
if (type2 === "content_block_start") {
|
|
31267
|
+
const block = payload.content_block ?? {};
|
|
31268
|
+
if (block.type === "tool_use") {
|
|
31269
|
+
const index = String(payload.index ?? this.toolBuffers.size);
|
|
31270
|
+
const id = typeof block.id === "string" ? block.id : `call_${index}`;
|
|
31271
|
+
const name = typeof block.name === "string" ? block.name : "";
|
|
31272
|
+
this.toolBuffers.set(index, { id, name, args: "" });
|
|
31273
|
+
}
|
|
31274
|
+
return;
|
|
31275
|
+
}
|
|
31276
|
+
if (type2 === "content_block_delta") {
|
|
31277
|
+
const index = String(payload.index ?? 0);
|
|
31278
|
+
const delta = payload.delta ?? {};
|
|
31279
|
+
if (delta.type === "text_delta" && typeof delta.text === "string" && delta.text.length > 0) {
|
|
31280
|
+
yield { type: "text_delta", text: delta.text };
|
|
31281
|
+
return;
|
|
31282
|
+
}
|
|
31283
|
+
if (delta.type === "input_json_delta" && typeof delta.partial_json === "string") {
|
|
31284
|
+
const existing = this.toolBuffers.get(index);
|
|
31285
|
+
if (existing) existing.args += delta.partial_json;
|
|
31286
|
+
}
|
|
31287
|
+
return;
|
|
31288
|
+
}
|
|
31289
|
+
if (type2 === "content_block_stop") {
|
|
31290
|
+
const index = String(payload.index ?? 0);
|
|
31291
|
+
const existing = this.toolBuffers.get(index);
|
|
31292
|
+
if (existing && existing.name) {
|
|
31293
|
+
this.toolBuffers.delete(index);
|
|
31294
|
+
try {
|
|
31295
|
+
const parsed = existing.args ? JSON.parse(existing.args) : {};
|
|
31296
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
31297
|
+
yield { type: "tool_call", id: existing.id, name: existing.name, args: parsed };
|
|
31298
|
+
return;
|
|
31299
|
+
}
|
|
31300
|
+
throw new Error();
|
|
31301
|
+
} catch {
|
|
31302
|
+
throw new AiParseError("AI provider returned malformed tool-call JSON");
|
|
31303
|
+
}
|
|
31304
|
+
}
|
|
31305
|
+
return;
|
|
31306
|
+
}
|
|
31307
|
+
if (type2 === "message_delta") {
|
|
31308
|
+
const delta = payload.delta ?? {};
|
|
31309
|
+
if (typeof delta.stop_reason === "string" && delta.stop_reason.length > 0) {
|
|
31310
|
+
this.lastFinishReason = delta.stop_reason;
|
|
31311
|
+
}
|
|
31312
|
+
const usage = payload.usage ?? {};
|
|
31313
|
+
if (usage.output_tokens !== void 0 || usage.input_tokens !== void 0) {
|
|
31314
|
+
const promptTokens = Number(usage.input_tokens ?? this.lastUsage?.promptTokens ?? 0);
|
|
31315
|
+
const completionTokens = Number(usage.output_tokens ?? this.lastUsage?.completionTokens ?? 0);
|
|
31316
|
+
this.lastUsage = { promptTokens, completionTokens, totalTokens: promptTokens + completionTokens };
|
|
31317
|
+
}
|
|
31318
|
+
return;
|
|
31319
|
+
}
|
|
31320
|
+
if (type2 === "message_stop") {
|
|
31321
|
+
if (this.doneEmitted) return;
|
|
31322
|
+
this.doneEmitted = true;
|
|
31323
|
+
yield {
|
|
31324
|
+
type: "done",
|
|
31325
|
+
finishReason: this.lastFinishReason ?? "end_turn",
|
|
31326
|
+
...this.lastUsage ? { usage: this.lastUsage } : {}
|
|
31327
|
+
};
|
|
31328
|
+
return;
|
|
31329
|
+
}
|
|
31330
|
+
if (type2 === "message_start") {
|
|
31331
|
+
const message = payload.message ?? {};
|
|
31332
|
+
const usage = message.usage ?? {};
|
|
31333
|
+
if (usage.input_tokens !== void 0 || usage.output_tokens !== void 0) {
|
|
31334
|
+
const promptTokens = Number(usage.input_tokens ?? 0);
|
|
31335
|
+
const completionTokens = Number(usage.output_tokens ?? 0);
|
|
31336
|
+
this.lastUsage = { promptTokens, completionTokens, totalTokens: promptTokens + completionTokens };
|
|
31337
|
+
}
|
|
31338
|
+
return;
|
|
31339
|
+
}
|
|
31340
|
+
if (type2 === "error") {
|
|
31341
|
+
const err = payload.error ?? {};
|
|
31342
|
+
throw new AiParseError(typeof err.message === "string" ? err.message : "AI provider signalled a stream error");
|
|
31343
|
+
}
|
|
31344
|
+
}
|
|
31345
|
+
*flushRemainingToolCalls() {
|
|
31346
|
+
for (const [index, buffered] of this.toolBuffers) {
|
|
31347
|
+
if (buffered.name && buffered.args) {
|
|
31348
|
+
try {
|
|
31349
|
+
const parsed = JSON.parse(buffered.args);
|
|
31350
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
31351
|
+
yield { type: "tool_call", id: buffered.id || `call_${index}`, name: buffered.name, args: parsed };
|
|
31352
|
+
continue;
|
|
31353
|
+
}
|
|
31354
|
+
} catch {
|
|
31355
|
+
}
|
|
31356
|
+
throw new AiParseError("AI provider returned malformed tool-call JSON");
|
|
30862
31357
|
}
|
|
30863
31358
|
}
|
|
31359
|
+
this.toolBuffers.clear();
|
|
30864
31360
|
}
|
|
30865
31361
|
};
|
|
30866
31362
|
}
|
|
@@ -32576,6 +33072,7 @@ __export(src_exports2, {
|
|
|
32576
33072
|
AiParseError: () => AiParseError,
|
|
32577
33073
|
AiTimeoutError: () => AiTimeoutError,
|
|
32578
33074
|
Api: () => Api,
|
|
33075
|
+
ApiStreamError: () => ApiStreamError,
|
|
32579
33076
|
Auth: () => Auth,
|
|
32580
33077
|
CANONICAL_SESSION_BACKENDS: () => CANONICAL_SESSION_BACKENDS,
|
|
32581
33078
|
CLOSE_GOING_AWAY: () => CLOSE_GOING_AWAY,
|
|
@@ -32805,7 +33302,9 @@ __export(src_exports2, {
|
|
|
32805
33302
|
originAllowed: () => originAllowed,
|
|
32806
33303
|
parseAmqpUrl: () => parseAmqpUrl,
|
|
32807
33304
|
parseFrame: () => parseFrame,
|
|
33305
|
+
parseLineStream: () => parseLineStream,
|
|
32808
33306
|
parseMultipart: () => parseMultipart,
|
|
33307
|
+
parseSseStream: () => parseSseStream,
|
|
32809
33308
|
parseUpgradeHeaders: () => parseUpgradeHeaders,
|
|
32810
33309
|
patch: () => patch,
|
|
32811
33310
|
pidfilePath: () => pidfilePath,
|