This repository has been archived on 2026-04-03. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
core/src/js/sw/sdk/pipe.js
Jeffrey Smith c470037fb5
Some checks failed
CI/CD / detect-changes (push) Successful in 23s
CI/CD / test-sqlite (push) Failing after 20s
CI/CD / test-frontend (push) Failing after 21s
CI/CD / test-go-pg (push) Failing after 38s
CI/CD / build-and-deploy (push) Has been skipped
rebrand + purge orphaned frontend: Chat Switchboard → Switchboard Core
Branding: bulk rename across 50+ files (Go, JS, CSS, HTML, YAML, JSON,
shell scripts). Fix Helm chart description and git repo URLs.
Fix test helper taglines.

Admin surface: strip 14 gutted tabs (providers, models, personas,
roles, knowledge, memory, tasks, health, routing, capabilities,
channels, dashboard, usage, stats). Collapse to 4 categories:
People, Workflows, System, Monitoring.

Settings surface: strip 11 gutted tabs (models, personas, providers,
roles, knowledge, memory, usage, workflows, tasks, data, gitkeys).
Keep: general, appearance, profile, teams, connections, notifications.

Team-admin surface: strip 5 gutted tabs (personas, providers,
knowledge, tasks, usage). Keep: members, groups, connections,
workflows, settings, activity.

Components: delete chat-pane/ (13 files, 1938 lines) and
notes-pane/ (10 files, 2381 lines).

main.go: remove stale Health.Prune maintenance goroutine.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 10:28:09 +00:00

138 lines
4.5 KiB
JavaScript

// ==========================================
// Switchboard Core — SDK: Pipe/Filter Pipeline
// ==========================================
// Three-stage pipeline: pre-send, stream, render.
// Verbatim port of switchboard-sdk.js pipe code.
//
// Factory: createPipe()
// ==========================================
/**
* Create the pipe/filter pipeline.
* @returns {object} pipe
*/
export function createPipe() {
const _chains = {
pre: [], // { priority, fn, scope, source, stats }
stream: [],
render: [],
};
/**
* Register a filter into a pipe stage.
*/
function _registerFilter(stage, priority, fn, opts) {
if (typeof fn !== 'function') {
console.error(`[sw.pipe] ${stage}: filter must be a function`);
return;
}
const chain = _chains[stage];
if (!chain) {
console.error(`[sw.pipe] ${stage}: unknown stage`);
return;
}
const _opts = opts || {};
const entry = {
priority,
fn,
scope: _opts.scope || null,
source: _opts.source || _inferSource(),
stats: { calls: 0, totalMs: 0, errors: 0 },
};
const dup = chain.find(e => e.source === entry.source && e.priority === entry.priority);
if (dup) {
console.warn(`[sw.pipe] ${stage}: duplicate registration (source=${entry.source}, priority=${priority})`);
}
chain.push(entry);
chain.sort((a, b) => a.priority - b.priority);
}
/**
* Infer source label from call stack.
*/
function _inferSource() {
try {
const stack = new Error().stack || '';
const extMatch = stack.match(/extensions\/([^/]+)\//);
if (extMatch) return extMatch[1];
} catch (_) { /* best effort */ }
return 'anonymous';
}
/**
* Run a filter chain against a context.
* Sync-only — filters must not return Promises.
*/
function _runChain(stage, ctx) {
const chain = _chains[stage];
if (!chain || chain.length === 0) return ctx;
const channelType = ctx.channel?.type || null;
for (const entry of chain) {
if (entry.scope?.channelType) {
if (!channelType || !entry.scope.channelType.includes(channelType)) {
continue;
}
}
const t0 = performance.now();
try {
const result = entry.fn(ctx);
entry.stats.calls++;
entry.stats.totalMs += performance.now() - t0;
if (result instanceof Promise) {
console.error(`[sw.pipe] ${stage}: filter '${entry.source}' returned Promise (async not supported)`);
entry.stats.errors++;
continue;
}
if (result === null || result === undefined) {
return null; // halt chain
}
ctx = result;
} catch (e) {
entry.stats.calls++;
entry.stats.errors++;
entry.stats.totalMs += performance.now() - t0;
console.error(`[sw.pipe] ${stage} filter '${entry.source}' (p=${entry.priority}) threw:`, e);
// Continue chain — error isolation
}
}
return ctx;
}
// ── Public API ──────────────────────────────
return {
pre(priority, fn, opts) { _registerFilter('pre', priority, fn, opts); },
stream(priority, fn, opts) { _registerFilter('stream', priority, fn, opts); },
render(priority, fn, opts) { _registerFilter('render', priority, fn, opts); },
list() {
const result = {};
for (const [stage, chain] of Object.entries(_chains)) {
result[stage] = chain.map(e => ({
priority: e.priority,
source: e.source,
scope: e.scope,
calls: e.stats.calls,
avgMs: e.stats.calls > 0
? Math.round((e.stats.totalMs / e.stats.calls) * 100) / 100
: 0,
errors: e.stats.errors,
}));
}
return result;
},
_runPre(ctx) { return _runChain('pre', ctx); },
_runStream(ctx) { return _runChain('stream', ctx); },
_runRender(ctx) { return _runChain('render', ctx); },
};
}