@saleso.innovations/bridge 0.1.4 → 0.1.6
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/INTEGRATION.md +22 -0
- package/dist/client.d.ts +15 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +51 -3
- package/dist/hermesForwarder.d.ts.map +1 -1
- package/dist/hermesForwarder.js +28 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/package.json +1 -1
package/INTEGRATION.md
CHANGED
|
@@ -53,6 +53,28 @@ export async function resumeCleosConnection() {
|
|
|
53
53
|
- Relay routing (Railway) between iOS app and Hermes WebSocket
|
|
54
54
|
- Message history in Convex
|
|
55
55
|
|
|
56
|
+
## Cron job delivery
|
|
57
|
+
|
|
58
|
+
When a Hermes cron job completes and should appear in the Cleos **Jobs** tab (not chat), deliver the result over the existing agent WebSocket:
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
import { reconnectHermesAgent } from "@saleso.innovations/bridge";
|
|
62
|
+
|
|
63
|
+
const connection = await reconnectHermesAgent({ onUserMessage: handleCleosMessage });
|
|
64
|
+
|
|
65
|
+
// On cron completion — conversationId is the active Cleos conversation for this agent.
|
|
66
|
+
connection.deliverCronResult(finalText, {
|
|
67
|
+
conversationId: "<convex-conversation-id>",
|
|
68
|
+
jobId: job.id,
|
|
69
|
+
jobName: job.name,
|
|
70
|
+
runAt: Date.now(),
|
|
71
|
+
});
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Cleos routes messages with `metadata.cron` directly to the Jobs inbox. Hermes default cron wrappers (`Cronjob Response: ...`) are also detected automatically.
|
|
75
|
+
|
|
76
|
+
Store the Cleos `conversationId` when pairing (fetch via Convex `conversations:getActive` for the new `agentId`) so cron delivery has a valid target.
|
|
77
|
+
|
|
56
78
|
## What Hermes handles
|
|
57
79
|
|
|
58
80
|
- Pairing UI (code entry)
|
package/dist/client.d.ts
CHANGED
|
@@ -8,20 +8,35 @@ export type ConnectOptions = {
|
|
|
8
8
|
capabilities?: string[];
|
|
9
9
|
onUserMessage?: (content: string, meta: UserMessageMeta, reply: AgentReply) => Promise<void> | void;
|
|
10
10
|
};
|
|
11
|
+
export type UserMessageAttachment = {
|
|
12
|
+
storageId: string;
|
|
13
|
+
mimeType: string;
|
|
14
|
+
fileName: string;
|
|
15
|
+
size?: number;
|
|
16
|
+
url?: string;
|
|
17
|
+
};
|
|
11
18
|
export type UserMessageMeta = {
|
|
12
19
|
agentId: string;
|
|
13
20
|
conversationId: string;
|
|
14
21
|
messageId: string;
|
|
22
|
+
attachments?: UserMessageAttachment[];
|
|
15
23
|
};
|
|
16
24
|
export type AgentReply = {
|
|
17
25
|
delta: (text: string, sequence: number) => void;
|
|
18
26
|
complete: (text: string, sequence: number) => void;
|
|
19
27
|
};
|
|
28
|
+
export type CronDeliveryMeta = {
|
|
29
|
+
conversationId: string;
|
|
30
|
+
jobId: string;
|
|
31
|
+
jobName: string;
|
|
32
|
+
runAt?: number;
|
|
33
|
+
};
|
|
20
34
|
export type ConnectResult = {
|
|
21
35
|
agentId: string;
|
|
22
36
|
agentToken: string;
|
|
23
37
|
close: () => void;
|
|
24
38
|
closed: Promise<void>;
|
|
39
|
+
deliverCronResult: (content: string, meta: CronDeliveryMeta) => void;
|
|
25
40
|
};
|
|
26
41
|
export declare function pairCleosAgent(options: Omit<ConnectOptions, "onUserMessage">): Promise<{
|
|
27
42
|
agentId: string;
|
package/dist/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAEA,OAAO,EAAoC,KAAK,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAIhG,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CACrG,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAEA,OAAO,EAAoC,KAAK,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAIhG,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CACrG,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,qBAAqB,EAAE,CAAC;CACvC,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAChD,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;CACpD,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,cAAc,EAAE,MAAM,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,iBAAiB,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,gBAAgB,KAAK,IAAI,CAAC;CACtE,CAAC;AAwLF,wBAAsB,cAAc,CAAC,OAAO,EAAE,IAAI,CAAC,cAAc,EAAE,eAAe,CAAC,GAAG,OAAO,CAAC;IAC5F,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC,CA0BD;AAED,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC,CAcxF;AAED,wBAAsB,oBAAoB,CAAC,OAAO,GAAE;IAClD,WAAW,CAAC,EAAE,qBAAqB,CAAC;IACpC,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,aAAa,CAAC,EAAE,cAAc,CAAC,eAAe,CAAC,CAAC;CAC5C,GAAG,OAAO,CAAC,aAAa,CAAC,CAc9B"}
|
package/dist/client.js
CHANGED
|
@@ -3,6 +3,47 @@ import WebSocket from "ws";
|
|
|
3
3
|
import { saveCredentials, loadCredentials } from "./credentials.js";
|
|
4
4
|
import { convexSiteUrlFromEnv, resolvePairingCode } from "./resolve.js";
|
|
5
5
|
import { normalizePairingCode } from "./normalizePairingCode.js";
|
|
6
|
+
function parseUserMessageAttachments(raw) {
|
|
7
|
+
if (!Array.isArray(raw))
|
|
8
|
+
return undefined;
|
|
9
|
+
const attachments = [];
|
|
10
|
+
for (const item of raw) {
|
|
11
|
+
if (!item || typeof item !== "object")
|
|
12
|
+
continue;
|
|
13
|
+
const record = item;
|
|
14
|
+
const storageId = typeof record.storageId === "string" ? record.storageId : "";
|
|
15
|
+
const mimeType = typeof record.mimeType === "string" ? record.mimeType : "";
|
|
16
|
+
const fileName = typeof record.fileName === "string" ? record.fileName : "";
|
|
17
|
+
if (!storageId || !mimeType || !fileName)
|
|
18
|
+
continue;
|
|
19
|
+
attachments.push({
|
|
20
|
+
storageId,
|
|
21
|
+
mimeType,
|
|
22
|
+
fileName,
|
|
23
|
+
size: typeof record.size === "number" ? record.size : undefined,
|
|
24
|
+
url: typeof record.url === "string" ? record.url : undefined,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
return attachments.length > 0 ? attachments : undefined;
|
|
28
|
+
}
|
|
29
|
+
function sendCronResult(ws, agentId, content, meta) {
|
|
30
|
+
ws.send(JSON.stringify({
|
|
31
|
+
type: "agent.message",
|
|
32
|
+
agentId,
|
|
33
|
+
conversationId: meta.conversationId,
|
|
34
|
+
messageId: randomUUID(),
|
|
35
|
+
content,
|
|
36
|
+
sequence: 0,
|
|
37
|
+
final: true,
|
|
38
|
+
metadata: {
|
|
39
|
+
cron: {
|
|
40
|
+
jobId: meta.jobId,
|
|
41
|
+
jobName: meta.jobName,
|
|
42
|
+
runAt: meta.runAt,
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
}));
|
|
46
|
+
}
|
|
6
47
|
function createReplySender(ws, agentId, conversationId, messageId) {
|
|
7
48
|
return {
|
|
8
49
|
delta(text, sequence) {
|
|
@@ -52,10 +93,11 @@ async function openAgentConnection(options) {
|
|
|
52
93
|
const content = typeof envelope.content === "string" ? envelope.content : "";
|
|
53
94
|
const agentId = typeof envelope.agentId === "string" ? envelope.agentId : options.agentId;
|
|
54
95
|
const conversationId = typeof envelope.conversationId === "string" ? envelope.conversationId : "unknown";
|
|
55
|
-
const
|
|
56
|
-
const
|
|
96
|
+
const attachments = parseUserMessageAttachments(envelope.attachments);
|
|
97
|
+
const replyMessageId = randomUUID();
|
|
98
|
+
const reply = createReplySender(ws, agentId, conversationId, replyMessageId);
|
|
57
99
|
if (options.onUserMessage) {
|
|
58
|
-
await options.onUserMessage(content, { agentId, conversationId, messageId }, reply);
|
|
100
|
+
await options.onUserMessage(content, { agentId, conversationId, messageId: replyMessageId, attachments }, reply);
|
|
59
101
|
return;
|
|
60
102
|
}
|
|
61
103
|
const echo = `Echo: ${content}`;
|
|
@@ -80,6 +122,12 @@ async function openAgentConnection(options) {
|
|
|
80
122
|
ws.close();
|
|
81
123
|
},
|
|
82
124
|
closed,
|
|
125
|
+
deliverCronResult(content, meta) {
|
|
126
|
+
if (ws.readyState !== WebSocket.OPEN) {
|
|
127
|
+
throw new Error("Cleos relay connection is not open");
|
|
128
|
+
}
|
|
129
|
+
sendCronResult(ws, options.agentId, content, meta);
|
|
130
|
+
},
|
|
83
131
|
};
|
|
84
132
|
}
|
|
85
133
|
async function resolveRelayTargets(options) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hermesForwarder.d.ts","sourceRoot":"","sources":["../src/hermesForwarder.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,
|
|
1
|
+
{"version":3,"file":"hermesForwarder.d.ts","sourceRoot":"","sources":["../src/hermesForwarder.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAyB,eAAe,EAAE,MAAM,aAAa,CAAC;AAGtF,MAAM,MAAM,sBAAsB,GAAG;IACnC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAkBF,wBAAgB,sBAAsB,CAAC,OAAO,GAAE,sBAA2B,GAAG;IAC5E,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,KAAK,EAAE,MAAM,CAAC;CACf,CAUA;AA2ED,wBAAsB,eAAe,CACnC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,eAAe,EACrB,KAAK,EAAE,UAAU,EACjB,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,IAAI,CAAC,CAyEf;AAED,wBAAgB,0BAA0B,CAAC,OAAO,GAAE,sBAA2B,IAC/D,SAAS,MAAM,EAAE,MAAM,eAAe,EAAE,OAAO,UAAU,KAAG,OAAO,CAAC,IAAI,CAAC,CAMxF"}
|
package/dist/hermesForwarder.js
CHANGED
|
@@ -64,6 +64,32 @@ function extractDeltaFromChunk(payload) {
|
|
|
64
64
|
const content = delta.content;
|
|
65
65
|
return typeof content === "string" ? content : null;
|
|
66
66
|
}
|
|
67
|
+
function buildHermesUserContent(text, attachments) {
|
|
68
|
+
const trimmed = text.trim();
|
|
69
|
+
const imageParts = (attachments ?? [])
|
|
70
|
+
.filter((attachment) => attachment.mimeType.startsWith("image/") && attachment.url)
|
|
71
|
+
.map((attachment) => ({
|
|
72
|
+
type: "image_url",
|
|
73
|
+
image_url: { url: attachment.url },
|
|
74
|
+
}));
|
|
75
|
+
const fileNames = (attachments ?? [])
|
|
76
|
+
.filter((attachment) => !attachment.mimeType.startsWith("image/"))
|
|
77
|
+
.map((attachment) => attachment.fileName);
|
|
78
|
+
let prose = trimmed;
|
|
79
|
+
if (fileNames.length > 0) {
|
|
80
|
+
const fileNote = `[Attached files: ${fileNames.join(", ")}]`;
|
|
81
|
+
prose = prose.length === 0 ? fileNote : `${prose}\n\n${fileNote}`;
|
|
82
|
+
}
|
|
83
|
+
if (imageParts.length === 0) {
|
|
84
|
+
return prose.length === 0 ? "(attachment)" : prose;
|
|
85
|
+
}
|
|
86
|
+
const parts = [];
|
|
87
|
+
if (prose.length > 0) {
|
|
88
|
+
parts.push({ type: "text", text: prose });
|
|
89
|
+
}
|
|
90
|
+
parts.push(...imageParts);
|
|
91
|
+
return parts;
|
|
92
|
+
}
|
|
67
93
|
export async function forwardToHermes(content, meta, reply, options = {}) {
|
|
68
94
|
const { apiUrl, apiKey, model } = resolveHermesApiConfig(options);
|
|
69
95
|
await ensureHermesReachable(apiUrl, apiKey);
|
|
@@ -73,10 +99,11 @@ export async function forwardToHermes(content, meta, reply, options = {}) {
|
|
|
73
99
|
if (apiKey)
|
|
74
100
|
headers.authorization = `Bearer ${apiKey}`;
|
|
75
101
|
const conversationKey = options.conversationId ?? meta.conversationId;
|
|
102
|
+
const userContent = buildHermesUserContent(content, meta.attachments);
|
|
76
103
|
const body = {
|
|
77
104
|
model,
|
|
78
105
|
stream: true,
|
|
79
|
-
messages: [{ role: "user", content }],
|
|
106
|
+
messages: [{ role: "user", content: userContent }],
|
|
80
107
|
user: conversationKey,
|
|
81
108
|
};
|
|
82
109
|
const response = await fetch(apiUrl, {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { connectHermesAgent, pairCleosAgent, reconnectHermesAgent } from "./client.js";
|
|
2
|
-
export type { AgentReply, ConnectOptions, ConnectResult, UserMessageMeta } from "./client.js";
|
|
2
|
+
export type { AgentReply, ConnectOptions, ConnectResult, CronDeliveryMeta, UserMessageMeta } from "./client.js";
|
|
3
3
|
export { loadCredentials, saveCredentials, credentialsPathForDisplay } from "./credentials.js";
|
|
4
4
|
export type { SavedAgentCredentials } from "./credentials.js";
|
|
5
5
|
export { resolvePairingCode, convexSiteUrlFromEnv } from "./resolve.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACvF,YAAY,EAAE,UAAU,EAAE,cAAc,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACvF,YAAY,EAAE,UAAU,EAAE,cAAc,EAAE,aAAa,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAChH,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,yBAAyB,EAAE,MAAM,kBAAkB,CAAC;AAC/F,YAAY,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACxE,YAAY,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,0BAA0B,EAAE,eAAe,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAC3G,YAAY,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AACnE,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,YAAY,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EACL,0BAA0B,EAC1B,6BAA6B,EAC7B,sBAAsB,EACtB,oBAAoB,GACrB,MAAM,gBAAgB,CAAC"}
|