clientcoded 0.1.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/package.json +29 -0
- package/src/index.d.ts +32 -0
- package/src/index.js +258 -0
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "clientcoded",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "ClientCoded SDK - Trace your AI agent's tool calls for root cause analysis",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"types": "src/index.d.ts",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"ai",
|
|
9
|
+
"agent",
|
|
10
|
+
"testing",
|
|
11
|
+
"qa",
|
|
12
|
+
"tracing",
|
|
13
|
+
"evaluation",
|
|
14
|
+
"llm"
|
|
15
|
+
],
|
|
16
|
+
"author": "ClientCoded <travis@clientcoded.com>",
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"homepage": "https://github.com/ClientCoded/clientcoded-js",
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "https://github.com/ClientCoded/clientcoded-js.git"
|
|
22
|
+
},
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=16.0.0"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"src/"
|
|
28
|
+
]
|
|
29
|
+
}
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
declare module "clientcoded" {
|
|
2
|
+
interface ConfigOptions {
|
|
3
|
+
agentId: string;
|
|
4
|
+
apiKey: string;
|
|
5
|
+
traceUrl?: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
interface TraceData {
|
|
9
|
+
input?: any;
|
|
10
|
+
output?: any;
|
|
11
|
+
error?: string;
|
|
12
|
+
latencyMs?: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function configure(config: ConfigOptions): void;
|
|
16
|
+
export function setConversationId(conversationId: string): void;
|
|
17
|
+
export function setTurn(turnNumber: number): void;
|
|
18
|
+
export function incrementTurn(): void;
|
|
19
|
+
export function trace<T extends (...args: any[]) => Promise<any>>(
|
|
20
|
+
name: string,
|
|
21
|
+
fn: T
|
|
22
|
+
): T;
|
|
23
|
+
export function traceSync<T extends (...args: any[]) => any>(
|
|
24
|
+
name: string,
|
|
25
|
+
fn: T
|
|
26
|
+
): T;
|
|
27
|
+
export function logTrace(toolName: string, data?: TraceData): void;
|
|
28
|
+
export function traceLangChain<T extends (...args: any[]) => Promise<any>>(
|
|
29
|
+
name: string,
|
|
30
|
+
fn: T
|
|
31
|
+
): T;
|
|
32
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
// ClientCoded JavaScript/TypeScript SDK
|
|
2
|
+
// npm install clientcoded
|
|
3
|
+
|
|
4
|
+
const DEFAULT_API = "https://clientcoded.app.n8n.cloud/webhook/ap-35-trace-ingest";
|
|
5
|
+
|
|
6
|
+
let _config = {
|
|
7
|
+
agentId: null,
|
|
8
|
+
apiKey: null,
|
|
9
|
+
traceUrl: DEFAULT_API,
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
let _conversationId = null;
|
|
13
|
+
let _turn = null;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Configure the SDK globally.
|
|
17
|
+
* @param {Object} config
|
|
18
|
+
* @param {string} config.agentId - Your agent's ID from ClientCoded dashboard
|
|
19
|
+
* @param {string} config.apiKey - Your API key from ClientCoded dashboard
|
|
20
|
+
* @param {string} [config.traceUrl] - Optional custom trace endpoint URL
|
|
21
|
+
*/
|
|
22
|
+
function configure(config) {
|
|
23
|
+
_config.agentId = config.agentId;
|
|
24
|
+
_config.apiKey = config.apiKey;
|
|
25
|
+
if (config.traceUrl) {
|
|
26
|
+
_config.traceUrl = config.traceUrl;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Set the current conversation ID. Call at the start of each conversation.
|
|
32
|
+
* @param {string} conversationId
|
|
33
|
+
*/
|
|
34
|
+
function setConversationId(conversationId) {
|
|
35
|
+
_conversationId = conversationId;
|
|
36
|
+
_turn = 0;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Set the current turn number.
|
|
41
|
+
* @param {number} turnNumber
|
|
42
|
+
*/
|
|
43
|
+
function setTurn(turnNumber) {
|
|
44
|
+
_turn = turnNumber;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Increment the turn counter by 1.
|
|
49
|
+
*/
|
|
50
|
+
function incrementTurn() {
|
|
51
|
+
_turn = (_turn || 0) + 1;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Wrap a function with tracing.
|
|
56
|
+
*
|
|
57
|
+
* Usage:
|
|
58
|
+
* const getInvoice = trace("get_invoice", async (invoiceId) => {
|
|
59
|
+
* return await stripe.invoices.retrieve(invoiceId);
|
|
60
|
+
* });
|
|
61
|
+
*
|
|
62
|
+
* @param {string} name - Name for this traced function
|
|
63
|
+
* @param {Function} fn - The function to trace
|
|
64
|
+
* @returns {Function} - Wrapped function with tracing
|
|
65
|
+
*/
|
|
66
|
+
function trace(name, fn) {
|
|
67
|
+
return async function (...args) {
|
|
68
|
+
const traceId = _uuid();
|
|
69
|
+
const start = Date.now();
|
|
70
|
+
let error = null;
|
|
71
|
+
let result = null;
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
result = await fn(...args);
|
|
75
|
+
return result;
|
|
76
|
+
} catch (e) {
|
|
77
|
+
error = e.message || String(e);
|
|
78
|
+
throw e;
|
|
79
|
+
} finally {
|
|
80
|
+
_sendTrace({
|
|
81
|
+
traceId,
|
|
82
|
+
toolName: name,
|
|
83
|
+
input: _safeSerializeInput(args),
|
|
84
|
+
output: _safeSerializeOutput(result),
|
|
85
|
+
error,
|
|
86
|
+
latencyMs: Date.now() - start,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Wrap a synchronous function with tracing.
|
|
94
|
+
*
|
|
95
|
+
* Usage:
|
|
96
|
+
* const parseData = traceSync("parse_data", (raw) => {
|
|
97
|
+
* return JSON.parse(raw);
|
|
98
|
+
* });
|
|
99
|
+
*
|
|
100
|
+
* @param {string} name - Name for this traced function
|
|
101
|
+
* @param {Function} fn - The synchronous function to trace
|
|
102
|
+
* @returns {Function} - Wrapped function with tracing
|
|
103
|
+
*/
|
|
104
|
+
function traceSync(name, fn) {
|
|
105
|
+
return function (...args) {
|
|
106
|
+
const traceId = _uuid();
|
|
107
|
+
const start = Date.now();
|
|
108
|
+
let error = null;
|
|
109
|
+
let result = null;
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
result = fn(...args);
|
|
113
|
+
return result;
|
|
114
|
+
} catch (e) {
|
|
115
|
+
error = e.message || String(e);
|
|
116
|
+
throw e;
|
|
117
|
+
} finally {
|
|
118
|
+
_sendTrace({
|
|
119
|
+
traceId,
|
|
120
|
+
toolName: name,
|
|
121
|
+
input: _safeSerializeInput(args),
|
|
122
|
+
output: _safeSerializeOutput(result),
|
|
123
|
+
error,
|
|
124
|
+
latencyMs: Date.now() - start,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Manual trace logging for cases where wrappers don't fit.
|
|
132
|
+
*
|
|
133
|
+
* Usage:
|
|
134
|
+
* const start = Date.now();
|
|
135
|
+
* try {
|
|
136
|
+
* const result = await complexOperation();
|
|
137
|
+
* logTrace("complex_op", { input: { key: "val" }, output: result });
|
|
138
|
+
* } catch (e) {
|
|
139
|
+
* logTrace("complex_op", { input: { key: "val" }, error: e.message });
|
|
140
|
+
* }
|
|
141
|
+
*
|
|
142
|
+
* @param {string} toolName
|
|
143
|
+
* @param {Object} data
|
|
144
|
+
* @param {*} [data.input]
|
|
145
|
+
* @param {*} [data.output]
|
|
146
|
+
* @param {string} [data.error]
|
|
147
|
+
* @param {number} [data.latencyMs]
|
|
148
|
+
*/
|
|
149
|
+
function logTrace(toolName, data = {}) {
|
|
150
|
+
_sendTrace({
|
|
151
|
+
traceId: _uuid(),
|
|
152
|
+
toolName,
|
|
153
|
+
input: data.input ? _safeSerializeInput([data.input]) : null,
|
|
154
|
+
output: data.output ? _safeSerializeOutput(data.output) : null,
|
|
155
|
+
error: data.error || null,
|
|
156
|
+
latencyMs: data.latencyMs || null,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Create a traced wrapper for a LangChain tool function.
|
|
162
|
+
*
|
|
163
|
+
* Usage:
|
|
164
|
+
* const searchDb = traceLangChain("search_database", async (query) => {
|
|
165
|
+
* return await db.execute(query);
|
|
166
|
+
* });
|
|
167
|
+
*
|
|
168
|
+
* @param {string} name
|
|
169
|
+
* @param {Function} fn
|
|
170
|
+
* @returns {Function}
|
|
171
|
+
*/
|
|
172
|
+
function traceLangChain(name, fn) {
|
|
173
|
+
return trace(name, fn);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// === Internal functions ===
|
|
177
|
+
|
|
178
|
+
function _sendTrace({ traceId, toolName, input, output, error, latencyMs }) {
|
|
179
|
+
if (!_config.agentId || !_config.apiKey) return;
|
|
180
|
+
|
|
181
|
+
const payload = {
|
|
182
|
+
agent_id: _config.agentId,
|
|
183
|
+
api_key: _config.apiKey,
|
|
184
|
+
trace_id: traceId,
|
|
185
|
+
conversation_id: _conversationId,
|
|
186
|
+
turn: _turn,
|
|
187
|
+
tool_name: toolName,
|
|
188
|
+
input: input,
|
|
189
|
+
output: output,
|
|
190
|
+
error: error,
|
|
191
|
+
latency_ms: latencyMs,
|
|
192
|
+
timestamp: Date.now() / 1000,
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
// Fire and forget
|
|
196
|
+
try {
|
|
197
|
+
const controller = new AbortController();
|
|
198
|
+
const timeout = setTimeout(() => controller.abort(), 2000);
|
|
199
|
+
|
|
200
|
+
fetch(_config.traceUrl, {
|
|
201
|
+
method: "POST",
|
|
202
|
+
headers: { "Content-Type": "application/json" },
|
|
203
|
+
body: JSON.stringify(payload),
|
|
204
|
+
signal: controller.signal,
|
|
205
|
+
})
|
|
206
|
+
.catch(() => {})
|
|
207
|
+
.finally(() => clearTimeout(timeout));
|
|
208
|
+
} catch {
|
|
209
|
+
// Never break the customer's agent
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function _safeSerializeInput(args) {
|
|
214
|
+
try {
|
|
215
|
+
const serialized = args.map((a) => _truncate(JSON.stringify(a)));
|
|
216
|
+
return { args: serialized };
|
|
217
|
+
} catch {
|
|
218
|
+
return { error: "unserializable" };
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function _safeSerializeOutput(result) {
|
|
223
|
+
if (result === null || result === undefined) return null;
|
|
224
|
+
try {
|
|
225
|
+
return _truncate(JSON.stringify(result));
|
|
226
|
+
} catch {
|
|
227
|
+
return "unserializable";
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function _truncate(s, maxLen = 2000) {
|
|
232
|
+
if (s.length > maxLen) {
|
|
233
|
+
return s.slice(0, maxLen) + "... (truncated)";
|
|
234
|
+
}
|
|
235
|
+
return s;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function _uuid() {
|
|
239
|
+
if (typeof crypto !== "undefined" && crypto.randomUUID) {
|
|
240
|
+
return crypto.randomUUID();
|
|
241
|
+
}
|
|
242
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
243
|
+
const r = (Math.random() * 16) | 0;
|
|
244
|
+
const v = c === "x" ? r : (r & 0x3) | 0x8;
|
|
245
|
+
return v.toString(16);
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
module.exports = {
|
|
250
|
+
configure,
|
|
251
|
+
setConversationId,
|
|
252
|
+
setTurn,
|
|
253
|
+
incrementTurn,
|
|
254
|
+
trace,
|
|
255
|
+
traceSync,
|
|
256
|
+
logTrace,
|
|
257
|
+
traceLangChain,
|
|
258
|
+
};
|