@xopcai/xopc 0.0.27 → 0.0.29
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/dist/extensions/telegram/xopc.extension.json +1 -1
- package/dist/extensions/weixin/src/adapters/onboard-cli.d.ts +7 -0
- package/dist/extensions/weixin/src/adapters/onboard-cli.js +61 -0
- package/dist/extensions/weixin/src/adapters/onboard-cli.js.map +1 -0
- package/dist/extensions/weixin/src/cli/qr-login.d.ts +5 -0
- package/dist/extensions/weixin/src/cli/qr-login.js +1 -1
- package/dist/extensions/weixin/src/cli/qr-login.js.map +1 -1
- package/dist/extensions/weixin/src/index.js +1 -1
- package/dist/extensions/weixin/src/plugin.d.ts +1 -0
- package/dist/extensions/weixin/src/plugin.js +2 -0
- package/dist/extensions/weixin/src/plugin.js.map +1 -1
- package/dist/gateway/static/root/assets/agents-CkgFSiCY.js +216 -0
- package/dist/gateway/static/root/assets/agents-CkgFSiCY.js.map +1 -0
- package/dist/gateway/static/root/assets/{apps-page-CBBh_Ww8.js → apps-page-Bmq19MS-.js} +2 -2
- package/dist/gateway/static/root/assets/{apps-page-CBBh_Ww8.js.map → apps-page-Bmq19MS-.js.map} +1 -1
- package/dist/gateway/static/root/assets/channels-settings-CE7jrdkO.js +9 -0
- package/dist/gateway/static/root/assets/channels-settings-CE7jrdkO.js.map +1 -0
- package/dist/gateway/static/root/assets/cron-page-BpPPcykJ.js +2 -0
- package/dist/gateway/static/root/assets/cron-page-BpPPcykJ.js.map +1 -0
- package/dist/gateway/static/root/assets/{cron-utils-08gdQfl9.js → cron-utils-N1PqD2DB.js} +2 -2
- package/dist/gateway/static/root/assets/{cron-utils-08gdQfl9.js.map → cron-utils-N1PqD2DB.js.map} +1 -1
- package/dist/gateway/static/root/assets/{dist-C1MrygQH.js → dist--p2HQ2QF.js} +2 -2
- package/dist/gateway/static/root/assets/{dist-C1MrygQH.js.map → dist--p2HQ2QF.js.map} +1 -1
- package/dist/gateway/static/root/assets/{extension-debug-page-DN3HKUGS.js → extension-debug-page-DwHCB_6T.js} +2 -2
- package/dist/gateway/static/root/assets/{extension-debug-page-DN3HKUGS.js.map → extension-debug-page-DwHCB_6T.js.map} +1 -1
- package/dist/gateway/static/root/assets/{extension-page-CoFDHZtZ.js → extension-page-BsYwQIex.js} +2 -2
- package/dist/gateway/static/root/assets/{extension-page-CoFDHZtZ.js.map → extension-page-BsYwQIex.js.map} +1 -1
- package/dist/gateway/static/root/assets/{extension-settings-page-BcPCu_Go.js → extension-settings-page-nsisEgjB.js} +2 -2
- package/dist/gateway/static/root/assets/{extension-settings-page-BcPCu_Go.js.map → extension-settings-page-nsisEgjB.js.map} +1 -1
- package/dist/gateway/static/root/assets/index-CR8zUHGR.js +4734 -0
- package/dist/gateway/static/root/assets/{index-PfkB8N37.js.map → index-CR8zUHGR.js.map} +1 -1
- package/dist/gateway/static/root/assets/index-Dnfha4O2.css +1 -0
- package/dist/gateway/static/root/assets/logs-page-CQwdV_Xw.js +2 -0
- package/dist/gateway/static/root/assets/logs-page-CQwdV_Xw.js.map +1 -0
- package/dist/gateway/static/root/assets/sessions-page-Be5kIGl_.js +2 -0
- package/dist/gateway/static/root/assets/sessions-page-Be5kIGl_.js.map +1 -0
- package/dist/gateway/static/root/assets/settings-page-PodSlNwr.js +2 -0
- package/dist/gateway/static/root/assets/settings-page-PodSlNwr.js.map +1 -0
- package/dist/gateway/static/root/assets/skills-page-Clg8deH0.js +3 -0
- package/dist/gateway/static/root/assets/{skills-page-BmBDCEbY.js.map → skills-page-Clg8deH0.js.map} +1 -1
- package/dist/gateway/static/root/index.html +2 -2
- package/dist/package.js +1 -1
- package/dist/src/agent/lifecycle/hook-handler.d.ts +2 -0
- package/dist/src/agent/lifecycle/hook-handler.js +24 -0
- package/dist/src/agent/lifecycle/hook-handler.js.map +1 -1
- package/dist/src/agent/messaging/command-handler.js +10 -2
- package/dist/src/agent/messaging/command-handler.js.map +1 -1
- package/dist/src/agent/service/process-direct-streaming.js +77 -20
- package/dist/src/agent/service/process-direct-streaming.js.map +1 -1
- package/dist/src/agent/service.d.ts +15 -0
- package/dist/src/agent/service.js +21 -1
- package/dist/src/agent/service.js.map +1 -1
- package/dist/src/channels/weixin/index.js +1 -1
- package/dist/src/cli/agent-chat-log-level-preset.d.ts +8 -0
- package/dist/src/cli/agent-chat-log-level-preset.js +25 -0
- package/dist/src/cli/agent-chat-log-level-preset.js.map +1 -0
- package/dist/src/cli/commands/agent/interactive.js +4 -2
- package/dist/src/cli/commands/agent/interactive.js.map +1 -1
- package/dist/src/cli/commands/agent/stream-renderer.d.ts +14 -0
- package/dist/src/cli/commands/agent/stream-renderer.js +99 -0
- package/dist/src/cli/commands/agent/stream-renderer.js.map +1 -0
- package/dist/src/cli/commands/agent.js +2 -2
- package/dist/src/cli/commands/agent.js.map +1 -1
- package/dist/src/cli/commands/onboard.js +77 -93
- package/dist/src/cli/commands/onboard.js.map +1 -1
- package/dist/src/cli/commands/tui.d.ts +1 -0
- package/dist/src/cli/commands/tui.js +40 -0
- package/dist/src/cli/commands/tui.js.map +1 -0
- package/dist/src/cli/index.d.ts +2 -0
- package/dist/src/cli/index.js +7 -3
- package/dist/src/cli/index.js.map +1 -1
- package/dist/src/config/schema.d.ts +6 -0
- package/dist/src/config/schema.js +11 -3
- package/dist/src/config/schema.js.map +1 -1
- package/dist/src/extensions/hooks.js +5 -1
- package/dist/src/extensions/hooks.js.map +1 -1
- package/dist/src/extensions/loader.d.ts +1 -0
- package/dist/src/extensions/loader.js +3 -1
- package/dist/src/extensions/loader.js.map +1 -1
- package/dist/src/extensions/sdk/index.d.ts +1 -1
- package/dist/src/extensions/sdk/index.js.map +1 -1
- package/dist/src/extensions/types/core.d.ts +8 -0
- package/dist/src/extensions/types/hooks.d.ts +16 -1
- package/dist/src/extensions/types/hooks.js +1 -0
- package/dist/src/extensions/types/hooks.js.map +1 -1
- package/dist/src/gateway/agents-admin.d.ts +19 -1
- package/dist/src/gateway/agents-admin.js +164 -3
- package/dist/src/gateway/agents-admin.js.map +1 -1
- package/dist/src/gateway/auth.d.ts +17 -3
- package/dist/src/gateway/auth.js +35 -16
- package/dist/src/gateway/auth.js.map +1 -1
- package/dist/src/gateway/hono/app.js +31 -1
- package/dist/src/gateway/hono/app.js.map +1 -1
- package/dist/src/gateway/hono/lib/config-payload.d.ts +1 -1
- package/dist/src/gateway/hono/middleware/auth.js +4 -3
- package/dist/src/gateway/hono/middleware/auth.js.map +1 -1
- package/dist/src/gateway/hono/middleware/scopes.d.ts +15 -0
- package/dist/src/gateway/hono/middleware/scopes.js +41 -0
- package/dist/src/gateway/hono/middleware/scopes.js.map +1 -0
- package/dist/src/gateway/hono/routes/agents.js +59 -5
- package/dist/src/gateway/hono/routes/agents.js.map +1 -1
- package/dist/src/gateway/hono/routes/config.js +2 -2
- package/dist/src/gateway/hono/routes/config.js.map +1 -1
- package/dist/src/gateway/hono/routes/public-gateway.js +1 -0
- package/dist/src/gateway/hono/routes/public-gateway.js.map +1 -1
- package/dist/src/gateway/hono/routes/sessions.js +17 -0
- package/dist/src/gateway/hono/routes/sessions.js.map +1 -1
- package/dist/src/gateway/security/audit.d.ts +18 -0
- package/dist/src/gateway/security/audit.js +68 -0
- package/dist/src/gateway/security/audit.js.map +1 -0
- package/dist/src/gateway/security/csp.d.ts +19 -0
- package/dist/src/gateway/security/csp.js +52 -0
- package/dist/src/gateway/security/csp.js.map +1 -0
- package/dist/src/gateway/security/dangerous-tools.d.ts +20 -0
- package/dist/src/gateway/security/dangerous-tools.js +46 -0
- package/dist/src/gateway/security/dangerous-tools.js.map +1 -0
- package/dist/src/gateway/security/flood-guard.d.ts +28 -0
- package/dist/src/gateway/security/flood-guard.js +42 -0
- package/dist/src/gateway/security/flood-guard.js.map +1 -0
- package/dist/src/gateway/security/index.d.ts +9 -0
- package/dist/src/gateway/security/index.js +10 -0
- package/dist/src/gateway/security/known-weak-secrets.d.ts +10 -0
- package/dist/src/gateway/security/known-weak-secrets.js +36 -0
- package/dist/src/gateway/security/known-weak-secrets.js.map +1 -0
- package/dist/src/gateway/security/operator-scopes.d.ts +37 -0
- package/dist/src/gateway/security/operator-scopes.js +137 -0
- package/dist/src/gateway/security/operator-scopes.js.map +1 -0
- package/dist/src/gateway/security/origin-check.d.ts +21 -0
- package/dist/src/gateway/security/origin-check.js +56 -0
- package/dist/src/gateway/security/origin-check.js.map +1 -0
- package/dist/src/gateway/security/preauth-connection-budget.d.ts +17 -0
- package/dist/src/gateway/security/preauth-connection-budget.js +49 -0
- package/dist/src/gateway/security/preauth-connection-budget.js.map +1 -0
- package/dist/src/gateway/security/secret-equal.d.ts +8 -0
- package/dist/src/gateway/security/secret-equal.js +30 -0
- package/dist/src/gateway/security/secret-equal.js.map +1 -0
- package/dist/src/gateway/service.d.ts +3 -1
- package/dist/src/gateway/service.js +40 -4
- package/dist/src/gateway/service.js.map +1 -1
- package/dist/src/session/client-history.d.ts +21 -0
- package/dist/src/session/client-history.js +89 -0
- package/dist/src/session/client-history.js.map +1 -0
- package/dist/src/session/index.d.ts +1 -0
- package/dist/src/session/index.js +2 -1
- package/dist/src/session/manager.d.ts +2 -0
- package/dist/src/session/manager.js +5 -0
- package/dist/src/session/manager.js.map +1 -1
- package/dist/src/session/thinking-resolve.js +1 -1
- package/dist/src/session/thinking-resolve.js.map +1 -1
- package/dist/src/tui/backends/embedded-backend.d.ts +42 -0
- package/dist/src/tui/backends/embedded-backend.js +173 -0
- package/dist/src/tui/backends/embedded-backend.js.map +1 -0
- package/dist/src/tui/backends/gateway-sse-backend.d.ts +53 -0
- package/dist/src/tui/backends/gateway-sse-backend.js +256 -0
- package/dist/src/tui/backends/gateway-sse-backend.js.map +1 -0
- package/dist/src/tui/chat-history.d.ts +4 -0
- package/dist/src/tui/chat-history.js +29 -0
- package/dist/src/tui/chat-history.js.map +1 -0
- package/dist/src/tui/components/assistant-message.d.ts +6 -0
- package/dist/src/tui/components/assistant-message.js +19 -0
- package/dist/src/tui/components/assistant-message.js.map +1 -0
- package/dist/src/tui/components/chat-log.d.ts +21 -0
- package/dist/src/tui/components/chat-log.js +113 -0
- package/dist/src/tui/components/chat-log.js.map +1 -0
- package/dist/src/tui/components/custom-editor.d.ts +14 -0
- package/dist/src/tui/components/custom-editor.js +50 -0
- package/dist/src/tui/components/custom-editor.js.map +1 -0
- package/dist/src/tui/components/fuzzy-filter.d.ts +17 -0
- package/dist/src/tui/components/fuzzy-filter.js +85 -0
- package/dist/src/tui/components/fuzzy-filter.js.map +1 -0
- package/dist/src/tui/components/searchable-select-list.d.ts +39 -0
- package/dist/src/tui/components/searchable-select-list.js +257 -0
- package/dist/src/tui/components/searchable-select-list.js.map +1 -0
- package/dist/src/tui/components/tool-execution.d.ts +16 -0
- package/dist/src/tui/components/tool-execution.js +76 -0
- package/dist/src/tui/components/tool-execution.js.map +1 -0
- package/dist/src/tui/components/user-message.d.ts +6 -0
- package/dist/src/tui/components/user-message.js +22 -0
- package/dist/src/tui/components/user-message.js.map +1 -0
- package/dist/src/tui/sse-consumer.d.ts +15 -0
- package/dist/src/tui/sse-consumer.js +75 -0
- package/dist/src/tui/sse-consumer.js.map +1 -0
- package/dist/src/tui/stream-assembler.d.ts +22 -0
- package/dist/src/tui/stream-assembler.js +63 -0
- package/dist/src/tui/stream-assembler.js.map +1 -0
- package/dist/src/tui/theme.d.ts +73 -0
- package/dist/src/tui/theme.js +157 -0
- package/dist/src/tui/theme.js.map +1 -0
- package/dist/src/tui/tui-agent-events.d.ts +7 -0
- package/dist/src/tui/tui-agent-events.js +103 -0
- package/dist/src/tui/tui-agent-events.js.map +1 -0
- package/dist/src/tui/tui-backend.d.ts +80 -0
- package/dist/src/tui/tui-backend.js +1 -0
- package/dist/src/tui/tui-commands.d.ts +23 -0
- package/dist/src/tui/tui-commands.js +165 -0
- package/dist/src/tui/tui-commands.js.map +1 -0
- package/dist/src/tui/tui-lifecycle.d.ts +26 -0
- package/dist/src/tui/tui-lifecycle.js +57 -0
- package/dist/src/tui/tui-lifecycle.js.map +1 -0
- package/dist/src/tui/tui-local-shell.d.ts +28 -0
- package/dist/src/tui/tui-local-shell.js +147 -0
- package/dist/src/tui/tui-local-shell.js.map +1 -0
- package/dist/src/tui/tui-overlays.d.ts +8 -0
- package/dist/src/tui/tui-overlays.js +22 -0
- package/dist/src/tui/tui-overlays.js.map +1 -0
- package/dist/src/tui/tui-picker-overlay.d.ts +26 -0
- package/dist/src/tui/tui-picker-overlay.js +69 -0
- package/dist/src/tui/tui-picker-overlay.js.map +1 -0
- package/dist/src/tui/tui-stdio-filter.d.ts +17 -0
- package/dist/src/tui/tui-stdio-filter.js +96 -0
- package/dist/src/tui/tui-stdio-filter.js.map +1 -0
- package/dist/src/tui/tui-submit.d.ts +25 -0
- package/dist/src/tui/tui-submit.js +102 -0
- package/dist/src/tui/tui-submit.js.map +1 -0
- package/dist/src/tui/tui-suspend.d.ts +10 -0
- package/dist/src/tui/tui-suspend.js +18 -0
- package/dist/src/tui/tui-suspend.js.map +1 -0
- package/dist/src/tui/tui-types.d.ts +86 -0
- package/dist/src/tui/tui-types.js +21 -0
- package/dist/src/tui/tui-types.js.map +1 -0
- package/dist/src/tui/tui.d.ts +5 -0
- package/dist/src/tui/tui.js +389 -0
- package/dist/src/tui/tui.js.map +1 -0
- package/package.json +5 -3
- package/dist/gateway/static/root/assets/agents-w8_jzuiX.js +0 -216
- package/dist/gateway/static/root/assets/agents-w8_jzuiX.js.map +0 -1
- package/dist/gateway/static/root/assets/channels-settings-DUKRPC7C.js +0 -9
- package/dist/gateway/static/root/assets/channels-settings-DUKRPC7C.js.map +0 -1
- package/dist/gateway/static/root/assets/cron-page-S18t1yG-.js +0 -2
- package/dist/gateway/static/root/assets/cron-page-S18t1yG-.js.map +0 -1
- package/dist/gateway/static/root/assets/index-OT4cGzon.css +0 -1
- package/dist/gateway/static/root/assets/index-PfkB8N37.js +0 -4734
- package/dist/gateway/static/root/assets/logs-page-DoWe1GWy.js +0 -2
- package/dist/gateway/static/root/assets/logs-page-DoWe1GWy.js.map +0 -1
- package/dist/gateway/static/root/assets/sessions-page-2uOYwEwd.js +0 -2
- package/dist/gateway/static/root/assets/sessions-page-2uOYwEwd.js.map +0 -1
- package/dist/gateway/static/root/assets/settings-page-fQWswCuq.js +0 -2
- package/dist/gateway/static/root/assets/settings-page-fQWswCuq.js.map +0 -1
- package/dist/gateway/static/root/assets/skills-page-BmBDCEbY.js +0 -3
|
@@ -12,7 +12,7 @@ async function resolveEffectiveThinkingLevel(sessionConfigStore, sessionKey, req
|
|
|
12
12
|
if (fromSession !== void 0) return fromSession;
|
|
13
13
|
return agentDefault ?? FALLBACK;
|
|
14
14
|
}
|
|
15
|
-
const REASONING_FALLBACK = "
|
|
15
|
+
const REASONING_FALLBACK = "stream";
|
|
16
16
|
/**
|
|
17
17
|
* Session override > agent default (`agents.defaults.reasoningDefault`).
|
|
18
18
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"thinking-resolve.js","names":[],"sources":["../../../src/session/thinking-resolve.ts"],"sourcesContent":["/**\n * Resolve effective thinking level: request override > session store > agent default.\n */\n\nimport type { ThinkingLevel } from '@mariozechner/pi-agent-core';\nimport type { SessionConfigStore } from './config-store.js';\nimport { resolveThinkingLevel, resolveReasoningLevel } from './config-store.js';\nimport {\n normalizeThinkLevel,\n type ThinkLevel,\n type ReasoningLevel,\n} from '../agent/transcript/thinking-types.js';\n\nconst FALLBACK: ThinkingLevel = 'medium';\n\n/**\n * @param requestOverride - Raw value from HTTP/API (e.g. Web pill); wins over persisted session when valid.\n */\nexport async function resolveEffectiveThinkingLevel(\n sessionConfigStore: SessionConfigStore,\n sessionKey: string,\n requestOverride?: string | null,\n agentDefault?: ThinkLevel,\n): Promise<ThinkingLevel> {\n const fromRequest = normalizeThinkLevel(requestOverride ?? undefined);\n if (fromRequest !== undefined) {\n return fromRequest as ThinkingLevel;\n }\n\n const fromSession = await resolveThinkingLevel(sessionConfigStore, sessionKey, agentDefault);\n if (fromSession !== undefined) {\n return fromSession as ThinkingLevel;\n }\n\n const def = agentDefault ?? FALLBACK;\n return def as ThinkingLevel;\n}\n\nconst REASONING_FALLBACK: ReasoningLevel = '
|
|
1
|
+
{"version":3,"file":"thinking-resolve.js","names":[],"sources":["../../../src/session/thinking-resolve.ts"],"sourcesContent":["/**\n * Resolve effective thinking level: request override > session store > agent default.\n */\n\nimport type { ThinkingLevel } from '@mariozechner/pi-agent-core';\nimport type { SessionConfigStore } from './config-store.js';\nimport { resolveThinkingLevel, resolveReasoningLevel } from './config-store.js';\nimport {\n normalizeThinkLevel,\n type ThinkLevel,\n type ReasoningLevel,\n} from '../agent/transcript/thinking-types.js';\n\nconst FALLBACK: ThinkingLevel = 'medium';\n\n/**\n * @param requestOverride - Raw value from HTTP/API (e.g. Web pill); wins over persisted session when valid.\n */\nexport async function resolveEffectiveThinkingLevel(\n sessionConfigStore: SessionConfigStore,\n sessionKey: string,\n requestOverride?: string | null,\n agentDefault?: ThinkLevel,\n): Promise<ThinkingLevel> {\n const fromRequest = normalizeThinkLevel(requestOverride ?? undefined);\n if (fromRequest !== undefined) {\n return fromRequest as ThinkingLevel;\n }\n\n const fromSession = await resolveThinkingLevel(sessionConfigStore, sessionKey, agentDefault);\n if (fromSession !== undefined) {\n return fromSession as ThinkingLevel;\n }\n\n const def = agentDefault ?? FALLBACK;\n return def as ThinkingLevel;\n}\n\nconst REASONING_FALLBACK: ReasoningLevel = 'stream';\n\n/**\n * Session override > agent default (`agents.defaults.reasoningDefault`).\n */\nexport async function resolveEffectiveReasoningLevel(\n sessionConfigStore: SessionConfigStore,\n sessionKey: string,\n agentDefault?: ReasoningLevel,\n): Promise<ReasoningLevel> {\n const def = agentDefault ?? REASONING_FALLBACK;\n const resolved = await resolveReasoningLevel(sessionConfigStore, sessionKey, def);\n return resolved ?? def;\n}\n"],"mappings":";;;AAaA,MAAM,WAA0B;;;;AAKhC,eAAsB,8BACpB,oBACA,YACA,iBACA,cACwB;CACxB,MAAM,cAAc,oBAAoB,mBAAmB,KAAA,EAAU;AACrE,KAAI,gBAAgB,KAAA,EAClB,QAAO;CAGT,MAAM,cAAc,MAAM,qBAAqB,oBAAoB,YAAY,aAAa;AAC5F,KAAI,gBAAgB,KAAA,EAClB,QAAO;AAIT,QADY,gBAAgB;;AAI9B,MAAM,qBAAqC;;;;AAK3C,eAAsB,+BACpB,oBACA,YACA,cACyB;CACzB,MAAM,MAAM,gBAAgB;AAE5B,QAAO,MADgB,sBAAsB,oBAAoB,YAAY,IAAI,IAC9D"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { ChatSendOptions, HistoryMessage, TuiBackend, TuiEvent, TuiModelChoice, TuiSessionItem } from '../tui-backend.js';
|
|
2
|
+
import type { SessionInfo } from '../tui-types.js';
|
|
3
|
+
/**
|
|
4
|
+
* TUI backend that runs the agent in-process (no gateway required).
|
|
5
|
+
*
|
|
6
|
+
* Wraps `AgentService` directly and emits TuiEvents by observing the
|
|
7
|
+
* `MessageBus` output stream.
|
|
8
|
+
*/
|
|
9
|
+
export declare class EmbeddedBackend implements TuiBackend {
|
|
10
|
+
private bus;
|
|
11
|
+
private agent;
|
|
12
|
+
private running;
|
|
13
|
+
private chatAbort;
|
|
14
|
+
onEvent?: (evt: TuiEvent) => void;
|
|
15
|
+
onConnected?: () => void;
|
|
16
|
+
onDisconnected?: (reason: string) => void;
|
|
17
|
+
constructor();
|
|
18
|
+
get connectionLabel(): string;
|
|
19
|
+
start(): void;
|
|
20
|
+
stop(): void;
|
|
21
|
+
sendChat(opts: ChatSendOptions): Promise<{
|
|
22
|
+
runId: string;
|
|
23
|
+
}>;
|
|
24
|
+
abortChat(_opts: {
|
|
25
|
+
sessionKey: string;
|
|
26
|
+
runId: string;
|
|
27
|
+
}): Promise<{
|
|
28
|
+
ok: boolean;
|
|
29
|
+
}>;
|
|
30
|
+
loadHistory(opts: {
|
|
31
|
+
sessionKey: string;
|
|
32
|
+
limit?: number;
|
|
33
|
+
}): Promise<{
|
|
34
|
+
messages: HistoryMessage[];
|
|
35
|
+
}>;
|
|
36
|
+
listSessions(): Promise<TuiSessionItem[]>;
|
|
37
|
+
getSessionInfo(_sessionKey: string): Promise<SessionInfo>;
|
|
38
|
+
listModels(): Promise<TuiModelChoice[]>;
|
|
39
|
+
resetSession(_sessionKey: string): Promise<void>;
|
|
40
|
+
patchSession(_sessionKey: string, _patch: Record<string, unknown>): Promise<void>;
|
|
41
|
+
private processOutbound;
|
|
42
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { getWorkspacePath } from "../../config/schema.js";
|
|
2
|
+
import { createLogger } from "../../utils/logger/index.js";
|
|
3
|
+
import { init_logger } from "../../utils/logger.js";
|
|
4
|
+
import { loadConfig } from "../../config/loader.js";
|
|
5
|
+
import { getAllProviders, getModelsByProvider, init_providers } from "../../providers/index.js";
|
|
6
|
+
import { MessageBus, MessageBusShutdownError } from "../../infra/bus/queue.js";
|
|
7
|
+
import "../../infra/bus/index.js";
|
|
8
|
+
import { prependEnvelopeTimestamp } from "../../channels/envelope-timestamp.js";
|
|
9
|
+
import { messagesToClientHistory } from "../../session/client-history.js";
|
|
10
|
+
import { AgentService } from "../../agent/service.js";
|
|
11
|
+
import "../../agent/index.js";
|
|
12
|
+
import "../../config/index.js";
|
|
13
|
+
//#region src/tui/backends/embedded-backend.ts
|
|
14
|
+
init_providers();
|
|
15
|
+
init_logger();
|
|
16
|
+
const log = createLogger("TUI:Embedded");
|
|
17
|
+
/**
|
|
18
|
+
* TUI backend that runs the agent in-process (no gateway required).
|
|
19
|
+
*
|
|
20
|
+
* Wraps `AgentService` directly and emits TuiEvents by observing the
|
|
21
|
+
* `MessageBus` output stream.
|
|
22
|
+
*/
|
|
23
|
+
var EmbeddedBackend = class {
|
|
24
|
+
bus;
|
|
25
|
+
agent = null;
|
|
26
|
+
running = false;
|
|
27
|
+
chatAbort = null;
|
|
28
|
+
onEvent;
|
|
29
|
+
onConnected;
|
|
30
|
+
onDisconnected;
|
|
31
|
+
constructor() {
|
|
32
|
+
this.bus = new MessageBus();
|
|
33
|
+
}
|
|
34
|
+
get connectionLabel() {
|
|
35
|
+
return "local embedded";
|
|
36
|
+
}
|
|
37
|
+
start() {
|
|
38
|
+
if (this.running) return;
|
|
39
|
+
this.running = true;
|
|
40
|
+
const config = loadConfig();
|
|
41
|
+
const workspace = getWorkspacePath(config);
|
|
42
|
+
const modelConfig = config.agents?.defaults?.model;
|
|
43
|
+
const modelId = typeof modelConfig === "string" ? modelConfig : modelConfig?.primary;
|
|
44
|
+
this.agent = new AgentService(this.bus, {
|
|
45
|
+
workspace: workspace ?? process.cwd(),
|
|
46
|
+
model: modelId,
|
|
47
|
+
config
|
|
48
|
+
});
|
|
49
|
+
this.agent.start().catch((err) => {
|
|
50
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
51
|
+
log.error({
|
|
52
|
+
err,
|
|
53
|
+
errorMessage
|
|
54
|
+
}, `Embedded agent failed: ${errorMessage}`);
|
|
55
|
+
this.onDisconnected?.(errorMessage);
|
|
56
|
+
});
|
|
57
|
+
this.processOutbound();
|
|
58
|
+
queueMicrotask(() => this.onConnected?.());
|
|
59
|
+
}
|
|
60
|
+
stop() {
|
|
61
|
+
this.running = false;
|
|
62
|
+
this.bus.shutdown();
|
|
63
|
+
this.agent?.stop();
|
|
64
|
+
this.agent = null;
|
|
65
|
+
}
|
|
66
|
+
async sendChat(opts) {
|
|
67
|
+
if (!this.agent) throw new Error("Agent not started");
|
|
68
|
+
const runId = crypto.randomUUID();
|
|
69
|
+
this.chatAbort?.abort();
|
|
70
|
+
this.chatAbort = new AbortController();
|
|
71
|
+
const signal = this.chatAbort.signal;
|
|
72
|
+
this.onEvent?.({
|
|
73
|
+
event: "status",
|
|
74
|
+
data: {
|
|
75
|
+
status: "started",
|
|
76
|
+
runId
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
(async () => {
|
|
80
|
+
try {
|
|
81
|
+
const messageForAgent = opts.message.trimStart().startsWith("/") ? opts.message : prependEnvelopeTimestamp(opts.message);
|
|
82
|
+
const stream = this.agent.processDirectStreaming(messageForAgent, opts.sessionKey, void 0, opts.thinking, { signal });
|
|
83
|
+
for await (const event of stream) {
|
|
84
|
+
if (signal.aborted) break;
|
|
85
|
+
this.onEvent?.({
|
|
86
|
+
event: event.type,
|
|
87
|
+
data: event
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
if (!signal.aborted) this.onEvent?.({
|
|
91
|
+
event: "result",
|
|
92
|
+
data: { ok: true }
|
|
93
|
+
});
|
|
94
|
+
} catch (error) {
|
|
95
|
+
if (signal.aborted) return;
|
|
96
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
97
|
+
this.onEvent?.({
|
|
98
|
+
event: "error",
|
|
99
|
+
data: { content: errorMessage }
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
})();
|
|
103
|
+
return { runId };
|
|
104
|
+
}
|
|
105
|
+
async abortChat(_opts) {
|
|
106
|
+
if (this.chatAbort) {
|
|
107
|
+
this.chatAbort.abort();
|
|
108
|
+
this.chatAbort = null;
|
|
109
|
+
return { ok: true };
|
|
110
|
+
}
|
|
111
|
+
return { ok: false };
|
|
112
|
+
}
|
|
113
|
+
async loadHistory(opts) {
|
|
114
|
+
if (!this.agent) return { messages: [] };
|
|
115
|
+
try {
|
|
116
|
+
const detail = await this.agent.loadSessionDetail(opts.sessionKey);
|
|
117
|
+
if (!detail) return { messages: [] };
|
|
118
|
+
return { messages: messagesToClientHistory(detail.messages, { limit: opts.limit }) };
|
|
119
|
+
} catch (error) {
|
|
120
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
121
|
+
log.warn({
|
|
122
|
+
err: error,
|
|
123
|
+
errorMessage
|
|
124
|
+
}, `Embedded loadHistory failed: ${errorMessage}`);
|
|
125
|
+
return { messages: [] };
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
async listSessions() {
|
|
129
|
+
return [];
|
|
130
|
+
}
|
|
131
|
+
async getSessionInfo(_sessionKey) {
|
|
132
|
+
const modelConfig = loadConfig().agents?.defaults?.model;
|
|
133
|
+
return { model: (typeof modelConfig === "string" ? modelConfig : modelConfig?.primary) ?? void 0 };
|
|
134
|
+
}
|
|
135
|
+
async listModels() {
|
|
136
|
+
const choices = [];
|
|
137
|
+
for (const provider of getAllProviders()) for (const model of getModelsByProvider(provider)) choices.push({
|
|
138
|
+
id: model.id,
|
|
139
|
+
name: model.name ?? model.id,
|
|
140
|
+
provider
|
|
141
|
+
});
|
|
142
|
+
return choices;
|
|
143
|
+
}
|
|
144
|
+
async resetSession(_sessionKey) {
|
|
145
|
+
this.stop();
|
|
146
|
+
this.bus = new MessageBus();
|
|
147
|
+
this.start();
|
|
148
|
+
}
|
|
149
|
+
async patchSession(_sessionKey, _patch) {}
|
|
150
|
+
processOutbound() {
|
|
151
|
+
(async () => {
|
|
152
|
+
while (this.running) try {
|
|
153
|
+
const msg = await this.bus.consumeOutbound();
|
|
154
|
+
log.debug({
|
|
155
|
+
channel: msg.channel,
|
|
156
|
+
chatId: msg.chat_id
|
|
157
|
+
}, "Outbound message");
|
|
158
|
+
} catch (error) {
|
|
159
|
+
if (error instanceof MessageBusShutdownError) break;
|
|
160
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
161
|
+
log.warn({
|
|
162
|
+
err: error,
|
|
163
|
+
errorMessage
|
|
164
|
+
}, `Outbound processor failed: ${errorMessage}`);
|
|
165
|
+
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
166
|
+
}
|
|
167
|
+
})();
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
//#endregion
|
|
171
|
+
export { EmbeddedBackend };
|
|
172
|
+
|
|
173
|
+
//# sourceMappingURL=embedded-backend.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"embedded-backend.js","names":[],"sources":["../../../../src/tui/backends/embedded-backend.ts"],"sourcesContent":["import { AgentService } from '../../agent/index.js';\nimport { messagesToClientHistory } from '../../session/client-history.js';\nimport { prependEnvelopeTimestamp } from '../../channels/envelope-timestamp.js';\nimport { loadConfig, getWorkspacePath } from '../../config/index.js';\nimport { MessageBus, MessageBusShutdownError } from '../../infra/bus/index.js';\nimport { getAllProviders, getModelsByProvider } from '../../providers/index.js';\nimport { createLogger } from '../../utils/logger.js';\nimport type {\n ChatSendOptions,\n HistoryMessage,\n TuiBackend,\n TuiEvent,\n TuiModelChoice,\n TuiSessionItem,\n} from '../tui-backend.js';\nimport type { SessionInfo } from '../tui-types.js';\n\nconst log = createLogger('TUI:Embedded');\n\n/**\n * TUI backend that runs the agent in-process (no gateway required).\n *\n * Wraps `AgentService` directly and emits TuiEvents by observing the\n * `MessageBus` output stream.\n */\nexport class EmbeddedBackend implements TuiBackend {\n private bus: MessageBus;\n private agent: AgentService | null = null;\n private running = false;\n private chatAbort: AbortController | null = null;\n\n onEvent?: (evt: TuiEvent) => void;\n onConnected?: () => void;\n onDisconnected?: (reason: string) => void;\n\n constructor() {\n this.bus = new MessageBus();\n }\n\n get connectionLabel(): string {\n return 'local embedded';\n }\n\n start(): void {\n if (this.running) return;\n this.running = true;\n\n const config = loadConfig();\n const workspace = getWorkspacePath(config);\n const modelConfig = config.agents?.defaults?.model;\n const modelId = typeof modelConfig === 'string' ? modelConfig : modelConfig?.primary;\n\n this.agent = new AgentService(this.bus, {\n workspace: workspace ?? process.cwd(),\n model: modelId,\n config,\n });\n\n this.agent.start().catch((err) => {\n const errorMessage = err instanceof Error ? err.message : String(err);\n log.error({ err, errorMessage }, `Embedded agent failed: ${errorMessage}`);\n this.onDisconnected?.(errorMessage);\n });\n\n // Process outbound messages in background\n this.processOutbound();\n\n // Signal ready\n queueMicrotask(() => this.onConnected?.());\n }\n\n stop(): void {\n this.running = false;\n this.bus.shutdown();\n void this.agent?.stop();\n this.agent = null;\n }\n\n async sendChat(opts: ChatSendOptions): Promise<{ runId: string }> {\n if (!this.agent) throw new Error('Agent not started');\n\n const runId = crypto.randomUUID();\n this.chatAbort?.abort();\n this.chatAbort = new AbortController();\n const signal = this.chatAbort.signal;\n\n this.onEvent?.({ event: 'status', data: { status: 'started', runId } });\n\n // Run the stream in background so the TUI event loop stays responsive.\n void (async () => {\n try {\n // Prepend envelope timestamp so the model knows the current date/time,\n // matching the behavior of channel pipelines (Telegram, Weixin, etc.).\n // Skip for slash commands — parseSlashCommand requires lines starting with '/'.\n const messageForAgent = opts.message.trimStart().startsWith('/')\n ? opts.message\n : prependEnvelopeTimestamp(opts.message);\n\n const stream = this.agent!.processDirectStreaming(\n messageForAgent,\n opts.sessionKey,\n undefined,\n opts.thinking,\n { signal },\n );\n\n for await (const event of stream) {\n if (signal.aborted) break;\n this.onEvent?.({ event: event.type, data: event });\n }\n\n if (!signal.aborted) {\n this.onEvent?.({\n event: 'result',\n data: { ok: true },\n });\n }\n } catch (error) {\n if (signal.aborted) return;\n const errorMessage = error instanceof Error ? error.message : String(error);\n this.onEvent?.({ event: 'error', data: { content: errorMessage } });\n }\n })();\n\n return { runId };\n }\n\n async abortChat(_opts: { sessionKey: string; runId: string }): Promise<{ ok: boolean }> {\n if (this.chatAbort) {\n this.chatAbort.abort();\n this.chatAbort = null;\n return { ok: true };\n }\n return { ok: false };\n }\n\n async loadHistory(opts: {\n sessionKey: string;\n limit?: number;\n }): Promise<{ messages: HistoryMessage[] }> {\n if (!this.agent) {\n return { messages: [] };\n }\n try {\n const detail = await this.agent.loadSessionDetail(opts.sessionKey);\n if (!detail) {\n return { messages: [] };\n }\n return {\n messages: messagesToClientHistory(detail.messages, { limit: opts.limit }),\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n log.warn({ err: error, errorMessage }, `Embedded loadHistory failed: ${errorMessage}`);\n return { messages: [] };\n }\n }\n\n async listSessions(): Promise<TuiSessionItem[]> {\n return [];\n }\n\n async getSessionInfo(_sessionKey: string): Promise<SessionInfo> {\n const config = loadConfig();\n const modelConfig = config.agents?.defaults?.model;\n const model = typeof modelConfig === 'string' ? modelConfig : modelConfig?.primary;\n return { model: model ?? undefined };\n }\n\n async listModels(): Promise<TuiModelChoice[]> {\n const choices: TuiModelChoice[] = [];\n for (const provider of getAllProviders()) {\n for (const model of getModelsByProvider(provider)) {\n choices.push({\n id: model.id,\n name: model.name ?? model.id,\n provider,\n });\n }\n }\n return choices;\n }\n\n async resetSession(_sessionKey: string): Promise<void> {\n // Restart agent for a clean session\n this.stop();\n this.bus = new MessageBus();\n this.start();\n }\n\n async patchSession(\n _sessionKey: string,\n _patch: Record<string, unknown>,\n ): Promise<void> {\n // Not supported in embedded mode\n }\n\n private processOutbound(): void {\n void (async () => {\n while (this.running) {\n try {\n const msg = await this.bus.consumeOutbound();\n log.debug({ channel: msg.channel, chatId: msg.chat_id }, 'Outbound message');\n } catch (error) {\n if (error instanceof MessageBusShutdownError) break;\n const errorMessage = error instanceof Error ? error.message : String(error);\n log.warn({ err: error, errorMessage }, `Outbound processor failed: ${errorMessage}`);\n await new Promise((resolve) => setTimeout(resolve, 1000));\n }\n }\n })();\n }\n}\n"],"mappings":";;;;;;;;;;;;;gBAKgF;aAC3B;AAWrD,MAAM,MAAM,aAAa,eAAe;;;;;;;AAQxC,IAAa,kBAAb,MAAmD;CACjD;CACA,QAAqC;CACrC,UAAkB;CAClB,YAA4C;CAE5C;CACA;CACA;CAEA,cAAc;AACZ,OAAK,MAAM,IAAI,YAAY;;CAG7B,IAAI,kBAA0B;AAC5B,SAAO;;CAGT,QAAc;AACZ,MAAI,KAAK,QAAS;AAClB,OAAK,UAAU;EAEf,MAAM,SAAS,YAAY;EAC3B,MAAM,YAAY,iBAAiB,OAAO;EAC1C,MAAM,cAAc,OAAO,QAAQ,UAAU;EAC7C,MAAM,UAAU,OAAO,gBAAgB,WAAW,cAAc,aAAa;AAE7E,OAAK,QAAQ,IAAI,aAAa,KAAK,KAAK;GACtC,WAAW,aAAa,QAAQ,KAAK;GACrC,OAAO;GACP;GACD,CAAC;AAEF,OAAK,MAAM,OAAO,CAAC,OAAO,QAAQ;GAChC,MAAM,eAAe,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AACrE,OAAI,MAAM;IAAE;IAAK;IAAc,EAAE,0BAA0B,eAAe;AAC1E,QAAK,iBAAiB,aAAa;IACnC;AAGF,OAAK,iBAAiB;AAGtB,uBAAqB,KAAK,eAAe,CAAC;;CAG5C,OAAa;AACX,OAAK,UAAU;AACf,OAAK,IAAI,UAAU;AACd,OAAK,OAAO,MAAM;AACvB,OAAK,QAAQ;;CAGf,MAAM,SAAS,MAAmD;AAChE,MAAI,CAAC,KAAK,MAAO,OAAM,IAAI,MAAM,oBAAoB;EAErD,MAAM,QAAQ,OAAO,YAAY;AACjC,OAAK,WAAW,OAAO;AACvB,OAAK,YAAY,IAAI,iBAAiB;EACtC,MAAM,SAAS,KAAK,UAAU;AAE9B,OAAK,UAAU;GAAE,OAAO;GAAU,MAAM;IAAE,QAAQ;IAAW;IAAO;GAAE,CAAC;AAGvE,GAAM,YAAY;AAChB,OAAI;IAIF,MAAM,kBAAkB,KAAK,QAAQ,WAAW,CAAC,WAAW,IAAI,GAC5D,KAAK,UACL,yBAAyB,KAAK,QAAQ;IAE1C,MAAM,SAAS,KAAK,MAAO,uBACzB,iBACA,KAAK,YACL,KAAA,GACA,KAAK,UACL,EAAE,QAAQ,CACX;AAED,eAAW,MAAM,SAAS,QAAQ;AAChC,SAAI,OAAO,QAAS;AACpB,UAAK,UAAU;MAAE,OAAO,MAAM;MAAM,MAAM;MAAO,CAAC;;AAGpD,QAAI,CAAC,OAAO,QACV,MAAK,UAAU;KACb,OAAO;KACP,MAAM,EAAE,IAAI,MAAM;KACnB,CAAC;YAEG,OAAO;AACd,QAAI,OAAO,QAAS;IACpB,MAAM,eAAe,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;AAC3E,SAAK,UAAU;KAAE,OAAO;KAAS,MAAM,EAAE,SAAS,cAAc;KAAE,CAAC;;MAEnE;AAEJ,SAAO,EAAE,OAAO;;CAGlB,MAAM,UAAU,OAAwE;AACtF,MAAI,KAAK,WAAW;AAClB,QAAK,UAAU,OAAO;AACtB,QAAK,YAAY;AACjB,UAAO,EAAE,IAAI,MAAM;;AAErB,SAAO,EAAE,IAAI,OAAO;;CAGtB,MAAM,YAAY,MAG0B;AAC1C,MAAI,CAAC,KAAK,MACR,QAAO,EAAE,UAAU,EAAE,EAAE;AAEzB,MAAI;GACF,MAAM,SAAS,MAAM,KAAK,MAAM,kBAAkB,KAAK,WAAW;AAClE,OAAI,CAAC,OACH,QAAO,EAAE,UAAU,EAAE,EAAE;AAEzB,UAAO,EACL,UAAU,wBAAwB,OAAO,UAAU,EAAE,OAAO,KAAK,OAAO,CAAC,EAC1E;WACM,OAAO;GACd,MAAM,eAAe,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;AAC3E,OAAI,KAAK;IAAE,KAAK;IAAO;IAAc,EAAE,gCAAgC,eAAe;AACtF,UAAO,EAAE,UAAU,EAAE,EAAE;;;CAI3B,MAAM,eAA0C;AAC9C,SAAO,EAAE;;CAGX,MAAM,eAAe,aAA2C;EAE9D,MAAM,cADS,YACW,CAAC,QAAQ,UAAU;AAE7C,SAAO,EAAE,QADK,OAAO,gBAAgB,WAAW,cAAc,aAAa,YAClD,KAAA,GAAW;;CAGtC,MAAM,aAAwC;EAC5C,MAAM,UAA4B,EAAE;AACpC,OAAK,MAAM,YAAY,iBAAiB,CACtC,MAAK,MAAM,SAAS,oBAAoB,SAAS,CAC/C,SAAQ,KAAK;GACX,IAAI,MAAM;GACV,MAAM,MAAM,QAAQ,MAAM;GAC1B;GACD,CAAC;AAGN,SAAO;;CAGT,MAAM,aAAa,aAAoC;AAErD,OAAK,MAAM;AACX,OAAK,MAAM,IAAI,YAAY;AAC3B,OAAK,OAAO;;CAGd,MAAM,aACJ,aACA,QACe;CAIjB,kBAAgC;AAC9B,GAAM,YAAY;AAChB,UAAO,KAAK,QACV,KAAI;IACF,MAAM,MAAM,MAAM,KAAK,IAAI,iBAAiB;AAC5C,QAAI,MAAM;KAAE,SAAS,IAAI;KAAS,QAAQ,IAAI;KAAS,EAAE,mBAAmB;YACrE,OAAO;AACd,QAAI,iBAAiB,wBAAyB;IAC9C,MAAM,eAAe,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;AAC3E,QAAI,KAAK;KAAE,KAAK;KAAO;KAAc,EAAE,8BAA8B,eAAe;AACpF,UAAM,IAAI,SAAS,YAAY,WAAW,SAAS,IAAK,CAAC;;MAG3D"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { ChatSendOptions, HistoryMessage, TuiBackend, TuiEvent, TuiModelChoice, TuiSessionItem } from '../tui-backend.js';
|
|
2
|
+
import type { SessionInfo } from '../tui-types.js';
|
|
3
|
+
interface GatewaySSEOptions {
|
|
4
|
+
url: string;
|
|
5
|
+
token?: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* TUI backend that communicates with a running xopc gateway via HTTP + SSE.
|
|
9
|
+
*
|
|
10
|
+
* - Agent streaming: `POST /api/agent` with `Accept: text/event-stream`
|
|
11
|
+
* - Broadcast events: `GET /api/events` via long-lived SSE
|
|
12
|
+
* - REST calls for sessions, models, etc.
|
|
13
|
+
*/
|
|
14
|
+
export declare class GatewaySseBackend implements TuiBackend {
|
|
15
|
+
private readonly baseUrl;
|
|
16
|
+
private readonly token;
|
|
17
|
+
private eventAbort;
|
|
18
|
+
private chatAbort;
|
|
19
|
+
onEvent?: (evt: TuiEvent) => void;
|
|
20
|
+
onConnected?: () => void;
|
|
21
|
+
onDisconnected?: (reason: string) => void;
|
|
22
|
+
onGap?: (info: {
|
|
23
|
+
expected: number;
|
|
24
|
+
received: number;
|
|
25
|
+
}) => void;
|
|
26
|
+
constructor(opts: GatewaySSEOptions);
|
|
27
|
+
get connectionLabel(): string;
|
|
28
|
+
start(): void;
|
|
29
|
+
stop(): void;
|
|
30
|
+
sendChat(opts: ChatSendOptions): Promise<{
|
|
31
|
+
runId: string;
|
|
32
|
+
}>;
|
|
33
|
+
abortChat(opts: {
|
|
34
|
+
sessionKey: string;
|
|
35
|
+
runId: string;
|
|
36
|
+
}): Promise<{
|
|
37
|
+
ok: boolean;
|
|
38
|
+
}>;
|
|
39
|
+
loadHistory(opts: {
|
|
40
|
+
sessionKey: string;
|
|
41
|
+
limit?: number;
|
|
42
|
+
}): Promise<{
|
|
43
|
+
messages: HistoryMessage[];
|
|
44
|
+
}>;
|
|
45
|
+
listSessions(): Promise<TuiSessionItem[]>;
|
|
46
|
+
getSessionInfo(sessionKey: string): Promise<SessionInfo>;
|
|
47
|
+
listModels(): Promise<TuiModelChoice[]>;
|
|
48
|
+
resetSession(sessionKey: string): Promise<void>;
|
|
49
|
+
patchSession(sessionKey: string, patch: Record<string, unknown>): Promise<void>;
|
|
50
|
+
private startEventStream;
|
|
51
|
+
private scheduleReconnect;
|
|
52
|
+
}
|
|
53
|
+
export {};
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { createLogger } from "../../utils/logger/index.js";
|
|
2
|
+
import { init_logger } from "../../utils/logger.js";
|
|
3
|
+
import { prependEnvelopeTimestamp } from "../../channels/envelope-timestamp.js";
|
|
4
|
+
import { consumeSSEStream, parseSSEData } from "../sse-consumer.js";
|
|
5
|
+
//#region src/tui/backends/gateway-sse-backend.ts
|
|
6
|
+
init_logger();
|
|
7
|
+
const log = createLogger("TUI:GatewaySSE");
|
|
8
|
+
/** Fetch wrapper that adds auth headers. */
|
|
9
|
+
async function gatewayFetch(baseUrl, path, token, init) {
|
|
10
|
+
const headers = {
|
|
11
|
+
"Content-Type": "application/json",
|
|
12
|
+
...token ? { Authorization: `Bearer ${token}` } : {},
|
|
13
|
+
...init?.headers
|
|
14
|
+
};
|
|
15
|
+
return fetch(`${baseUrl}${path}`, {
|
|
16
|
+
...init,
|
|
17
|
+
headers
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* TUI backend that communicates with a running xopc gateway via HTTP + SSE.
|
|
22
|
+
*
|
|
23
|
+
* - Agent streaming: `POST /api/agent` with `Accept: text/event-stream`
|
|
24
|
+
* - Broadcast events: `GET /api/events` via long-lived SSE
|
|
25
|
+
* - REST calls for sessions, models, etc.
|
|
26
|
+
*/
|
|
27
|
+
var GatewaySseBackend = class {
|
|
28
|
+
baseUrl;
|
|
29
|
+
token;
|
|
30
|
+
eventAbort = null;
|
|
31
|
+
chatAbort = null;
|
|
32
|
+
onEvent;
|
|
33
|
+
onConnected;
|
|
34
|
+
onDisconnected;
|
|
35
|
+
onGap;
|
|
36
|
+
constructor(opts) {
|
|
37
|
+
this.baseUrl = opts.url.replace(/\/+$/, "");
|
|
38
|
+
this.token = opts.token;
|
|
39
|
+
}
|
|
40
|
+
get connectionLabel() {
|
|
41
|
+
return this.baseUrl;
|
|
42
|
+
}
|
|
43
|
+
start() {
|
|
44
|
+
this.startEventStream();
|
|
45
|
+
}
|
|
46
|
+
stop() {
|
|
47
|
+
this.eventAbort?.abort();
|
|
48
|
+
this.eventAbort = null;
|
|
49
|
+
this.chatAbort?.abort();
|
|
50
|
+
this.chatAbort = null;
|
|
51
|
+
}
|
|
52
|
+
async sendChat(opts) {
|
|
53
|
+
this.chatAbort?.abort();
|
|
54
|
+
this.chatAbort = new AbortController();
|
|
55
|
+
const signal = this.chatAbort.signal;
|
|
56
|
+
const runId = crypto.randomUUID();
|
|
57
|
+
this.onEvent?.({
|
|
58
|
+
event: "status",
|
|
59
|
+
data: {
|
|
60
|
+
status: "started",
|
|
61
|
+
runId
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
(async () => {
|
|
65
|
+
try {
|
|
66
|
+
const res = await gatewayFetch(this.baseUrl, "/api/agent", this.token, {
|
|
67
|
+
method: "POST",
|
|
68
|
+
headers: { Accept: "text/event-stream" },
|
|
69
|
+
body: JSON.stringify({
|
|
70
|
+
message: opts.message.trimStart().startsWith("/") ? opts.message : prependEnvelopeTimestamp(opts.message),
|
|
71
|
+
channel: "webchat",
|
|
72
|
+
sessionKey: opts.sessionKey,
|
|
73
|
+
thinking: opts.thinking
|
|
74
|
+
}),
|
|
75
|
+
signal
|
|
76
|
+
});
|
|
77
|
+
if (!res.ok) {
|
|
78
|
+
const body = await res.json().catch(() => ({}));
|
|
79
|
+
this.onEvent?.({
|
|
80
|
+
event: "error",
|
|
81
|
+
data: { content: body.error?.message ?? `Gateway error: ${res.status}` }
|
|
82
|
+
});
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
if ((res.headers.get("Content-Type") ?? "").includes("text/event-stream") && res.body) await consumeSSEStream(res.body, (sseEvent) => {
|
|
86
|
+
if (signal.aborted) return;
|
|
87
|
+
const data = parseSSEData(sseEvent.data);
|
|
88
|
+
if (!data) return;
|
|
89
|
+
this.onEvent?.({
|
|
90
|
+
event: sseEvent.event,
|
|
91
|
+
data
|
|
92
|
+
});
|
|
93
|
+
}, signal);
|
|
94
|
+
else {
|
|
95
|
+
const json = await res.json();
|
|
96
|
+
if (json.ok && json.payload?.content) {
|
|
97
|
+
this.onEvent?.({
|
|
98
|
+
event: "token",
|
|
99
|
+
data: { content: json.payload.content }
|
|
100
|
+
});
|
|
101
|
+
this.onEvent?.({
|
|
102
|
+
event: "result",
|
|
103
|
+
data: { ok: true }
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
} catch (error) {
|
|
108
|
+
if (signal.aborted) return;
|
|
109
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
110
|
+
this.onEvent?.({
|
|
111
|
+
event: "error",
|
|
112
|
+
data: { content: errorMessage }
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
})();
|
|
116
|
+
return { runId };
|
|
117
|
+
}
|
|
118
|
+
async abortChat(opts) {
|
|
119
|
+
this.chatAbort?.abort();
|
|
120
|
+
this.chatAbort = null;
|
|
121
|
+
try {
|
|
122
|
+
return { ok: (await (await gatewayFetch(this.baseUrl, "/api/agent/abort", this.token, {
|
|
123
|
+
method: "POST",
|
|
124
|
+
body: JSON.stringify({ runId: opts.runId })
|
|
125
|
+
})).json()).ok ?? false };
|
|
126
|
+
} catch {
|
|
127
|
+
return { ok: false };
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
async loadHistory(opts) {
|
|
131
|
+
try {
|
|
132
|
+
const params = new URLSearchParams();
|
|
133
|
+
if (opts.limit) params.set("limit", String(opts.limit));
|
|
134
|
+
const qs = params.toString();
|
|
135
|
+
const res = await gatewayFetch(this.baseUrl, `/api/sessions/${encodeURIComponent(opts.sessionKey)}/messages${qs ? `?${qs}` : ""}`, this.token);
|
|
136
|
+
if (!res.ok) return { messages: [] };
|
|
137
|
+
return { messages: (await res.json()).payload?.messages ?? [] };
|
|
138
|
+
} catch (error) {
|
|
139
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
140
|
+
log.warn({
|
|
141
|
+
err: error,
|
|
142
|
+
errorMessage
|
|
143
|
+
}, `Failed to load history: ${errorMessage}`);
|
|
144
|
+
return { messages: [] };
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
async listSessions() {
|
|
148
|
+
try {
|
|
149
|
+
const res = await gatewayFetch(this.baseUrl, "/api/sessions", this.token);
|
|
150
|
+
if (!res.ok) return [];
|
|
151
|
+
return ((await res.json()).items ?? []).map((s) => ({
|
|
152
|
+
key: s.key,
|
|
153
|
+
displayName: s.name,
|
|
154
|
+
updatedAt: s.updatedAt ? Date.parse(s.updatedAt) : void 0,
|
|
155
|
+
totalTokens: s.estimatedTokens ?? null,
|
|
156
|
+
model: typeof s.customData?.model === "string" ? s.customData.model : typeof s.customData?.modelRef === "string" ? s.customData.modelRef : null
|
|
157
|
+
}));
|
|
158
|
+
} catch {
|
|
159
|
+
return [];
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
async getSessionInfo(sessionKey) {
|
|
163
|
+
try {
|
|
164
|
+
const res = await gatewayFetch(this.baseUrl, `/api/sessions/${encodeURIComponent(sessionKey)}`, this.token);
|
|
165
|
+
if (!res.ok) return {};
|
|
166
|
+
const s = (await res.json()).session;
|
|
167
|
+
if (!s) return {};
|
|
168
|
+
return {
|
|
169
|
+
displayName: s.name,
|
|
170
|
+
totalTokens: s.estimatedTokens ?? void 0,
|
|
171
|
+
model: typeof s.customData?.model === "string" ? s.customData.model : typeof s.customData?.modelRef === "string" ? s.customData.modelRef : void 0,
|
|
172
|
+
modelProvider: typeof s.customData?.modelProvider === "string" ? s.customData.modelProvider : void 0
|
|
173
|
+
};
|
|
174
|
+
} catch {
|
|
175
|
+
return {};
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
async listModels() {
|
|
179
|
+
try {
|
|
180
|
+
const res = await gatewayFetch(this.baseUrl, "/api/models", this.token);
|
|
181
|
+
if (!res.ok) return [];
|
|
182
|
+
return (await res.json()).payload?.models ?? [];
|
|
183
|
+
} catch {
|
|
184
|
+
return [];
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
async resetSession(sessionKey) {
|
|
188
|
+
await gatewayFetch(this.baseUrl, `/api/sessions/${encodeURIComponent(sessionKey)}`, this.token, { method: "DELETE" }).catch(() => {});
|
|
189
|
+
}
|
|
190
|
+
async patchSession(sessionKey, patch) {
|
|
191
|
+
await gatewayFetch(this.baseUrl, `/api/sessions/${encodeURIComponent(sessionKey)}`, this.token, {
|
|
192
|
+
method: "PATCH",
|
|
193
|
+
body: JSON.stringify(patch)
|
|
194
|
+
}).catch(() => {});
|
|
195
|
+
}
|
|
196
|
+
startEventStream() {
|
|
197
|
+
this.eventAbort?.abort();
|
|
198
|
+
this.eventAbort = new AbortController();
|
|
199
|
+
const url = new URL(`${this.baseUrl}/api/events`);
|
|
200
|
+
if (this.token) url.searchParams.set("token", this.token);
|
|
201
|
+
const connect = async () => {
|
|
202
|
+
try {
|
|
203
|
+
const res = await fetch(url.toString(), {
|
|
204
|
+
signal: this.eventAbort.signal,
|
|
205
|
+
headers: { Accept: "text/event-stream" }
|
|
206
|
+
});
|
|
207
|
+
if (!res.ok || !res.body) {
|
|
208
|
+
this.onDisconnected?.(`event stream error: ${res.status}`);
|
|
209
|
+
this.scheduleReconnect();
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
this.onConnected?.();
|
|
213
|
+
await consumeSSEStream(res.body, (sseEvent) => {
|
|
214
|
+
if (sseEvent.event === "connected") return;
|
|
215
|
+
if (sseEvent.event === "gap") {
|
|
216
|
+
const gapData = parseSSEData(sseEvent.data);
|
|
217
|
+
if (gapData && typeof gapData.expected === "number" && typeof gapData.received === "number") this.onGap?.({
|
|
218
|
+
expected: gapData.expected,
|
|
219
|
+
received: gapData.received
|
|
220
|
+
});
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
const data = parseSSEData(sseEvent.data);
|
|
224
|
+
if (data !== null) this.onEvent?.({
|
|
225
|
+
event: sseEvent.event,
|
|
226
|
+
data
|
|
227
|
+
});
|
|
228
|
+
}, this.eventAbort.signal);
|
|
229
|
+
if (!this.eventAbort?.signal.aborted) {
|
|
230
|
+
this.onDisconnected?.("stream closed");
|
|
231
|
+
this.scheduleReconnect();
|
|
232
|
+
}
|
|
233
|
+
} catch (error) {
|
|
234
|
+
if (this.eventAbort?.signal.aborted) return;
|
|
235
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
236
|
+
log.warn({
|
|
237
|
+
err: error,
|
|
238
|
+
errorMessage
|
|
239
|
+
}, `Event stream failed: ${errorMessage}`);
|
|
240
|
+
this.onDisconnected?.(errorMessage);
|
|
241
|
+
this.scheduleReconnect();
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
connect();
|
|
245
|
+
}
|
|
246
|
+
scheduleReconnect() {
|
|
247
|
+
if (this.eventAbort?.signal.aborted) return;
|
|
248
|
+
setTimeout(() => {
|
|
249
|
+
if (!this.eventAbort?.signal.aborted) this.startEventStream();
|
|
250
|
+
}, 3e3);
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
//#endregion
|
|
254
|
+
export { GatewaySseBackend };
|
|
255
|
+
|
|
256
|
+
//# sourceMappingURL=gateway-sse-backend.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gateway-sse-backend.js","names":[],"sources":["../../../../src/tui/backends/gateway-sse-backend.ts"],"sourcesContent":["import { prependEnvelopeTimestamp } from '../../channels/envelope-timestamp.js';\nimport { createLogger } from '../../utils/logger.js';\nimport { consumeSSEStream, parseSSEData } from '../sse-consumer.js';\nimport type {\n ChatSendOptions,\n HistoryMessage,\n TuiBackend,\n TuiEvent,\n TuiModelChoice,\n TuiSessionItem,\n} from '../tui-backend.js';\nimport type { SessionInfo } from '../tui-types.js';\n\nconst log = createLogger('TUI:GatewaySSE');\n\ninterface GatewaySSEOptions {\n url: string;\n token?: string;\n}\n\n/** Fetch wrapper that adds auth headers. */\nasync function gatewayFetch(\n baseUrl: string,\n path: string,\n token: string | undefined,\n init?: RequestInit,\n): Promise<Response> {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n ...(token ? { Authorization: `Bearer ${token}` } : {}),\n ...(init?.headers as Record<string, string> | undefined),\n };\n return fetch(`${baseUrl}${path}`, { ...init, headers });\n}\n\n/**\n * TUI backend that communicates with a running xopc gateway via HTTP + SSE.\n *\n * - Agent streaming: `POST /api/agent` with `Accept: text/event-stream`\n * - Broadcast events: `GET /api/events` via long-lived SSE\n * - REST calls for sessions, models, etc.\n */\nexport class GatewaySseBackend implements TuiBackend {\n private readonly baseUrl: string;\n private readonly token: string | undefined;\n private eventAbort: AbortController | null = null;\n private chatAbort: AbortController | null = null;\n\n onEvent?: (evt: TuiEvent) => void;\n onConnected?: () => void;\n onDisconnected?: (reason: string) => void;\n onGap?: (info: { expected: number; received: number }) => void;\n\n constructor(opts: GatewaySSEOptions) {\n this.baseUrl = opts.url.replace(/\\/+$/, '');\n this.token = opts.token;\n }\n\n get connectionLabel(): string {\n return this.baseUrl;\n }\n\n start(): void {\n this.startEventStream();\n }\n\n stop(): void {\n this.eventAbort?.abort();\n this.eventAbort = null;\n this.chatAbort?.abort();\n this.chatAbort = null;\n }\n\n // ── Agent chat (POST /api/agent → SSE response body) ──\n\n async sendChat(opts: ChatSendOptions): Promise<{ runId: string }> {\n this.chatAbort?.abort();\n this.chatAbort = new AbortController();\n const signal = this.chatAbort.signal;\n const runId = crypto.randomUUID();\n\n // Match EmbeddedBackend: set activeRunId before any token/tool events so TUI state stays on one\n // runId (avoids assistant under \"default\" and tools under the real uuid).\n this.onEvent?.({ event: 'status', data: { status: 'started', runId } });\n\n // Fire-and-forget: run the HTTP request + SSE consumption in background\n // so the TUI event loop stays responsive for keyboard input.\n void (async () => {\n try {\n const res = await gatewayFetch(this.baseUrl, '/api/agent', this.token, {\n method: 'POST',\n headers: { Accept: 'text/event-stream' },\n body: JSON.stringify({\n // Prepend envelope timestamp for regular messages so the model knows\n // the current date/time. Skip for slash commands — parseSlashCommand\n // requires lines starting with '/'.\n message: opts.message.trimStart().startsWith('/')\n ? opts.message\n : prependEnvelopeTimestamp(opts.message),\n channel: 'webchat',\n sessionKey: opts.sessionKey,\n thinking: opts.thinking,\n }),\n signal,\n });\n\n if (!res.ok) {\n const body = (await res.json().catch(() => ({}))) as { error?: { message?: string } };\n this.onEvent?.({\n event: 'error',\n data: { content: body.error?.message ?? `Gateway error: ${res.status}` },\n });\n return;\n }\n\n const contentType = res.headers.get('Content-Type') ?? '';\n\n if (contentType.includes('text/event-stream') && res.body) {\n await consumeSSEStream(\n res.body,\n (sseEvent) => {\n if (signal.aborted) return;\n const data = parseSSEData<Record<string, unknown>>(sseEvent.data);\n if (!data) return;\n this.onEvent?.({ event: sseEvent.event, data });\n },\n signal,\n );\n } else {\n const json = (await res.json()) as { ok?: boolean; payload?: { content?: string } };\n if (json.ok && json.payload?.content) {\n this.onEvent?.({\n event: 'token',\n data: { content: json.payload.content },\n });\n this.onEvent?.({ event: 'result', data: { ok: true } });\n }\n }\n } catch (error) {\n if (signal.aborted) return;\n const errorMessage = error instanceof Error ? error.message : String(error);\n this.onEvent?.({ event: 'error', data: { content: errorMessage } });\n }\n })();\n\n return { runId };\n }\n\n async abortChat(opts: { sessionKey: string; runId: string }): Promise<{ ok: boolean }> {\n this.chatAbort?.abort();\n this.chatAbort = null;\n try {\n const res = await gatewayFetch(this.baseUrl, '/api/agent/abort', this.token, {\n method: 'POST',\n body: JSON.stringify({ runId: opts.runId }),\n });\n const json = (await res.json()) as { ok?: boolean };\n return { ok: json.ok ?? false };\n } catch {\n return { ok: false };\n }\n }\n\n // ── REST helpers ──\n\n async loadHistory(opts: {\n sessionKey: string;\n limit?: number;\n }): Promise<{ messages: HistoryMessage[] }> {\n try {\n const params = new URLSearchParams();\n if (opts.limit) params.set('limit', String(opts.limit));\n const qs = params.toString();\n const res = await gatewayFetch(\n this.baseUrl,\n `/api/sessions/${encodeURIComponent(opts.sessionKey)}/messages${qs ? `?${qs}` : ''}`,\n this.token,\n );\n if (!res.ok) return { messages: [] };\n const json = (await res.json()) as { ok?: boolean; payload?: { messages?: HistoryMessage[] } };\n return { messages: json.payload?.messages ?? [] };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n log.warn({ err: error, errorMessage }, `Failed to load history: ${errorMessage}`);\n return { messages: [] };\n }\n }\n\n async listSessions(): Promise<TuiSessionItem[]> {\n try {\n const res = await gatewayFetch(this.baseUrl, '/api/sessions', this.token);\n if (!res.ok) return [];\n const json = (await res.json()) as {\n items?: Array<{\n key: string;\n name?: string;\n updatedAt?: string;\n estimatedTokens?: number;\n customData?: Record<string, unknown>;\n }>;\n };\n return (json.items ?? []).map((s) => ({\n key: s.key,\n displayName: s.name,\n updatedAt: s.updatedAt ? Date.parse(s.updatedAt) : undefined,\n totalTokens: s.estimatedTokens ?? null,\n model:\n typeof s.customData?.model === 'string'\n ? s.customData.model\n : typeof s.customData?.modelRef === 'string'\n ? s.customData.modelRef\n : null,\n }));\n } catch {\n return [];\n }\n }\n\n async getSessionInfo(sessionKey: string): Promise<SessionInfo> {\n try {\n const res = await gatewayFetch(\n this.baseUrl,\n `/api/sessions/${encodeURIComponent(sessionKey)}`,\n this.token,\n );\n if (!res.ok) return {};\n const json = (await res.json()) as {\n session?: {\n name?: string;\n estimatedTokens?: number;\n customData?: Record<string, unknown>;\n };\n };\n const s = json.session;\n if (!s) return {};\n return {\n displayName: s.name,\n totalTokens: s.estimatedTokens ?? undefined,\n model:\n typeof s.customData?.model === 'string'\n ? s.customData.model\n : typeof s.customData?.modelRef === 'string'\n ? s.customData.modelRef\n : undefined,\n modelProvider:\n typeof s.customData?.modelProvider === 'string' ? s.customData.modelProvider : undefined,\n };\n } catch {\n return {};\n }\n }\n\n async listModels(): Promise<TuiModelChoice[]> {\n try {\n const res = await gatewayFetch(this.baseUrl, '/api/models', this.token);\n if (!res.ok) return [];\n const json = (await res.json()) as {\n ok?: boolean;\n payload?: { models?: TuiModelChoice[] };\n };\n return json.payload?.models ?? [];\n } catch {\n return [];\n }\n }\n\n async resetSession(sessionKey: string): Promise<void> {\n await gatewayFetch(\n this.baseUrl,\n `/api/sessions/${encodeURIComponent(sessionKey)}`,\n this.token,\n { method: 'DELETE' },\n ).catch(() => {});\n }\n\n async patchSession(sessionKey: string, patch: Record<string, unknown>): Promise<void> {\n await gatewayFetch(\n this.baseUrl,\n `/api/sessions/${encodeURIComponent(sessionKey)}`,\n this.token,\n { method: 'PATCH', body: JSON.stringify(patch) },\n ).catch(() => {});\n }\n\n // ── Broadcast SSE (GET /api/events) ──\n\n private startEventStream(): void {\n this.eventAbort?.abort();\n this.eventAbort = new AbortController();\n\n const url = new URL(`${this.baseUrl}/api/events`);\n if (this.token) url.searchParams.set('token', this.token);\n\n const connect = async () => {\n try {\n const res = await fetch(url.toString(), {\n signal: this.eventAbort!.signal,\n headers: { Accept: 'text/event-stream' },\n });\n\n if (!res.ok || !res.body) {\n this.onDisconnected?.(`event stream error: ${res.status}`);\n this.scheduleReconnect();\n return;\n }\n\n this.onConnected?.();\n\n await consumeSSEStream(\n res.body,\n (sseEvent) => {\n if (sseEvent.event === 'connected') return;\n if (sseEvent.event === 'gap') {\n const gapData = parseSSEData(sseEvent.data) as {\n expected?: unknown;\n received?: unknown;\n } | null;\n if (\n gapData &&\n typeof gapData.expected === 'number' &&\n typeof gapData.received === 'number'\n ) {\n this.onGap?.({ expected: gapData.expected, received: gapData.received });\n }\n return;\n }\n const data = parseSSEData(sseEvent.data);\n if (data !== null) {\n this.onEvent?.({ event: sseEvent.event, data });\n }\n },\n this.eventAbort!.signal,\n );\n\n // Stream ended normally\n if (!this.eventAbort?.signal.aborted) {\n this.onDisconnected?.('stream closed');\n this.scheduleReconnect();\n }\n } catch (error) {\n if (this.eventAbort?.signal.aborted) return;\n const errorMessage = error instanceof Error ? error.message : String(error);\n log.warn({ err: error, errorMessage }, `Event stream failed: ${errorMessage}`);\n this.onDisconnected?.(errorMessage);\n this.scheduleReconnect();\n }\n };\n\n void connect();\n }\n\n private scheduleReconnect(): void {\n if (this.eventAbort?.signal.aborted) return;\n setTimeout(() => {\n if (!this.eventAbort?.signal.aborted) {\n this.startEventStream();\n }\n }, 3000);\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { HistoryMessage } from './tui-backend.js';
|
|
2
|
+
import { ChatLog } from './components/chat-log.js';
|
|
3
|
+
/** Replay persisted transcript into the scroll log (synthetic run ids per assistant row). */
|
|
4
|
+
export declare function appendHistoryToChatLog(chatLog: ChatLog, messages: HistoryMessage[], toolsExpanded: boolean): void;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
//#region src/tui/chat-history.ts
|
|
2
|
+
/** Replay persisted transcript into the scroll log (synthetic run ids per assistant row). */
|
|
3
|
+
function appendHistoryToChatLog(chatLog, messages, toolsExpanded) {
|
|
4
|
+
chatLog.setToolsExpanded(toolsExpanded);
|
|
5
|
+
messages.forEach((hm, idx) => {
|
|
6
|
+
const runId = `history:${idx}`;
|
|
7
|
+
if (hm.role === "user") {
|
|
8
|
+
chatLog.addUser(hm.content);
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
if (hm.role === "system") {
|
|
12
|
+
chatLog.addSystem(hm.content);
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
const tools = hm.toolCalls ?? [];
|
|
16
|
+
for (let t = 0; t < tools.length; t++) {
|
|
17
|
+
const tc = tools[t];
|
|
18
|
+
const tid = `history:${idx}:t:${t}`;
|
|
19
|
+
chatLog.startTool(tid, tc.name, tc.args ?? {}, runId);
|
|
20
|
+
if (tc.result !== void 0) chatLog.updateToolResult(tid, tc.result, tc.isError ?? false);
|
|
21
|
+
}
|
|
22
|
+
const body = hm.content.trim() ? hm.content : " ";
|
|
23
|
+
chatLog.finalizeAssistant(body, runId);
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
//#endregion
|
|
27
|
+
export { appendHistoryToChatLog };
|
|
28
|
+
|
|
29
|
+
//# sourceMappingURL=chat-history.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat-history.js","names":[],"sources":["../../../src/tui/chat-history.ts"],"sourcesContent":["import type { HistoryMessage } from './tui-backend.js';\nimport { ChatLog } from './components/chat-log.js';\n\n/** Replay persisted transcript into the scroll log (synthetic run ids per assistant row). */\nexport function appendHistoryToChatLog(\n chatLog: ChatLog,\n messages: HistoryMessage[],\n toolsExpanded: boolean,\n): void {\n chatLog.setToolsExpanded(toolsExpanded);\n\n messages.forEach((hm, idx) => {\n const runId = `history:${idx}`;\n\n if (hm.role === 'user') {\n chatLog.addUser(hm.content);\n return;\n }\n\n if (hm.role === 'system') {\n chatLog.addSystem(hm.content);\n return;\n }\n\n const tools = hm.toolCalls ?? [];\n for (let t = 0; t < tools.length; t++) {\n const tc = tools[t]!;\n const tid = `history:${idx}:t:${t}`;\n chatLog.startTool(tid, tc.name, tc.args ?? {}, runId);\n if (tc.result !== undefined) {\n chatLog.updateToolResult(tid, tc.result, tc.isError ?? false);\n }\n }\n\n const body = hm.content.trim() ? hm.content : ' ';\n chatLog.finalizeAssistant(body, runId);\n });\n}\n"],"mappings":";;AAIA,SAAgB,uBACd,SACA,UACA,eACM;AACN,SAAQ,iBAAiB,cAAc;AAEvC,UAAS,SAAS,IAAI,QAAQ;EAC5B,MAAM,QAAQ,WAAW;AAEzB,MAAI,GAAG,SAAS,QAAQ;AACtB,WAAQ,QAAQ,GAAG,QAAQ;AAC3B;;AAGF,MAAI,GAAG,SAAS,UAAU;AACxB,WAAQ,UAAU,GAAG,QAAQ;AAC7B;;EAGF,MAAM,QAAQ,GAAG,aAAa,EAAE;AAChC,OAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;GACrC,MAAM,KAAK,MAAM;GACjB,MAAM,MAAM,WAAW,IAAI,KAAK;AAChC,WAAQ,UAAU,KAAK,GAAG,MAAM,GAAG,QAAQ,EAAE,EAAE,MAAM;AACrD,OAAI,GAAG,WAAW,KAAA,EAChB,SAAQ,iBAAiB,KAAK,GAAG,QAAQ,GAAG,WAAW,MAAM;;EAIjE,MAAM,OAAO,GAAG,QAAQ,MAAM,GAAG,GAAG,UAAU;AAC9C,UAAQ,kBAAkB,MAAM,MAAM;GACtC"}
|