@ziggs-ai/api-client 0.1.3 → 0.1.5
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 +13 -7
- package/dist/ConnectionManager.d.ts +46 -0
- package/dist/ConnectionManager.js +132 -0
- package/dist/http/AgentSearchClient.d.ts +36 -0
- package/dist/http/AgentSearchClient.js +72 -0
- package/dist/http/AgreementClient.d.ts +153 -0
- package/dist/http/AgreementClient.js +477 -0
- package/dist/http/ArtifactsClient.d.ts +48 -0
- package/dist/http/ArtifactsClient.js +90 -0
- package/dist/http/ChatClient.d.ts +34 -0
- package/dist/http/ChatClient.js +104 -0
- package/dist/http/ContextDiscoveryClient.d.ts +23 -0
- package/dist/http/ContextDiscoveryClient.js +35 -0
- package/dist/http/ContextReadClient.d.ts +33 -0
- package/dist/http/ContextReadClient.js +54 -0
- package/dist/http/MarketplaceClient.d.ts +19 -0
- package/dist/http/MarketplaceClient.js +72 -0
- package/dist/http/MessagesClient.d.ts +26 -0
- package/dist/http/MessagesClient.js +49 -0
- package/dist/http/ScopeClient.d.ts +33 -0
- package/dist/http/ScopeClient.js +39 -0
- package/dist/http/TaskClient.d.ts +75 -0
- package/dist/http/TaskClient.js +352 -0
- package/dist/http/TelemetryClient.d.ts +11 -0
- package/dist/http/TelemetryClient.js +53 -0
- package/dist/http/index.d.ts +16 -0
- package/dist/http/index.js +11 -0
- package/dist/index.d.ts +9 -0
- package/{src → dist}/index.js +2 -12
- package/dist/shared/runtimeLog.d.ts +14 -0
- package/dist/shared/runtimeLog.js +64 -0
- package/dist/types.d.ts +130 -0
- package/dist/types.js +50 -0
- package/dist/utils/urlUtils.d.ts +2 -0
- package/dist/utils/urlUtils.js +8 -0
- package/dist/websocket/ControlSocket.d.ts +13 -0
- package/dist/websocket/ControlSocket.js +37 -0
- package/dist/websocket/WebSocketClient.d.ts +71 -0
- package/dist/websocket/WebSocketClient.js +233 -0
- package/dist/websocket/index.js +1 -0
- package/package.json +20 -7
- package/src/ConnectionManager.ts +172 -0
- package/src/http/AgentSearchClient.ts +115 -0
- package/src/http/AgreementClient.ts +721 -0
- package/src/http/ArtifactsClient.ts +133 -0
- package/src/http/ChatClient.ts +147 -0
- package/src/http/ContextDiscoveryClient.ts +52 -0
- package/src/http/ContextReadClient.ts +83 -0
- package/src/http/MarketplaceClient.ts +94 -0
- package/src/http/MessagesClient.ts +71 -0
- package/src/http/ScopeClient.ts +64 -0
- package/src/http/TaskClient.ts +450 -0
- package/src/http/{TelemetryClient.js → TelemetryClient.ts} +21 -7
- package/src/http/index.ts +26 -0
- package/src/index.ts +27 -0
- package/src/shared/runtimeLog.ts +68 -0
- package/src/types.ts +158 -0
- package/src/utils/urlUtils.ts +9 -0
- package/src/websocket/ControlSocket.ts +51 -0
- package/src/websocket/WebSocketClient.ts +315 -0
- package/src/websocket/index.ts +1 -0
- package/src/ConnectionManager.js +0 -179
- package/src/http/AgentSearchClient.js +0 -113
- package/src/http/ContextReader.js +0 -99
- package/src/http/ContextWriter.js +0 -98
- package/src/http/TaskClient.js +0 -612
- package/src/http/index.js +0 -6
- package/src/types.js +0 -28
- package/src/utils/urlUtils.js +0 -17
- package/src/websocket/ControlSocket.js +0 -55
- package/src/websocket/WebSocketClient.js +0 -318
- /package/{src/websocket/index.js → dist/websocket/index.d.ts} +0 -0
package/src/ConnectionManager.js
DELETED
|
@@ -1,179 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ConnectionManager — generic, agent-agnostic pool.
|
|
3
|
-
*
|
|
4
|
-
* Holds an in-memory map of registered entities keyed by id. Each entity is
|
|
5
|
-
* described by a pair of functions: `openFn()` returns a live "connection"
|
|
6
|
-
* handle, `closeFn(handle)` tears it down. The manager enforces a `maxActive`
|
|
7
|
-
* cap with LRU eviction and automatically releases idle entries after
|
|
8
|
-
* `idleTimeoutMs`.
|
|
9
|
-
*
|
|
10
|
-
* Optionally opens a control socket (`createControlSocket`) so the backend
|
|
11
|
-
* can push `wake` events for entries that aren't currently active — the
|
|
12
|
-
* manager handles receipt and calls `wake(id)` on your behalf.
|
|
13
|
-
*
|
|
14
|
-
* This lives in api-client because it has no dependency on ZiggsAgent (or any
|
|
15
|
-
* other SDK concept). Anyone writing their own agent runtime — or another
|
|
16
|
-
* language binding — can use ConnectionManager directly, or just use
|
|
17
|
-
* `createControlSocket` alone if they already manage their own lifecycle.
|
|
18
|
-
*
|
|
19
|
-
* Usage:
|
|
20
|
-
* const mgr = new ConnectionManager({
|
|
21
|
-
* maxActive: 50,
|
|
22
|
-
* idleTimeoutMs: 60_000,
|
|
23
|
-
* control: { wsUrl, operatorKey }, // optional
|
|
24
|
-
* });
|
|
25
|
-
* mgr.register(
|
|
26
|
-
* 'agent-42',
|
|
27
|
-
* async () => openMySocketFor('agent-42'),
|
|
28
|
-
* async (handle) => handle.close(),
|
|
29
|
-
* { domain: 'science' } // optional metadata for query()
|
|
30
|
-
* );
|
|
31
|
-
* mgr.start(); // opens the control socket (if configured)
|
|
32
|
-
* const handle = await mgr.wake('agent-42');
|
|
33
|
-
* await mgr.stop();
|
|
34
|
-
*/
|
|
35
|
-
|
|
36
|
-
import { createControlSocket } from './websocket/ControlSocket.js';
|
|
37
|
-
|
|
38
|
-
export class ConnectionManager {
|
|
39
|
-
/**
|
|
40
|
-
* @param {object} opts
|
|
41
|
-
* @param {number} [opts.maxActive=50] Max simultaneous open handles
|
|
42
|
-
* @param {number} [opts.idleTimeoutMs=60000] Ms before an idle handle is auto-closed
|
|
43
|
-
* @param {{ wsUrl: string, operatorKey: string }} [opts.control] Enables control socket wake-on-demand.
|
|
44
|
-
*/
|
|
45
|
-
constructor({ maxActive = 50, idleTimeoutMs = 60_000, control } = {}) {
|
|
46
|
-
this.maxActive = maxActive;
|
|
47
|
-
this.idleTimeoutMs = idleTimeoutMs;
|
|
48
|
-
this._controlOpts = control ?? null;
|
|
49
|
-
this._controlHandle = null;
|
|
50
|
-
|
|
51
|
-
// id → { openFn, closeFn }
|
|
52
|
-
this._entries = new Map();
|
|
53
|
-
// id → { handle, timer, lastActive }
|
|
54
|
-
this._active = new Map();
|
|
55
|
-
// id → user-supplied metadata (for query())
|
|
56
|
-
this._meta = new Map();
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// ---- registration ----
|
|
60
|
-
|
|
61
|
-
register(id, openFn, closeFn, meta) {
|
|
62
|
-
if (!id) throw new Error('[ConnectionManager] id is required');
|
|
63
|
-
if (typeof openFn !== 'function') throw new Error('[ConnectionManager] openFn must be a function');
|
|
64
|
-
if (typeof closeFn !== 'function') throw new Error('[ConnectionManager] closeFn must be a function');
|
|
65
|
-
this._entries.set(id, { openFn, closeFn });
|
|
66
|
-
if (meta) this._meta.set(id, meta);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// ---- lifecycle ----
|
|
70
|
-
|
|
71
|
-
/** Open the control socket (if configured). Safe to call multiple times. */
|
|
72
|
-
start() {
|
|
73
|
-
if (this._controlHandle || !this._controlOpts) return;
|
|
74
|
-
this._controlHandle = createControlSocket({
|
|
75
|
-
...this._controlOpts,
|
|
76
|
-
agentIds: () => this.list(),
|
|
77
|
-
onWake: (id) => this.wake(id).catch(err => console.warn(`[ConnectionManager] wake("${id}") failed: ${err.message}`)),
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/** Close the control socket and all active handles. */
|
|
82
|
-
async stop() {
|
|
83
|
-
this._controlHandle?.close();
|
|
84
|
-
this._controlHandle = null;
|
|
85
|
-
await this.sleepAll();
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// ---- connection lifecycle ----
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Ensure an entry is active. Returns the live handle from `openFn()`.
|
|
92
|
-
* If `maxActive` is hit, evicts the LRU entry first.
|
|
93
|
-
*/
|
|
94
|
-
async wake(id) {
|
|
95
|
-
const existing = this._active.get(id);
|
|
96
|
-
if (existing) {
|
|
97
|
-
this._resetTimer(id);
|
|
98
|
-
return existing.handle;
|
|
99
|
-
}
|
|
100
|
-
const entry = this._entries.get(id);
|
|
101
|
-
if (!entry) throw new Error(`[ConnectionManager] unknown id: "${id}"`);
|
|
102
|
-
|
|
103
|
-
if (this._active.size >= this.maxActive) await this._evictLRU();
|
|
104
|
-
|
|
105
|
-
const handle = await entry.openFn();
|
|
106
|
-
this._active.set(id, { handle, timer: null, lastActive: Date.now() });
|
|
107
|
-
this._scheduleIdle(id);
|
|
108
|
-
return handle;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/** Close a single active entry. */
|
|
112
|
-
async sleep(id) {
|
|
113
|
-
const entry = this._active.get(id);
|
|
114
|
-
if (!entry) return;
|
|
115
|
-
clearTimeout(entry.timer);
|
|
116
|
-
this._active.delete(id);
|
|
117
|
-
const reg = this._entries.get(id);
|
|
118
|
-
if (reg) await reg.closeFn(entry.handle);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
async sleepAll() {
|
|
122
|
-
await Promise.all([...this._active.keys()].map(id => this.sleep(id)));
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/** Call after `wake` if you performed work and want to extend the idle timer. */
|
|
126
|
-
touch(id) { this._resetTimer(id); }
|
|
127
|
-
|
|
128
|
-
// ---- introspection ----
|
|
129
|
-
|
|
130
|
-
/** All registered ids. */
|
|
131
|
-
list() { return [...this._entries.keys()]; }
|
|
132
|
-
/** Currently active ids. */
|
|
133
|
-
listActive() { return [...this._active.keys()]; }
|
|
134
|
-
get size() { return this._entries.size; }
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* Filter ids by metadata. `filter.domain` is exact match; `filter.expertise`
|
|
138
|
-
* and `filter.tags` are "at least one must match". No filter → all ids.
|
|
139
|
-
*/
|
|
140
|
-
query({ domain, expertise, tags } = {}) {
|
|
141
|
-
const results = [];
|
|
142
|
-
for (const [id, meta] of this._meta) {
|
|
143
|
-
if (domain && meta.domain !== domain) continue;
|
|
144
|
-
if (expertise?.length && !expertise.some(e => meta.expertise?.includes(e))) continue;
|
|
145
|
-
if (tags?.length && !tags.some(t => meta.tags?.includes(t))) continue;
|
|
146
|
-
results.push(id);
|
|
147
|
-
}
|
|
148
|
-
return results;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/** Read metadata for a single id (undefined if not registered / no meta). */
|
|
152
|
-
getMeta(id) { return this._meta.get(id); }
|
|
153
|
-
|
|
154
|
-
// ---- internals ----
|
|
155
|
-
|
|
156
|
-
_scheduleIdle(id) {
|
|
157
|
-
const entry = this._active.get(id);
|
|
158
|
-
if (!entry) return;
|
|
159
|
-
clearTimeout(entry.timer);
|
|
160
|
-
entry.timer = setTimeout(() => {
|
|
161
|
-
this.sleep(id).catch(err => console.warn(`[ConnectionManager] idle sleep("${id}") failed: ${err.message}`));
|
|
162
|
-
}, this.idleTimeoutMs);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
_resetTimer(id) {
|
|
166
|
-
const entry = this._active.get(id);
|
|
167
|
-
if (!entry) return;
|
|
168
|
-
entry.lastActive = Date.now();
|
|
169
|
-
this._scheduleIdle(id);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
async _evictLRU() {
|
|
173
|
-
let oldest = null;
|
|
174
|
-
for (const [id, entry] of this._active) {
|
|
175
|
-
if (!oldest || entry.lastActive < this._active.get(oldest).lastActive) oldest = id;
|
|
176
|
-
}
|
|
177
|
-
if (oldest) await this.sleep(oldest);
|
|
178
|
-
}
|
|
179
|
-
}
|
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
import 'dotenv/config';
|
|
2
|
-
import { getBackendUrl } from '../utils/urlUtils.js';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Client for searching and retrieving agent information from the Ziggs API
|
|
6
|
-
*/
|
|
7
|
-
export class AgentSearchClient {
|
|
8
|
-
constructor(operatorKey, agentId) {
|
|
9
|
-
if (!operatorKey) {
|
|
10
|
-
throw new Error('AgentSearchClient: operatorKey is required');
|
|
11
|
-
}
|
|
12
|
-
if (!agentId) {
|
|
13
|
-
throw new Error('AgentSearchClient: agentId is required (operator-token impersonation)');
|
|
14
|
-
}
|
|
15
|
-
this.operatorKey = operatorKey;
|
|
16
|
-
this.agentId = agentId;
|
|
17
|
-
this.baseUrl = getBackendUrl();
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Search for agents based on a text query
|
|
22
|
-
* @param {string} query - Search query text
|
|
23
|
-
* @param {Object} options - Additional options
|
|
24
|
-
* @param {boolean} options.fullResponse - Whether to return full response data
|
|
25
|
-
* @returns {Promise<Object>} Search results
|
|
26
|
-
*/
|
|
27
|
-
async searchAgents(query, options = {}) {
|
|
28
|
-
const { fullResponse = true } = options;
|
|
29
|
-
|
|
30
|
-
try {
|
|
31
|
-
const response = await fetch(`${this.baseUrl}/agents/searchAgents`, {
|
|
32
|
-
method: 'POST',
|
|
33
|
-
headers: this._buildHeaders(),
|
|
34
|
-
body: JSON.stringify({
|
|
35
|
-
query,
|
|
36
|
-
fullResponse
|
|
37
|
-
})
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
if (!response.ok) {
|
|
41
|
-
const errorText = await response.text();
|
|
42
|
-
return {
|
|
43
|
-
success: false,
|
|
44
|
-
error: `Search failed: ${response.status}`,
|
|
45
|
-
message: errorText
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const data = await response.json();
|
|
50
|
-
return data;
|
|
51
|
-
|
|
52
|
-
} catch (error) {
|
|
53
|
-
return {
|
|
54
|
-
success: false,
|
|
55
|
-
error: error.message || 'Failed to search agents'
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Get detailed information about a specific agent
|
|
62
|
-
* @param {string} agentId - The unique identifier of the agent
|
|
63
|
-
* @returns {Promise<Object>} Agent details
|
|
64
|
-
*/
|
|
65
|
-
async getAgentById(agentId) {
|
|
66
|
-
if (!agentId) {
|
|
67
|
-
return {
|
|
68
|
-
success: false,
|
|
69
|
-
error: 'agentId is required'
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
try {
|
|
74
|
-
const response = await fetch(`${this.baseUrl}/agents/getAgentById`, {
|
|
75
|
-
method: 'POST',
|
|
76
|
-
headers: this._buildHeaders(),
|
|
77
|
-
body: JSON.stringify({ agentId })
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
if (!response.ok) {
|
|
81
|
-
return {
|
|
82
|
-
success: false,
|
|
83
|
-
error: `Failed to get agent details: ${response.status}`
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const agent = await response.json();
|
|
88
|
-
|
|
89
|
-
return {
|
|
90
|
-
success: true,
|
|
91
|
-
agentId: agent.agentId,
|
|
92
|
-
name: agent.name,
|
|
93
|
-
description: agent.storePage?.description || 'No description available',
|
|
94
|
-
storePage: agent.storePage,
|
|
95
|
-
type: agent.type || 'agent'
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
} catch (error) {
|
|
99
|
-
return {
|
|
100
|
-
success: false,
|
|
101
|
-
error: error.message || 'Failed to get agent details'
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
_buildHeaders() {
|
|
107
|
-
return {
|
|
108
|
-
'Content-Type': 'application/json',
|
|
109
|
-
'Authorization': `Bearer ${this.operatorKey}`,
|
|
110
|
-
'X-Agent-Id': this.agentId,
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
}
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
import 'dotenv/config';
|
|
2
|
-
import { getBackendUrl } from '../utils/urlUtils.js';
|
|
3
|
-
|
|
4
|
-
export class ContextReader {
|
|
5
|
-
constructor(operatorKey, agentId) {
|
|
6
|
-
if (!operatorKey) {
|
|
7
|
-
throw new Error('ContextReader: operatorKey is required');
|
|
8
|
-
}
|
|
9
|
-
if (!agentId) {
|
|
10
|
-
throw new Error('ContextReader: agentId is required (operator-token impersonation)');
|
|
11
|
-
}
|
|
12
|
-
this.operatorKey = operatorKey;
|
|
13
|
-
this.agentId = agentId;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
_getBaseUrl() {
|
|
17
|
-
const route = process.env.HISTORY_SERVICE_ROUTE || 'context';
|
|
18
|
-
return `${getBackendUrl()}/${route}`;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* @param {string} chatId - Chat ID
|
|
23
|
-
* @param {Object} [options] - Optional limits (passed as query params to Shared Context Service API)
|
|
24
|
-
* @param {number} [options.maxMessages] - Max number of history entries (default: server default)
|
|
25
|
-
* @param {number} [options.maxCharacters] - Max total character count of history text (default: server default)
|
|
26
|
-
* @param {number} [options.maxTaskHistory] - Max number of task_history entries (default: server default)
|
|
27
|
-
* @param {boolean} [options.summarize] - Default true. Set false to skip timelineSummary generation.
|
|
28
|
-
*/
|
|
29
|
-
async read(chatId, options = {}) {
|
|
30
|
-
if (!chatId) {
|
|
31
|
-
return this._emptyContext();
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
try {
|
|
35
|
-
const url = new URL(`${this._getBaseUrl()}/read`);
|
|
36
|
-
url.searchParams.set('chatId', chatId);
|
|
37
|
-
if (options.maxMessages != null) url.searchParams.set('maxMessages', String(options.maxMessages));
|
|
38
|
-
if (options.maxCharacters != null) url.searchParams.set('maxCharacters', String(options.maxCharacters));
|
|
39
|
-
if (options.maxTaskHistory != null) url.searchParams.set('maxTaskHistory', String(options.maxTaskHistory));
|
|
40
|
-
// Default summarize on; callers can disable with summarize: false
|
|
41
|
-
if (options.summarize !== false) url.searchParams.set('summarize', 'true');
|
|
42
|
-
|
|
43
|
-
const res = await fetch(url.toString(), {
|
|
44
|
-
method: 'GET',
|
|
45
|
-
headers: this._buildHeaders()
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
if (!res.ok) {
|
|
49
|
-
console.warn(`⚠️ Context read failed: ${res.status}`);
|
|
50
|
-
return this._emptyContext();
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const data = await res.json();
|
|
54
|
-
return this._extractContext(data);
|
|
55
|
-
|
|
56
|
-
} catch (error) {
|
|
57
|
-
console.warn(`⚠️ Context read error: ${error.message}`);
|
|
58
|
-
return this._emptyContext();
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
_extractContext(data) {
|
|
63
|
-
const history = Array.isArray(data?.history) ? data.history : [];
|
|
64
|
-
const users = Array.isArray(data?.users) ? data.users : [];
|
|
65
|
-
const activeTasks = Array.isArray(data?.openTasks) ? data.openTasks : [];
|
|
66
|
-
const agents = Array.isArray(data?.agents) ? data.agents : [];
|
|
67
|
-
const perspectiveId = data?.perspectiveId || null;
|
|
68
|
-
const timelineSummary = data?.timelineSummary ?? null;
|
|
69
|
-
|
|
70
|
-
return {
|
|
71
|
-
history,
|
|
72
|
-
users,
|
|
73
|
-
activeTasks,
|
|
74
|
-
agents,
|
|
75
|
-
perspectiveId,
|
|
76
|
-
...(timelineSummary != null && { timelineSummary }),
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
_emptyContext() {
|
|
81
|
-
return {
|
|
82
|
-
history: [],
|
|
83
|
-
users: [],
|
|
84
|
-
activeTasks: [],
|
|
85
|
-
agents: [],
|
|
86
|
-
perspectiveId: null,
|
|
87
|
-
latestSequence: null,
|
|
88
|
-
timelineSummary: null
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
_buildHeaders() {
|
|
93
|
-
return {
|
|
94
|
-
'content-type': 'application/json',
|
|
95
|
-
'Authorization': `Bearer ${this.operatorKey}`,
|
|
96
|
-
'X-Agent-Id': this.agentId,
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
}
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
import 'dotenv/config';
|
|
2
|
-
import { EntryTypes, ContentTypes } from '../types.js';
|
|
3
|
-
import { getBackendUrl } from '../utils/urlUtils.js';
|
|
4
|
-
|
|
5
|
-
export class ContextWriter {
|
|
6
|
-
constructor(operatorKey, agentId) {
|
|
7
|
-
if (!operatorKey) {
|
|
8
|
-
throw new Error('ContextWriter: operatorKey is required');
|
|
9
|
-
}
|
|
10
|
-
if (!agentId) {
|
|
11
|
-
throw new Error('ContextWriter: agentId is required (operator-token impersonation)');
|
|
12
|
-
}
|
|
13
|
-
this.operatorKey = operatorKey;
|
|
14
|
-
this.agentId = agentId;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
_getBaseUrl() {
|
|
18
|
-
const route = process.env.HISTORY_SERVICE_ROUTE || 'context';
|
|
19
|
-
return `${getBackendUrl()}/${route}`;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
async recordOperation(chatId, operation) {
|
|
23
|
-
if (!chatId) return;
|
|
24
|
-
|
|
25
|
-
const prefix = operation.state === 'completed'
|
|
26
|
-
? 'operation_completed:'
|
|
27
|
-
: operation.state === 'failed'
|
|
28
|
-
? 'operation_error:'
|
|
29
|
-
: 'operation_started:';
|
|
30
|
-
|
|
31
|
-
const text = prefix + JSON.stringify(operation);
|
|
32
|
-
|
|
33
|
-
try {
|
|
34
|
-
await fetch(`${this._getBaseUrl()}/ingest`, {
|
|
35
|
-
method: 'POST',
|
|
36
|
-
headers: this._buildHeaders(),
|
|
37
|
-
body: JSON.stringify({
|
|
38
|
-
chatId,
|
|
39
|
-
text,
|
|
40
|
-
entryType: EntryTypes.ARTIFACT,
|
|
41
|
-
content_type: ContentTypes.OPERATION
|
|
42
|
-
})
|
|
43
|
-
});
|
|
44
|
-
} catch (error) {
|
|
45
|
-
console.warn(`⚠️ Failed to record operation: ${error.message}`);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
async recordMessage(chatId, message) {
|
|
50
|
-
if (!chatId) return;
|
|
51
|
-
|
|
52
|
-
try {
|
|
53
|
-
await fetch(`${this._getBaseUrl()}/ingest`, {
|
|
54
|
-
method: 'POST',
|
|
55
|
-
headers: this._buildHeaders(),
|
|
56
|
-
body: JSON.stringify({
|
|
57
|
-
chatId,
|
|
58
|
-
text: message.text,
|
|
59
|
-
entryType: EntryTypes.MESSAGE,
|
|
60
|
-
content_type: ContentTypes.TEXT
|
|
61
|
-
})
|
|
62
|
-
});
|
|
63
|
-
} catch (error) {
|
|
64
|
-
console.warn(`⚠️ Failed to record message: ${error.message}`);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Send agent thought (reasoning) as artifact so it appears in timeline and frontend.
|
|
70
|
-
* Backend stores with entryType=artifact, content_type=thought. Sender is taken from JWT.
|
|
71
|
-
*/
|
|
72
|
-
async recordThought(chatId, thoughtText) {
|
|
73
|
-
if (!chatId || thoughtText == null || String(thoughtText).trim() === '') return;
|
|
74
|
-
|
|
75
|
-
try {
|
|
76
|
-
await fetch(`${this._getBaseUrl()}/ingest`, {
|
|
77
|
-
method: 'POST',
|
|
78
|
-
headers: this._buildHeaders(),
|
|
79
|
-
body: JSON.stringify({
|
|
80
|
-
chatId,
|
|
81
|
-
text: String(thoughtText).trim(),
|
|
82
|
-
entryType: EntryTypes.ARTIFACT,
|
|
83
|
-
content_type: ContentTypes.THOUGHT
|
|
84
|
-
})
|
|
85
|
-
});
|
|
86
|
-
} catch (error) {
|
|
87
|
-
console.warn(`⚠️ Failed to record thought artifact: ${error.message}`);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
_buildHeaders() {
|
|
92
|
-
return {
|
|
93
|
-
'content-type': 'application/json',
|
|
94
|
-
'Authorization': `Bearer ${this.operatorKey}`,
|
|
95
|
-
'X-Agent-Id': this.agentId,
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
}
|