@krischoichoi/channel-harness 0.6.0
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/README.md +110 -0
- package/lib/access/controller.d.ts +37 -0
- package/lib/access/controller.d.ts.map +1 -0
- package/lib/access/controller.js +51 -0
- package/lib/access/controller.js.map +1 -0
- package/lib/access/decision.d.ts +18 -0
- package/lib/access/decision.d.ts.map +1 -0
- package/lib/access/decision.js +2 -0
- package/lib/access/decision.js.map +1 -0
- package/lib/access/resolver.d.ts +41 -0
- package/lib/access/resolver.d.ts.map +1 -0
- package/lib/access/resolver.js +31 -0
- package/lib/access/resolver.js.map +1 -0
- package/lib/agent-manager.d.ts +314 -0
- package/lib/agent-manager.d.ts.map +1 -0
- package/lib/agent-manager.js +603 -0
- package/lib/agent-manager.js.map +1 -0
- package/lib/agent-router.d.ts +37 -0
- package/lib/agent-router.d.ts.map +1 -0
- package/lib/agent-router.js +33 -0
- package/lib/agent-router.js.map +1 -0
- package/lib/bind-support.d.ts +23 -0
- package/lib/bind-support.d.ts.map +1 -0
- package/lib/bind-support.js +63 -0
- package/lib/bind-support.js.map +1 -0
- package/lib/binding-store.d.ts +104 -0
- package/lib/binding-store.d.ts.map +1 -0
- package/lib/binding-store.js +354 -0
- package/lib/binding-store.js.map +1 -0
- package/lib/bridge.d.ts +248 -0
- package/lib/bridge.d.ts.map +1 -0
- package/lib/bridge.js +961 -0
- package/lib/bridge.js.map +1 -0
- package/lib/channel-label.d.ts +46 -0
- package/lib/channel-label.d.ts.map +1 -0
- package/lib/channel-label.js +89 -0
- package/lib/channel-label.js.map +1 -0
- package/lib/channel-session-factory.d.ts +76 -0
- package/lib/channel-session-factory.d.ts.map +1 -0
- package/lib/channel-session-factory.js +187 -0
- package/lib/channel-session-factory.js.map +1 -0
- package/lib/commands/bind.d.ts +21 -0
- package/lib/commands/bind.d.ts.map +1 -0
- package/lib/commands/bind.js +91 -0
- package/lib/commands/bind.js.map +1 -0
- package/lib/commands/help.d.ts +15 -0
- package/lib/commands/help.d.ts.map +1 -0
- package/lib/commands/help.js +89 -0
- package/lib/commands/help.js.map +1 -0
- package/lib/commands/index.d.ts +122 -0
- package/lib/commands/index.d.ts.map +1 -0
- package/lib/commands/index.js +63 -0
- package/lib/commands/index.js.map +1 -0
- package/lib/commands/locale.d.ts +9 -0
- package/lib/commands/locale.d.ts.map +1 -0
- package/lib/commands/locale.js +27 -0
- package/lib/commands/locale.js.map +1 -0
- package/lib/commands/mirror.d.ts +14 -0
- package/lib/commands/mirror.d.ts.map +1 -0
- package/lib/commands/mirror.js +44 -0
- package/lib/commands/mirror.js.map +1 -0
- package/lib/commands/model.d.ts +20 -0
- package/lib/commands/model.d.ts.map +1 -0
- package/lib/commands/model.js +106 -0
- package/lib/commands/model.js.map +1 -0
- package/lib/commands/models.d.ts +14 -0
- package/lib/commands/models.d.ts.map +1 -0
- package/lib/commands/models.js +67 -0
- package/lib/commands/models.js.map +1 -0
- package/lib/commands/new.d.ts +14 -0
- package/lib/commands/new.d.ts.map +1 -0
- package/lib/commands/new.js +37 -0
- package/lib/commands/new.js.map +1 -0
- package/lib/commands/status.d.ts +12 -0
- package/lib/commands/status.d.ts.map +1 -0
- package/lib/commands/status.js +30 -0
- package/lib/commands/status.js.map +1 -0
- package/lib/commands/stop.d.ts +16 -0
- package/lib/commands/stop.d.ts.map +1 -0
- package/lib/commands/stop.js +27 -0
- package/lib/commands/stop.js.map +1 -0
- package/lib/commands/version.d.ts +25 -0
- package/lib/commands/version.d.ts.map +1 -0
- package/lib/commands/version.js +57 -0
- package/lib/commands/version.js.map +1 -0
- package/lib/config.d.ts +119 -0
- package/lib/config.d.ts.map +1 -0
- package/lib/config.js +60 -0
- package/lib/config.js.map +1 -0
- package/lib/debug-logger.d.ts +8 -0
- package/lib/debug-logger.d.ts.map +1 -0
- package/lib/debug-logger.js +32 -0
- package/lib/debug-logger.js.map +1 -0
- package/lib/dsh-home.d.ts +12 -0
- package/lib/dsh-home.d.ts.map +1 -0
- package/lib/dsh-home.js +31 -0
- package/lib/dsh-home.js.map +1 -0
- package/lib/failure-display.d.ts +10 -0
- package/lib/failure-display.d.ts.map +1 -0
- package/lib/failure-display.js +42 -0
- package/lib/failure-display.js.map +1 -0
- package/lib/file-provider.d.ts +96 -0
- package/lib/file-provider.d.ts.map +1 -0
- package/lib/file-provider.js +44 -0
- package/lib/file-provider.js.map +1 -0
- package/lib/index.d.ts +31 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +31 -0
- package/lib/index.js.map +1 -0
- package/lib/interactions/index.d.ts +13 -0
- package/lib/interactions/index.d.ts.map +1 -0
- package/lib/interactions/index.js +13 -0
- package/lib/interactions/index.js.map +1 -0
- package/lib/interactions/question-backend.d.ts +133 -0
- package/lib/interactions/question-backend.d.ts.map +1 -0
- package/lib/interactions/question-backend.js +37 -0
- package/lib/interactions/question-backend.js.map +1 -0
- package/lib/interactions/question-presenter.d.ts +68 -0
- package/lib/interactions/question-presenter.d.ts.map +1 -0
- package/lib/interactions/question-presenter.js +425 -0
- package/lib/interactions/question-presenter.js.map +1 -0
- package/lib/interactions/question-renderer.d.ts +37 -0
- package/lib/interactions/question-renderer.d.ts.map +1 -0
- package/lib/interactions/question-renderer.js +79 -0
- package/lib/interactions/question-renderer.js.map +1 -0
- package/lib/interactions/question-state.d.ts +106 -0
- package/lib/interactions/question-state.d.ts.map +1 -0
- package/lib/interactions/question-state.js +99 -0
- package/lib/interactions/question-state.js.map +1 -0
- package/lib/interactions/question-text-answer.d.ts +37 -0
- package/lib/interactions/question-text-answer.d.ts.map +1 -0
- package/lib/interactions/question-text-answer.js +70 -0
- package/lib/interactions/question-text-answer.js.map +1 -0
- package/lib/interactions/question-waterfall-backend.d.ts +42 -0
- package/lib/interactions/question-waterfall-backend.d.ts.map +1 -0
- package/lib/interactions/question-waterfall-backend.js +162 -0
- package/lib/interactions/question-waterfall-backend.js.map +1 -0
- package/lib/lifecycle.d.ts +36 -0
- package/lib/lifecycle.d.ts.map +1 -0
- package/lib/lifecycle.js +303 -0
- package/lib/lifecycle.js.map +1 -0
- package/lib/loggable-error.d.ts +10 -0
- package/lib/loggable-error.d.ts.map +1 -0
- package/lib/loggable-error.js +32 -0
- package/lib/loggable-error.js.map +1 -0
- package/lib/message-converter.d.ts +64 -0
- package/lib/message-converter.d.ts.map +1 -0
- package/lib/message-converter.js +191 -0
- package/lib/message-converter.js.map +1 -0
- package/lib/model-selection.d.ts +54 -0
- package/lib/model-selection.d.ts.map +1 -0
- package/lib/model-selection.js +116 -0
- package/lib/model-selection.js.map +1 -0
- package/lib/outbox/binding-resolver.d.ts +23 -0
- package/lib/outbox/binding-resolver.d.ts.map +1 -0
- package/lib/outbox/binding-resolver.js +38 -0
- package/lib/outbox/binding-resolver.js.map +1 -0
- package/lib/outbox/capabilities.d.ts +48 -0
- package/lib/outbox/capabilities.d.ts.map +1 -0
- package/lib/outbox/capabilities.js +35 -0
- package/lib/outbox/capabilities.js.map +1 -0
- package/lib/outbox/index.d.ts +14 -0
- package/lib/outbox/index.d.ts.map +1 -0
- package/lib/outbox/index.js +14 -0
- package/lib/outbox/index.js.map +1 -0
- package/lib/outbox/service.d.ts +48 -0
- package/lib/outbox/service.d.ts.map +1 -0
- package/lib/outbox/service.js +74 -0
- package/lib/outbox/service.js.map +1 -0
- package/lib/outbox/target.d.ts +17 -0
- package/lib/outbox/target.d.ts.map +1 -0
- package/lib/outbox/target.js +15 -0
- package/lib/outbox/target.js.map +1 -0
- package/lib/outbox/tool-send.d.ts +19 -0
- package/lib/outbox/tool-send.d.ts.map +1 -0
- package/lib/outbox/tool-send.js +77 -0
- package/lib/outbox/tool-send.js.map +1 -0
- package/lib/outbox/types.d.ts +42 -0
- package/lib/outbox/types.d.ts.map +1 -0
- package/lib/outbox/types.js +30 -0
- package/lib/outbox/types.js.map +1 -0
- package/lib/plugin.d.ts +27 -0
- package/lib/plugin.d.ts.map +1 -0
- package/lib/plugin.js +47 -0
- package/lib/plugin.js.map +1 -0
- package/lib/reply-context-store.d.ts +69 -0
- package/lib/reply-context-store.d.ts.map +1 -0
- package/lib/reply-context-store.js +75 -0
- package/lib/reply-context-store.js.map +1 -0
- package/lib/reply-router.d.ts +138 -0
- package/lib/reply-router.d.ts.map +1 -0
- package/lib/reply-router.js +674 -0
- package/lib/reply-router.js.map +1 -0
- package/lib/session-router.d.ts +49 -0
- package/lib/session-router.d.ts.map +1 -0
- package/lib/session-router.js +45 -0
- package/lib/session-router.js.map +1 -0
- package/lib/workspace-resolver.d.ts +67 -0
- package/lib/workspace-resolver.d.ts.map +1 -0
- package/lib/workspace-resolver.js +102 -0
- package/lib/workspace-resolver.js.map +1 -0
- package/package.json +103 -0
package/lib/lifecycle.js
ADDED
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
import { SessionId } from '@deepseek-ai/dsh-session';
|
|
2
|
+
import { createBindingStore } from './binding-store.js';
|
|
3
|
+
import { AgentManager, HarnessAgentGateway, resolvePersistedInspection } from './agent-manager.js';
|
|
4
|
+
import { confirmBindTarget, resolveBindTarget } from './bind-support.js';
|
|
5
|
+
import { AgentRouter } from './agent-router.js';
|
|
6
|
+
import { ReplyRouter } from './reply-router.js';
|
|
7
|
+
import { ChannelHarnessBridge } from './bridge.js';
|
|
8
|
+
import { ChannelOutboxService } from './outbox/service.js';
|
|
9
|
+
import { OutboxError } from './outbox/types.js';
|
|
10
|
+
import { HarnessChannelWorkspaceResolver } from './workspace-resolver.js';
|
|
11
|
+
import { ReplyContextStore } from './reply-context-store.js';
|
|
12
|
+
import { installDebugConsoleExporter } from './debug-logger.js';
|
|
13
|
+
import { StoredChannelAccessPolicyResolver } from './access/resolver.js';
|
|
14
|
+
import { liveAttachmentProvider } from './file-provider.js';
|
|
15
|
+
import { createQuestionInteraction } from './interactions/question-backend.js';
|
|
16
|
+
export function startBridge(ctx, config, resolvePersistence) {
|
|
17
|
+
installDebugConsoleExporter(ctx);
|
|
18
|
+
const logger = ctx.logger('channel-harness');
|
|
19
|
+
const channels = ctx.get('channels');
|
|
20
|
+
const bindingStore = createBindingStore(config.bindingStore);
|
|
21
|
+
const agentGateway = new HarnessAgentGateway(ctx, resolvePersistence);
|
|
22
|
+
const agentManager = new AgentManager(agentGateway, logger, config.maxConcurrency);
|
|
23
|
+
const agentRouter = new AgentRouter(config);
|
|
24
|
+
const workspaceResolver = new HarnessChannelWorkspaceResolver(ctx, config.workspace, logger);
|
|
25
|
+
const getAdapter = (channelId) => channels.get(channelId);
|
|
26
|
+
const replyContexts = new ReplyContextStore();
|
|
27
|
+
// Question answerer composition: the channel composes ONE PREPENDED answerer
|
|
28
|
+
// on the official `user-questions/request` waterfall — headless it is
|
|
29
|
+
// typically the only answerer; in the Web profile it must win over the
|
|
30
|
+
// official Remote/Web answerer (which registers at boot, before this bridge)
|
|
31
|
+
// or every channel-bound ask would be swallowed by the browser. A declined
|
|
32
|
+
// presentation still delegates via `next()`. Everything lives in
|
|
33
|
+
// interactions/question-backend.ts.
|
|
34
|
+
const questionPresenter = config.userQuestions.enabled
|
|
35
|
+
? createQuestionInteraction({
|
|
36
|
+
ctx,
|
|
37
|
+
getUserQuestions: () => ctx.get('userQuestions'),
|
|
38
|
+
agentManager,
|
|
39
|
+
replyContexts,
|
|
40
|
+
getAdapter,
|
|
41
|
+
logger,
|
|
42
|
+
timeoutMs: config.userQuestions.timeoutMs,
|
|
43
|
+
})
|
|
44
|
+
: undefined;
|
|
45
|
+
questionPresenter?.start();
|
|
46
|
+
// Optional attachment service -> real image path (WX5). Resolved LIVE at
|
|
47
|
+
// every use, exactly like `sessionPersistence` above: the profile entry group
|
|
48
|
+
// creates every row concurrently (`Promise.allSettled`), so a startup
|
|
49
|
+
// snapshot can precede the service's own fiber. A deployment without an
|
|
50
|
+
// attachment backend keeps today's fallback — the hook throws, `imageBlock`
|
|
51
|
+
// catches, and the converter emits the deterministic text placeholder.
|
|
52
|
+
const saveImage = async (input) => {
|
|
53
|
+
const attachments = ctx.get('attachments');
|
|
54
|
+
if (!attachments) {
|
|
55
|
+
throw new Error('the Harness attachments service is not mounted');
|
|
56
|
+
}
|
|
57
|
+
return attachments.saveImage(input);
|
|
58
|
+
};
|
|
59
|
+
// Optional generic-file extension. Harness currently has a native image
|
|
60
|
+
// service but no generic FileBlock/FileAttachment surface, so deployments
|
|
61
|
+
// may provide this separately without coupling document parsers to the
|
|
62
|
+
// bridge. The provider is resolved LIVE per call (see
|
|
63
|
+
// `liveAttachmentProvider`): the bundle's `channels-files` row loads
|
|
64
|
+
// concurrently with this plugin, and deleting that row must stay supported.
|
|
65
|
+
const resolveFileProvider = () => ctx.get('channelFiles');
|
|
66
|
+
const fileProvider = liveAttachmentProvider(resolveFileProvider);
|
|
67
|
+
// Best-effort typing indicator wiring: a typing API failure must NEVER break
|
|
68
|
+
// the inbound/outbound flow, so every call is fire-and-forget with a swallow.
|
|
69
|
+
const startTyping = (sessionId, context) => {
|
|
70
|
+
const binding = agentManager.bindingFor(sessionId);
|
|
71
|
+
if (!binding)
|
|
72
|
+
return;
|
|
73
|
+
const adapter = getAdapter(binding.channelId);
|
|
74
|
+
if (adapter?.startTypingForTarget && context) {
|
|
75
|
+
void adapter.startTypingForTarget(typingTarget(binding, context)).catch(() => { });
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (adapter?.startTyping)
|
|
79
|
+
void adapter.startTyping(binding.conversationId).catch(() => { });
|
|
80
|
+
};
|
|
81
|
+
const stopTyping = (sessionId, context) => {
|
|
82
|
+
const binding = agentManager.bindingFor(sessionId);
|
|
83
|
+
if (!binding)
|
|
84
|
+
return;
|
|
85
|
+
const adapter = getAdapter(binding.channelId);
|
|
86
|
+
if (adapter?.stopTypingForTarget && context) {
|
|
87
|
+
void adapter.stopTypingForTarget(typingTarget(binding, context)).catch(() => { });
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (adapter?.stopTyping)
|
|
91
|
+
void adapter.stopTyping(binding.conversationId).catch(() => { });
|
|
92
|
+
};
|
|
93
|
+
ctx.on('agent/inbox/claimed', ({ agent, message, turn }) => {
|
|
94
|
+
const context = replyContexts.claim({
|
|
95
|
+
sessionId: String(agent.session.id),
|
|
96
|
+
messageId: String(message.id),
|
|
97
|
+
turn,
|
|
98
|
+
});
|
|
99
|
+
startTyping(String(agent.session.id), context);
|
|
100
|
+
});
|
|
101
|
+
ctx.on('agent/inbox/discarded', ({ message }) => {
|
|
102
|
+
// Resolve the session id BEFORE dropping the pending entry so we can
|
|
103
|
+
// cancel any typing the (never-claimed) inbound would have triggered.
|
|
104
|
+
const messageId = String(message.id);
|
|
105
|
+
const sessionId = replyContexts.pendingSessionId(messageId);
|
|
106
|
+
const context = replyContexts.pendingContext(messageId);
|
|
107
|
+
replyContexts.discard(messageId);
|
|
108
|
+
if (sessionId)
|
|
109
|
+
stopTyping(sessionId, context);
|
|
110
|
+
});
|
|
111
|
+
const replyRouter = new ReplyRouter({
|
|
112
|
+
config: config.reply,
|
|
113
|
+
getAdapter,
|
|
114
|
+
getBinding: (sessionId) => agentManager.bindingFor(sessionId),
|
|
115
|
+
replyContexts,
|
|
116
|
+
logger,
|
|
117
|
+
});
|
|
118
|
+
const stopListening = replyRouter.attach(ctx);
|
|
119
|
+
// Deferred bridge reference: `commandDeps` routes the /new command backed by
|
|
120
|
+
// the bridge back to the bridge's own fresh-session bootstrap. The arrow only
|
|
121
|
+
// calls `bridge.startNewSession` at runtime (when a command actually runs), by
|
|
122
|
+
// which point the bridge is assigned (definite-assignment assertion below).
|
|
123
|
+
// Durable outbox: the proactive send path. The
|
|
124
|
+
// binding authority is the DURABLE store (never AgentManager's hint cache),
|
|
125
|
+
// and the adapter lookup + asset store are the same ones the reply pipeline
|
|
126
|
+
// uses. Optional: absent -> the send_channel_message tool is not installed.
|
|
127
|
+
const outbox = new ChannelOutboxService({
|
|
128
|
+
bindingStore,
|
|
129
|
+
getAdapter,
|
|
130
|
+
// Live again: the resolver runs per send, so a provider mounted after the
|
|
131
|
+
// bridge started (or an HMR replacement) is picked up instead of leaving
|
|
132
|
+
// the outbox permanently without an attachment resolver (issue #7).
|
|
133
|
+
attachmentResolver: async (attachmentId, sessionId) => {
|
|
134
|
+
const provider = resolveFileProvider();
|
|
135
|
+
if (!provider) {
|
|
136
|
+
throw new OutboxError('OUTBOX_CAPABILITY_UNAVAILABLE', 'outbound attachments require a private asset store that is not configured', { sessionId });
|
|
137
|
+
}
|
|
138
|
+
return provider.resolveAttachment(attachmentId, sessionId);
|
|
139
|
+
},
|
|
140
|
+
logger,
|
|
141
|
+
});
|
|
142
|
+
let bridge;
|
|
143
|
+
const commandDeps = {
|
|
144
|
+
startNewSession: (agent) => bridge.startNewSession(agent),
|
|
145
|
+
// /mirror (issue #5): the toggle state lives on the durable binding.
|
|
146
|
+
mirror: {
|
|
147
|
+
get: async (agent) => (await bindingStore.findBySessionId(String(agent.id)))?.mirror === true,
|
|
148
|
+
set: async (agent, on) => {
|
|
149
|
+
const binding = await bindingStore.findBySessionId(String(agent.id));
|
|
150
|
+
if (!binding)
|
|
151
|
+
throw new Error(`no session binding for '${String(agent.id)}'`);
|
|
152
|
+
await bindingStore.put({ ...binding, mirror: on, updatedAt: Date.now() });
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
// /bind (issue #6): fail-closed rebind support over the durable bindings
|
|
156
|
+
// and the persisted session universe.
|
|
157
|
+
bind: {
|
|
158
|
+
resolve: (agent, query) => resolveBindTarget(agentGateway, bindingStore, agent, query),
|
|
159
|
+
confirm: (agent, sessionId) => confirmBindTarget(agentGateway, bindingStore, agent, sessionId),
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
// Fail-closed Access Gate: production ALWAYS resolves the
|
|
163
|
+
// policy from the shared ChannelStorage and logs decisions on the
|
|
164
|
+
// `channel-access` namespace. Reads once per inbound — no policy caching (§16).
|
|
165
|
+
const accessResolver = new StoredChannelAccessPolicyResolver(() => channels.resources.storage);
|
|
166
|
+
const accessLogger = ctx.logger('channel-access');
|
|
167
|
+
bridge = new ChannelHarnessBridge({
|
|
168
|
+
config,
|
|
169
|
+
bindingStore,
|
|
170
|
+
agentManager,
|
|
171
|
+
agentRouter,
|
|
172
|
+
getAdapter,
|
|
173
|
+
replyContexts,
|
|
174
|
+
logger,
|
|
175
|
+
accessResolver,
|
|
176
|
+
accessLogger,
|
|
177
|
+
saveImage,
|
|
178
|
+
fileProvider,
|
|
179
|
+
ctx,
|
|
180
|
+
commandDeps,
|
|
181
|
+
workspaceResolver,
|
|
182
|
+
outbox,
|
|
183
|
+
questionPresenter,
|
|
184
|
+
});
|
|
185
|
+
const stopInbound = channels.on((event) => bridge.handleChannelEvent(event));
|
|
186
|
+
let disposed = false;
|
|
187
|
+
async function dispose() {
|
|
188
|
+
if (disposed)
|
|
189
|
+
return;
|
|
190
|
+
disposed = true;
|
|
191
|
+
// 1. Stop new inbound (adapter events no longer reach the bridge).
|
|
192
|
+
stopInbound();
|
|
193
|
+
// Cancel channel-owned question waits before draining the blocked Agent.
|
|
194
|
+
await questionPresenter?.stop();
|
|
195
|
+
// Release this bridge's Agent-scoped commands before a replacement bridge
|
|
196
|
+
// can borrow the same live agents and install fresh handlers.
|
|
197
|
+
await bridge.disposeCommandSetups();
|
|
198
|
+
// 2. Drain active turns with a bounded wait (the session/event listener is
|
|
199
|
+
// still attached here, but we do NOT depend on it for final-reply
|
|
200
|
+
// correctness).
|
|
201
|
+
await drainActiveTurns(agentManager, replyRouter, logger, config.drainTimeoutMs);
|
|
202
|
+
// 3. RECONCILE replies from the Session durable log (final text delivery
|
|
203
|
+
// does NOT rely on the listener still being attached).
|
|
204
|
+
await reconcileReplies(ctx, resolvePersistence, agentManager, replyRouter, logger);
|
|
205
|
+
// 4. Finalize any replies still marked active whose turn/end never arrived.
|
|
206
|
+
await replyRouter.flushAll();
|
|
207
|
+
// 5. Stop listening to session/event.
|
|
208
|
+
stopListening();
|
|
209
|
+
// 6. Dispose owned agent handles (each exactly once).
|
|
210
|
+
await agentManager.disposeAll();
|
|
211
|
+
// 7. Dispose the reply router (clear timers).
|
|
212
|
+
replyRouter.dispose();
|
|
213
|
+
}
|
|
214
|
+
return {
|
|
215
|
+
dispose,
|
|
216
|
+
handleChannelEvent: (event) => bridge.handleChannelEvent(event),
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
function typingTarget(binding, context) {
|
|
220
|
+
return {
|
|
221
|
+
channelId: binding.channelId,
|
|
222
|
+
accountId: binding.accountId,
|
|
223
|
+
conversationId: binding.conversationId,
|
|
224
|
+
threadId: binding.threadId,
|
|
225
|
+
conversationType: context.conversationType,
|
|
226
|
+
replyToMessageId: context.replyToMessageId,
|
|
227
|
+
raw: context.raw,
|
|
228
|
+
runId: context.runId,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
async function drainActiveTurns(agentManager, replyRouter, logger, drainTimeoutMs) {
|
|
232
|
+
const sessionIds = replyRouter.activeSessions();
|
|
233
|
+
if (sessionIds.length === 0)
|
|
234
|
+
return;
|
|
235
|
+
await Promise.all(sessionIds.map(async (sessionId) => {
|
|
236
|
+
const ref = agentManager.refFor(sessionId);
|
|
237
|
+
if (!ref)
|
|
238
|
+
return;
|
|
239
|
+
const timedOut = await withTimeout(ref.whenIdle(), drainTimeoutMs);
|
|
240
|
+
if (timedOut) {
|
|
241
|
+
logger.warn(`[channel-harness] drain timed out for session '${sessionId}'`);
|
|
242
|
+
}
|
|
243
|
+
}));
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Reconcile every active session's reply from the durable log. Reads the live
|
|
247
|
+
* Session when it is still in the store; otherwise inspects persistence
|
|
248
|
+
* (resolved LIVE at dispose time — the currently mounted backend, whatever
|
|
249
|
+
* the bridge started with). Skips gracefully when neither is available.
|
|
250
|
+
* Final-reply correctness comes from the Session log, not from the
|
|
251
|
+
* still-attached event listener.
|
|
252
|
+
*/
|
|
253
|
+
async function reconcileReplies(ctx, resolvePersistence, agentManager, replyRouter, logger) {
|
|
254
|
+
const sessionIds = agentManager.activeSessions();
|
|
255
|
+
if (sessionIds.length === 0)
|
|
256
|
+
return;
|
|
257
|
+
const sessions = ctx.get('sessions');
|
|
258
|
+
const persistence = resolvePersistence?.();
|
|
259
|
+
for (const sessionId of sessionIds) {
|
|
260
|
+
try {
|
|
261
|
+
const live = sessions?.get(SessionId(sessionId));
|
|
262
|
+
if (live) {
|
|
263
|
+
await replyRouter.reconcileSession({ id: sessionId, events: live.snapshotEvents() });
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
if (persistence) {
|
|
267
|
+
// V3 persistence inspection uses the public read handle.
|
|
268
|
+
const inspection = await resolvePersistedInspection(persistence, SessionId(sessionId));
|
|
269
|
+
if (inspection) {
|
|
270
|
+
await replyRouter.reconcileSession({ id: sessionId, events: inspection.events });
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
// else: no live session and no persistence — skip gracefully.
|
|
274
|
+
}
|
|
275
|
+
catch (error) {
|
|
276
|
+
logger.warn(`[channel-harness] reconcile failed for session '${sessionId}'`, error);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
/** Resolve true when the timeout elapses before the promise settles. */
|
|
281
|
+
function withTimeout(promise, ms) {
|
|
282
|
+
return new Promise((resolve) => {
|
|
283
|
+
let settled = false;
|
|
284
|
+
const timer = setTimeout(() => {
|
|
285
|
+
if (!settled)
|
|
286
|
+
resolve(true);
|
|
287
|
+
}, ms);
|
|
288
|
+
promise.then(() => {
|
|
289
|
+
if (settled)
|
|
290
|
+
return;
|
|
291
|
+
settled = true;
|
|
292
|
+
clearTimeout(timer);
|
|
293
|
+
resolve(false);
|
|
294
|
+
}, () => {
|
|
295
|
+
if (settled)
|
|
296
|
+
return;
|
|
297
|
+
settled = true;
|
|
298
|
+
clearTimeout(timer);
|
|
299
|
+
resolve(false);
|
|
300
|
+
});
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
//# sourceMappingURL=lifecycle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lifecycle.js","sourceRoot":"","sources":["../src/lifecycle.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** JSON-safe shape for errors passed through logger exporters. */
|
|
2
|
+
export interface LoggableError {
|
|
3
|
+
name: string;
|
|
4
|
+
message: string;
|
|
5
|
+
stack?: string;
|
|
6
|
+
cause?: LoggableError;
|
|
7
|
+
}
|
|
8
|
+
/** Preserve useful Error fields without relying on non-enumerable properties. */
|
|
9
|
+
export declare function toLoggableError(error: unknown): LoggableError;
|
|
10
|
+
//# sourceMappingURL=loggable-error.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loggable-error.d.ts","sourceRoot":"","sources":["../src/loggable-error.ts"],"names":[],"mappings":"AAAA,kEAAkE;AAClE,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,aAAa,CAAC;CACvB;AAED,iFAAiF;AACjF,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,aAAa,CAE7D"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/** Preserve useful Error fields without relying on non-enumerable properties. */
|
|
2
|
+
export function toLoggableError(error) {
|
|
3
|
+
return normalizeError(error, new Set());
|
|
4
|
+
}
|
|
5
|
+
function normalizeError(error, seen) {
|
|
6
|
+
if (!(error instanceof Error)) {
|
|
7
|
+
return { name: 'NonErrorThrown', message: formatUnknown(error) };
|
|
8
|
+
}
|
|
9
|
+
if (seen.has(error)) {
|
|
10
|
+
return { name: error.name, message: `${error.message} (circular cause)` };
|
|
11
|
+
}
|
|
12
|
+
seen.add(error);
|
|
13
|
+
const result = {
|
|
14
|
+
name: error.name,
|
|
15
|
+
message: error.message,
|
|
16
|
+
stack: error.stack,
|
|
17
|
+
};
|
|
18
|
+
if (error.cause !== undefined)
|
|
19
|
+
result.cause = normalizeError(error.cause, seen);
|
|
20
|
+
return result;
|
|
21
|
+
}
|
|
22
|
+
function formatUnknown(value) {
|
|
23
|
+
if (typeof value === 'string')
|
|
24
|
+
return value;
|
|
25
|
+
try {
|
|
26
|
+
return JSON.stringify(value) ?? String(value);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return String(value);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=loggable-error.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loggable-error.js","sourceRoot":"","sources":["../src/loggable-error.ts"],"names":[],"mappings":"AAQA,iFAAiF;AACjF,MAAM,UAAU,eAAe,CAAC,KAAc;IAC5C,OAAO,cAAc,CAAC,KAAK,EAAE,IAAI,GAAG,EAAS,CAAC,CAAC;AACjD,CAAC;AAED,SAAS,cAAc,CAAC,KAAc,EAAE,IAAgB;IACtD,IAAI,CAAC,CAAC,KAAK,YAAY,KAAK,CAAC,EAAE,CAAC;QAC9B,OAAO,EAAE,IAAI,EAAE,gBAAgB,EAAE,OAAO,EAAE,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC;IACnE,CAAC;IAED,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;QACpB,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,KAAK,CAAC,OAAO,mBAAmB,EAAE,CAAC;IAC5E,CAAC;IAED,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IAChB,MAAM,MAAM,GAAkB;QAC5B,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,KAAK,EAAE,KAAK,CAAC,KAAK;KACnB,CAAC;IACF,IAAI,KAAK,CAAC,KAAK,KAAK,SAAS;QAAE,MAAM,CAAC,KAAK,GAAG,cAAc,CAAC,KAAK,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IAChF,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,aAAa,CAAC,KAAc;IACnC,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC5C,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC;IAChD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;IACvB,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inbound conversion: `MessageReceived` -> a Harness `UserMessage`.
|
|
3
|
+
*
|
|
4
|
+
* Content is structured-text by default (red line 6): platform parts without
|
|
5
|
+
* downloadable local bytes are mapped to plain-text blocks — images/files/audio/video
|
|
6
|
+
* become `[type: ...]` placeholders instead of raw platform JSON. Conversation
|
|
7
|
+
* metadata (channel / sender / message id) is folded into a configurable text
|
|
8
|
+
* prefix so the model can still tell where a message came from.
|
|
9
|
+
*
|
|
10
|
+
* WX5 attachment path: when an image part carries `localData` (the adapter
|
|
11
|
+
* already downloaded + decrypted the bytes), and a `saveImage` hook is supplied,
|
|
12
|
+
* the converter registers the bytes with the Harness attachment service and emits
|
|
13
|
+
* a real `ImageBlock` (`{ type: 'image', attachment: ImageAttachmentRef }`) so
|
|
14
|
+
* the model receives a genuine multimodal image instead of the `[image]`
|
|
15
|
+
* placeholder. The hook is optional; without it (or on attachment failure) the
|
|
16
|
+
* converter falls back to the text placeholder so message delivery never breaks.
|
|
17
|
+
*/
|
|
18
|
+
import { type ContentBlock, type UserMessage } from '@deepseek-ai/dsh-llm';
|
|
19
|
+
import type { ImageAttachmentRef, SaveImageAttachment } from '@deepseek-ai/dsh-attachment';
|
|
20
|
+
import { type AudioPart, type FilePart, type MessagePart, type MessageReceived, type VideoPart } from '@krischoichoi/channel-core';
|
|
21
|
+
import type { ChannelAttachmentDescriptor } from './file-provider.js';
|
|
22
|
+
/** Type-level alias: the bridge always produces Harness user messages. */
|
|
23
|
+
export type UserMessageLike = UserMessage;
|
|
24
|
+
/**
|
|
25
|
+
* Bridge-provided seam to commit one image to the Harness attachment service.
|
|
26
|
+
* Mirrors `AttachmentStore.saveImage` so the converter stays decoupled from the
|
|
27
|
+
* concrete Cordis service (resolved at the use site and injected). Returns the
|
|
28
|
+
* durable reference used to build the `ImageBlock`.
|
|
29
|
+
*/
|
|
30
|
+
export type SaveImageHook = (input: SaveImageAttachment) => Promise<ImageAttachmentRef>;
|
|
31
|
+
/** A binary (file / audio / video) part carrying local bytes. */
|
|
32
|
+
export type StoredBinaryPart = FilePart | AudioPart | VideoPart;
|
|
33
|
+
/**
|
|
34
|
+
* Optional bridge-provided seam that persists a file/audio/video part with
|
|
35
|
+
* `localData` into the private channel asset store and returns a compact
|
|
36
|
+
* model descriptor (plan \u00a750/\u00a754). Mirrors `SaveImageHook`: the
|
|
37
|
+
* converter stays decoupled from the concrete store (injected at the use
|
|
38
|
+
* site). Absent -> the part falls back to its `[file: name]` placeholder;
|
|
39
|
+
* a thrown/undefined result also falls back, never breaking delivery.
|
|
40
|
+
*/
|
|
41
|
+
export type FileStoreHook = (part: StoredBinaryPart) => Promise<ChannelAttachmentDescriptor | undefined>;
|
|
42
|
+
export interface MessageConvertOptions {
|
|
43
|
+
/** Whether to prepend the `[channel=.. sender=.. message=..]` prefix. Defaults to false. */
|
|
44
|
+
includeMetadataPrefix?: boolean;
|
|
45
|
+
/** Optional image-commit hook enabling the real attachment path. */
|
|
46
|
+
saveImage?: SaveImageHook;
|
|
47
|
+
/** Optional file/audio/video store hook enabling the private asset path. */
|
|
48
|
+
fileStore?: FileStoreHook;
|
|
49
|
+
}
|
|
50
|
+
/** Convert a channel message-received event into a model-facing user message. */
|
|
51
|
+
export declare function toHarnessUserMessage(event: MessageReceived, options?: MessageConvertOptions): Promise<UserMessageLike>;
|
|
52
|
+
/**
|
|
53
|
+
* Map structured parts to a single text line. Never embeds raw payloads.
|
|
54
|
+
* Image parts with local bytes and a saveImage hook are surfaced via
|
|
55
|
+
* `onImage` instead of being folded into text; they never contribute a text
|
|
56
|
+
* placeholder (unless the attachment path is unavailable, in which case they
|
|
57
|
+
* fall back to the ordinary `[image]` placeholder).
|
|
58
|
+
*/
|
|
59
|
+
export declare function partsToText(parts: readonly MessagePart[], options?: {
|
|
60
|
+
saveImage?: SaveImageHook;
|
|
61
|
+
fileStore?: FileStoreHook;
|
|
62
|
+
onImage?: (block: ContentBlock) => void;
|
|
63
|
+
}): Promise<string>;
|
|
64
|
+
//# sourceMappingURL=message-converter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"message-converter.d.ts","sourceRoot":"","sources":["../src/message-converter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EAEL,KAAK,YAAY,EAEjB,KAAK,WAAW,EACjB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAE,kBAAkB,EAAkB,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAC3G,OAAO,EAGL,KAAK,SAAS,EACd,KAAK,QAAQ,EAEb,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,KAAK,SAAS,EACf,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,oBAAoB,CAAC;AAEtE,0EAA0E;AAC1E,MAAM,MAAM,eAAe,GAAG,WAAW,CAAC;AAE1C;;;;;GAKG;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,KAAK,EAAE,mBAAmB,KAAK,OAAO,CAAC,kBAAkB,CAAC,CAAC;AAExF,iEAAiE;AACjE,MAAM,MAAM,gBAAgB,GAAG,QAAQ,GAAG,SAAS,GAAG,SAAS,CAAC;AAEhE;;;;;;;GAOG;AACH,MAAM,MAAM,aAAa,GAAG,CAC1B,IAAI,EAAE,gBAAgB,KACnB,OAAO,CAAC,2BAA2B,GAAG,SAAS,CAAC,CAAC;AAEtD,MAAM,WAAW,qBAAqB;IACpC,4FAA4F;IAC5F,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,oEAAoE;IACpE,SAAS,CAAC,EAAE,aAAa,CAAC;IAC1B,4EAA4E;IAC5E,SAAS,CAAC,EAAE,aAAa,CAAC;CAC3B;AAED,iFAAiF;AACjF,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,eAAe,EACtB,OAAO,GAAE,qBAA0B,GAClC,OAAO,CAAC,eAAe,CAAC,CAkB1B;AA0BD;;;;;;GAMG;AACH,wBAAsB,WAAW,CAC/B,KAAK,EAAE,SAAS,WAAW,EAAE,EAC7B,OAAO,GAAE;IACP,SAAS,CAAC,EAAE,aAAa,CAAC;IAC1B,SAAS,CAAC,EAAE,aAAa,CAAC;IAC1B,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC;CACpC,GACL,OAAO,CAAC,MAAM,CAAC,CA8CjB"}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inbound conversion: `MessageReceived` -> a Harness `UserMessage`.
|
|
3
|
+
*
|
|
4
|
+
* Content is structured-text by default (red line 6): platform parts without
|
|
5
|
+
* downloadable local bytes are mapped to plain-text blocks — images/files/audio/video
|
|
6
|
+
* become `[type: ...]` placeholders instead of raw platform JSON. Conversation
|
|
7
|
+
* metadata (channel / sender / message id) is folded into a configurable text
|
|
8
|
+
* prefix so the model can still tell where a message came from.
|
|
9
|
+
*
|
|
10
|
+
* WX5 attachment path: when an image part carries `localData` (the adapter
|
|
11
|
+
* already downloaded + decrypted the bytes), and a `saveImage` hook is supplied,
|
|
12
|
+
* the converter registers the bytes with the Harness attachment service and emits
|
|
13
|
+
* a real `ImageBlock` (`{ type: 'image', attachment: ImageAttachmentRef }`) so
|
|
14
|
+
* the model receives a genuine multimodal image instead of the `[image]`
|
|
15
|
+
* placeholder. The hook is optional; without it (or on attachment failure) the
|
|
16
|
+
* converter falls back to the text placeholder so message delivery never breaks.
|
|
17
|
+
*/
|
|
18
|
+
import { createUserMessage, } from '@deepseek-ai/dsh-llm';
|
|
19
|
+
import { mimeHintFromFilename, normalizeMimeHint, } from '@krischoichoi/channel-core';
|
|
20
|
+
/** Convert a channel message-received event into a model-facing user message. */
|
|
21
|
+
export async function toHarnessUserMessage(event, options = {}) {
|
|
22
|
+
const blocks = [];
|
|
23
|
+
const includePrefix = options.includeMetadataPrefix ?? false;
|
|
24
|
+
if (includePrefix) {
|
|
25
|
+
const prefix = metadataPrefix(event);
|
|
26
|
+
if (prefix)
|
|
27
|
+
blocks.push({ type: 'text', text: prefix });
|
|
28
|
+
}
|
|
29
|
+
blocks.push(...await partsToBlocks(event.message.content, options));
|
|
30
|
+
if (blocks.length === 0)
|
|
31
|
+
blocks.push({ type: 'text', text: '' });
|
|
32
|
+
return createUserMessage({
|
|
33
|
+
content: blocks,
|
|
34
|
+
// Channel inbound messages are authored by the remote human. Marking
|
|
35
|
+
// them as plugin context makes Harness skip user-only behavior such as
|
|
36
|
+
// deterministic and LLM-backed session title generation.
|
|
37
|
+
source: { kind: 'user' },
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
/** Convert platform parts to Harness blocks without changing their order. */
|
|
41
|
+
async function partsToBlocks(parts, options) {
|
|
42
|
+
const blocks = [];
|
|
43
|
+
for (const part of parts) {
|
|
44
|
+
if (part.type === 'image') {
|
|
45
|
+
const block = await imageBlock(part, options.saveImage);
|
|
46
|
+
if (block) {
|
|
47
|
+
blocks.push(block);
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const text = await partsToText([part], { fileStore: options.fileStore });
|
|
52
|
+
if (text)
|
|
53
|
+
blocks.push({ type: 'text', text });
|
|
54
|
+
}
|
|
55
|
+
return blocks;
|
|
56
|
+
}
|
|
57
|
+
function metadataPrefix(event) {
|
|
58
|
+
return `[channel=${event.channel} sender=${event.sender.id} message=${event.message.id}] `;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Map structured parts to a single text line. Never embeds raw payloads.
|
|
62
|
+
* Image parts with local bytes and a saveImage hook are surfaced via
|
|
63
|
+
* `onImage` instead of being folded into text; they never contribute a text
|
|
64
|
+
* placeholder (unless the attachment path is unavailable, in which case they
|
|
65
|
+
* fall back to the ordinary `[image]` placeholder).
|
|
66
|
+
*/
|
|
67
|
+
export async function partsToText(parts, options = {}) {
|
|
68
|
+
const segments = [];
|
|
69
|
+
for (const part of parts) {
|
|
70
|
+
switch (part.type) {
|
|
71
|
+
case 'text':
|
|
72
|
+
segments.push(part.text);
|
|
73
|
+
break;
|
|
74
|
+
case 'image': {
|
|
75
|
+
const block = await imageBlock(part, options.saveImage);
|
|
76
|
+
if (block) {
|
|
77
|
+
options.onImage?.(block);
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
segments.push(part.alt ? `[image: ${part.alt}]` : part.url ? `[image: ${part.url}]` : '[image]');
|
|
81
|
+
}
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
case 'file':
|
|
85
|
+
segments.push((await storedDescriptor(part, options.fileStore)) ??
|
|
86
|
+
(part.name ? `[file: ${part.name}]` : part.url ? `[file: ${part.url}]` : '[file]'));
|
|
87
|
+
break;
|
|
88
|
+
case 'audio':
|
|
89
|
+
segments.push((await storedDescriptor(part, options.fileStore)) ??
|
|
90
|
+
(part.durationMs !== undefined ? `[audio: ${part.durationMs}ms]` : '[audio]'));
|
|
91
|
+
break;
|
|
92
|
+
case 'video':
|
|
93
|
+
segments.push((await storedDescriptor(part, options.fileStore)) ??
|
|
94
|
+
(part.durationMs !== undefined ? `[video: ${part.durationMs}ms]` : '[video]'));
|
|
95
|
+
break;
|
|
96
|
+
case 'location':
|
|
97
|
+
segments.push(`[location: ${part.latitude},${part.longitude}]`);
|
|
98
|
+
break;
|
|
99
|
+
case 'card':
|
|
100
|
+
segments.push(`[card: ${part.kind}]`);
|
|
101
|
+
break;
|
|
102
|
+
case 'unsupported':
|
|
103
|
+
segments.push('[unsupported content]');
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return segments.join('');
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Produce an image content block when the part carries local bytes and a
|
|
111
|
+
* saveImage hook commits them. A media type is inferred from `mimeType`
|
|
112
|
+
* (or the data URI / URL extension) and accepted only when it maps to a
|
|
113
|
+
* supported raster type; otherwise the bytes are committed as-is and the
|
|
114
|
+
* attachment service performs final validation. Any failure returns undefined
|
|
115
|
+
* so the caller falls back to the text placeholder (never breaks delivery).
|
|
116
|
+
*/
|
|
117
|
+
async function imageBlock(part, saveImage) {
|
|
118
|
+
if (!saveImage || !part.localData || part.localData.byteLength === 0)
|
|
119
|
+
return undefined;
|
|
120
|
+
const mediaType = inferImageMediaType(part);
|
|
121
|
+
if (!mediaType)
|
|
122
|
+
return undefined;
|
|
123
|
+
try {
|
|
124
|
+
const ref = await saveImage({
|
|
125
|
+
data: part.localData,
|
|
126
|
+
mediaType,
|
|
127
|
+
name: part.alt,
|
|
128
|
+
});
|
|
129
|
+
return { type: 'image', attachment: ref };
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
return undefined;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/** Infer a raster media type from a part's mimeType / url / dataUri. */
|
|
136
|
+
function inferImageMediaType(part) {
|
|
137
|
+
const candidate = normalizeMimeHint(part.mimeType)
|
|
138
|
+
?? normalizeMimeHint(mimeFromDataUri(part.dataUri))
|
|
139
|
+
?? mimeHintFromFilename(part.url);
|
|
140
|
+
if (!candidate)
|
|
141
|
+
return undefined;
|
|
142
|
+
const normalized = candidate.split(';')[0].trim().toLowerCase();
|
|
143
|
+
switch (normalized) {
|
|
144
|
+
case 'image/png':
|
|
145
|
+
case 'image/jpeg':
|
|
146
|
+
case 'image/jpg':
|
|
147
|
+
return normalized === 'image/jpg' ? 'image/jpeg' : normalized;
|
|
148
|
+
case 'image/webp':
|
|
149
|
+
case 'image/gif':
|
|
150
|
+
return normalized;
|
|
151
|
+
default:
|
|
152
|
+
return undefined;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
function mimeFromDataUri(dataUri) {
|
|
156
|
+
if (!dataUri)
|
|
157
|
+
return undefined;
|
|
158
|
+
const m = /^data:([^;,]+)/.exec(dataUri);
|
|
159
|
+
return m?.[1];
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Render a stored file/audio/video descriptor line when the optional
|
|
163
|
+
* `fileStore` hook persists the part's local bytes. Returns `undefined`
|
|
164
|
+
* (fall back to the placeholder) on no local bytes, a missing hook, an
|
|
165
|
+
* unrecognized result, or a hook failure — delivery never breaks.
|
|
166
|
+
*/
|
|
167
|
+
async function storedDescriptor(part, fileStore) {
|
|
168
|
+
if (!fileStore || !part.localData || part.localData.byteLength === 0)
|
|
169
|
+
return undefined;
|
|
170
|
+
try {
|
|
171
|
+
const descriptor = await fileStore(part);
|
|
172
|
+
return descriptor ? renderDescriptor(descriptor) : undefined;
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
return undefined;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
function renderDescriptor(asset) {
|
|
179
|
+
const mime = asset.mimeType ? ' ' + asset.mimeType : '';
|
|
180
|
+
const readable = asset.readable ? ' readable' : '';
|
|
181
|
+
return `[attachment ${asset.attachmentId} ${asset.name}${mime} ${humanSize(asset.bytes)}${readable}]`;
|
|
182
|
+
}
|
|
183
|
+
function humanSize(bytes) {
|
|
184
|
+
if (bytes < 1024)
|
|
185
|
+
return bytes + ' B';
|
|
186
|
+
const kb = bytes / 1024;
|
|
187
|
+
if (kb < 1024)
|
|
188
|
+
return kb.toFixed(1) + ' KB';
|
|
189
|
+
return (kb / 1024).toFixed(1) + ' MB';
|
|
190
|
+
}
|
|
191
|
+
//# sourceMappingURL=message-converter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"message-converter.js","sourceRoot":"","sources":["../src/message-converter.ts"],"names":[],"mappings":"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"}
|