neatlogs 1.0.6 → 1.0.8
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/azure-openai.cjs +344 -0
- package/dist/azure-openai.cjs.map +1 -0
- package/dist/azure-openai.d.ts +30 -0
- package/dist/azure-openai.mjs +318 -0
- package/dist/azure-openai.mjs.map +1 -0
- package/dist/bedrock.cjs +540 -0
- package/dist/bedrock.cjs.map +1 -0
- package/dist/bedrock.d.ts +29 -0
- package/dist/bedrock.mjs +514 -0
- package/dist/bedrock.mjs.map +1 -0
- package/dist/browser.cjs +121 -0
- package/dist/browser.cjs.map +1 -0
- package/dist/browser.d.ts +143 -0
- package/dist/browser.mjs +96 -0
- package/dist/browser.mjs.map +1 -0
- package/dist/claude-agent-sdk.cjs +406 -0
- package/dist/claude-agent-sdk.cjs.map +1 -0
- package/dist/claude-agent-sdk.d.ts +49 -0
- package/dist/claude-agent-sdk.mjs +381 -0
- package/dist/claude-agent-sdk.mjs.map +1 -0
- package/dist/index.cjs +3219 -1129
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.mjs +3210 -1131
- package/dist/index.mjs.map +1 -1
- package/dist/langchain.cjs +9 -9
- package/dist/langchain.cjs.map +1 -1
- package/dist/langchain.mjs +9 -9
- package/dist/langchain.mjs.map +1 -1
- package/dist/mastra-wrap.cjs +25 -25
- package/dist/mastra-wrap.cjs.map +1 -1
- package/dist/mastra-wrap.mjs +25 -25
- package/dist/mastra-wrap.mjs.map +1 -1
- package/dist/openai-agents.cjs +6 -6
- package/dist/openai-agents.cjs.map +1 -1
- package/dist/openai-agents.mjs +6 -6
- package/dist/openai-agents.mjs.map +1 -1
- package/dist/opencode-plugin.cjs +5879 -0
- package/dist/opencode-plugin.cjs.map +1 -0
- package/dist/opencode-plugin.d.ts +38 -0
- package/dist/opencode-plugin.mjs +5854 -0
- package/dist/opencode-plugin.mjs.map +1 -0
- package/dist/openrouter-agent.cjs +273 -0
- package/dist/openrouter-agent.cjs.map +1 -0
- package/dist/openrouter-agent.d.ts +34 -0
- package/dist/openrouter-agent.mjs +247 -0
- package/dist/openrouter-agent.mjs.map +1 -0
- package/dist/pi-agent.cjs +10 -10
- package/dist/pi-agent.cjs.map +1 -1
- package/dist/pi-agent.mjs +10 -10
- package/dist/pi-agent.mjs.map +1 -1
- package/dist/vertex-ai.cjs +424 -0
- package/dist/vertex-ai.cjs.map +1 -0
- package/dist/vertex-ai.d.ts +39 -0
- package/dist/vertex-ai.mjs +397 -0
- package/dist/vertex-ai.mjs.map +1 -0
- package/package.json +76 -1
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/openrouter-agent.ts
|
|
21
|
+
var openrouter_agent_exports = {};
|
|
22
|
+
__export(openrouter_agent_exports, {
|
|
23
|
+
wrapCallModel: () => wrapCallModel,
|
|
24
|
+
wrapOpenRouterAgent: () => wrapOpenRouterAgent
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(openrouter_agent_exports);
|
|
27
|
+
var import_api = require("@opentelemetry/api");
|
|
28
|
+
var TRACER_NAME = "neatlogs.openrouter_agent";
|
|
29
|
+
var PROVIDER = "openrouter";
|
|
30
|
+
function wrapOpenRouterAgent(client) {
|
|
31
|
+
const c = client;
|
|
32
|
+
if (!c || c._neatlogsWrapped) return client;
|
|
33
|
+
return new Proxy(client, {
|
|
34
|
+
get(obj, prop, receiver) {
|
|
35
|
+
const value = Reflect.get(obj, prop, receiver);
|
|
36
|
+
if (prop === "callModel" && typeof value === "function") {
|
|
37
|
+
return tracedCallModel(value.bind(obj));
|
|
38
|
+
}
|
|
39
|
+
return value;
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
function wrapCallModel(callModel) {
|
|
44
|
+
return function(clientArg, opts, ...rest) {
|
|
45
|
+
const span = startLlmSpan(opts);
|
|
46
|
+
const ctx = import_api.trace.setSpan(import_api.context.active(), span);
|
|
47
|
+
const result = import_api.context.with(ctx, () => callModel.call(this, clientArg, opts, ...rest));
|
|
48
|
+
return instrumentModelResult(result, span);
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function tracedCallModel(original) {
|
|
52
|
+
return function(opts, ...rest) {
|
|
53
|
+
const span = startLlmSpan(opts);
|
|
54
|
+
const ctx = import_api.trace.setSpan(import_api.context.active(), span);
|
|
55
|
+
const result = import_api.context.with(ctx, () => original(opts, ...rest));
|
|
56
|
+
return instrumentModelResult(result, span);
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function startLlmSpan(opts) {
|
|
60
|
+
const tracer = import_api.trace.getTracer(TRACER_NAME);
|
|
61
|
+
const model = opts?.model ?? "";
|
|
62
|
+
const span = tracer.startSpan("openrouter.call_model", {
|
|
63
|
+
attributes: {
|
|
64
|
+
"neatlogs.span.kind": "LLM",
|
|
65
|
+
"neatlogs.llm.provider": PROVIDER,
|
|
66
|
+
"neatlogs.llm.system": PROVIDER,
|
|
67
|
+
"neatlogs.llm.model_name": model
|
|
68
|
+
}
|
|
69
|
+
}, import_api.context.active());
|
|
70
|
+
const messages = Array.isArray(opts?.messages) ? opts.messages : Array.isArray(opts?.input) ? opts.input : [];
|
|
71
|
+
if (messages.length) {
|
|
72
|
+
messages.forEach((msg, i) => {
|
|
73
|
+
span.setAttribute(`neatlogs.llm.input_messages.${i}.role`, msg?.role ?? "user");
|
|
74
|
+
const content = msg?.content;
|
|
75
|
+
span.setAttribute(
|
|
76
|
+
`neatlogs.llm.input_messages.${i}.content`,
|
|
77
|
+
typeof content === "string" ? content : safeStringify(content)
|
|
78
|
+
);
|
|
79
|
+
});
|
|
80
|
+
span.setAttribute("input.value", safeStringify({ messages }));
|
|
81
|
+
} else if (typeof opts?.input === "string") {
|
|
82
|
+
span.setAttribute("neatlogs.llm.input_messages.0.role", "user");
|
|
83
|
+
span.setAttribute("neatlogs.llm.input_messages.0.content", opts.input);
|
|
84
|
+
span.setAttribute("input.value", opts.input);
|
|
85
|
+
}
|
|
86
|
+
if (typeof opts?.instructions === "string" && opts.instructions) {
|
|
87
|
+
span.setAttribute("neatlogs.llm.system_prompt", opts.instructions);
|
|
88
|
+
}
|
|
89
|
+
if (Array.isArray(opts?.tools)) {
|
|
90
|
+
for (let i = 0; i < opts.tools.length; i++) {
|
|
91
|
+
const t = opts.tools[i] ?? {};
|
|
92
|
+
const name = t.name ?? t.function?.name;
|
|
93
|
+
if (name) span.setAttribute(`neatlogs.llm.tools.${i}.name`, name);
|
|
94
|
+
const desc = t.description ?? t.function?.description;
|
|
95
|
+
if (desc) span.setAttribute(`neatlogs.llm.tools.${i}.description`, desc);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
const temperature = opts?.temperature;
|
|
99
|
+
const topP = opts?.top_p ?? opts?.topP;
|
|
100
|
+
const maxTokens = opts?.max_tokens ?? opts?.maxTokens ?? opts?.max_output_tokens ?? opts?.maxOutputTokens;
|
|
101
|
+
const frequencyPenalty = opts?.frequency_penalty ?? opts?.frequencyPenalty;
|
|
102
|
+
const presencePenalty = opts?.presence_penalty ?? opts?.presencePenalty;
|
|
103
|
+
const topK = opts?.top_k ?? opts?.topK;
|
|
104
|
+
if (temperature != null) span.setAttribute("neatlogs.llm.temperature", temperature);
|
|
105
|
+
if (topP != null) span.setAttribute("neatlogs.llm.top_p", topP);
|
|
106
|
+
if (maxTokens != null) span.setAttribute("neatlogs.llm.max_tokens", maxTokens);
|
|
107
|
+
const params = {};
|
|
108
|
+
if (temperature != null) params.temperature = temperature;
|
|
109
|
+
if (topP != null) params.top_p = topP;
|
|
110
|
+
if (maxTokens != null) params.max_tokens = maxTokens;
|
|
111
|
+
if (frequencyPenalty != null) params.frequency_penalty = frequencyPenalty;
|
|
112
|
+
if (presencePenalty != null) params.presence_penalty = presencePenalty;
|
|
113
|
+
if (topK != null) params.top_k = topK;
|
|
114
|
+
if (Object.keys(params).length) {
|
|
115
|
+
span.setAttribute("neatlogs.llm.invocation_parameters", JSON.stringify(params));
|
|
116
|
+
}
|
|
117
|
+
return span;
|
|
118
|
+
}
|
|
119
|
+
function instrumentModelResult(result, span) {
|
|
120
|
+
if (!result || typeof result !== "object" && typeof result !== "function") {
|
|
121
|
+
span.setStatus({ code: import_api.SpanStatusCode.OK });
|
|
122
|
+
span.end();
|
|
123
|
+
return result;
|
|
124
|
+
}
|
|
125
|
+
let finalized = false;
|
|
126
|
+
const finalizeFromResult = (resolved) => {
|
|
127
|
+
if (finalized) return;
|
|
128
|
+
finalized = true;
|
|
129
|
+
try {
|
|
130
|
+
finalizeLlm(span, resolved);
|
|
131
|
+
} catch {
|
|
132
|
+
span.setStatus({ code: import_api.SpanStatusCode.OK });
|
|
133
|
+
span.end();
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
const finalizeError = (err) => {
|
|
137
|
+
if (finalized) return;
|
|
138
|
+
finalized = true;
|
|
139
|
+
recordError(span, err);
|
|
140
|
+
};
|
|
141
|
+
return new Proxy(result, {
|
|
142
|
+
get(obj, prop, receiver) {
|
|
143
|
+
const value = Reflect.get(obj, prop, receiver);
|
|
144
|
+
if (prop === "getResponse" && typeof value === "function") {
|
|
145
|
+
return function(...args) {
|
|
146
|
+
return Promise.resolve(value.apply(obj, args)).then(
|
|
147
|
+
(resp) => {
|
|
148
|
+
finalizeFromResult(resp);
|
|
149
|
+
return resp;
|
|
150
|
+
},
|
|
151
|
+
(err) => {
|
|
152
|
+
finalizeError(err);
|
|
153
|
+
throw err;
|
|
154
|
+
}
|
|
155
|
+
);
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
if (prop === "getText" && typeof value === "function") {
|
|
159
|
+
return function(...args) {
|
|
160
|
+
return Promise.resolve(value.apply(obj, args)).then(
|
|
161
|
+
async (textStr) => {
|
|
162
|
+
let resp;
|
|
163
|
+
if (typeof obj?.getResponse === "function") {
|
|
164
|
+
try {
|
|
165
|
+
resp = await obj.getResponse();
|
|
166
|
+
} catch {
|
|
167
|
+
resp = void 0;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
finalizeFromResult(resp ? { ...resp, text: textStr } : { text: textStr });
|
|
171
|
+
return textStr;
|
|
172
|
+
},
|
|
173
|
+
(err) => {
|
|
174
|
+
finalizeError(err);
|
|
175
|
+
throw err;
|
|
176
|
+
}
|
|
177
|
+
);
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
if (prop === "then" && typeof value === "function") {
|
|
181
|
+
return function(onFulfilled, onRejected) {
|
|
182
|
+
return value.call(
|
|
183
|
+
obj,
|
|
184
|
+
(resolved) => {
|
|
185
|
+
finalizeFromResult(typeof resolved === "string" ? { text: resolved } : resolved ?? obj);
|
|
186
|
+
return onFulfilled ? onFulfilled(resolved) : resolved;
|
|
187
|
+
},
|
|
188
|
+
(err) => {
|
|
189
|
+
finalizeError(err);
|
|
190
|
+
return onRejected ? onRejected(err) : Promise.reject(err);
|
|
191
|
+
}
|
|
192
|
+
);
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
return value;
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
function setOpenResponsesUsage(span, resp) {
|
|
200
|
+
const u = resp?.usage ?? resp;
|
|
201
|
+
if (!u) return;
|
|
202
|
+
const input = u.inputTokens ?? u.input_tokens ?? u.prompt_tokens;
|
|
203
|
+
const output = u.outputTokens ?? u.output_tokens ?? u.completion_tokens;
|
|
204
|
+
const total = u.totalTokens ?? u.total_tokens;
|
|
205
|
+
const cached = u.cachedTokens ?? u.cached_tokens;
|
|
206
|
+
if (input != null) span.setAttribute("neatlogs.llm.token_count.prompt", input);
|
|
207
|
+
if (output != null) span.setAttribute("neatlogs.llm.token_count.completion", output);
|
|
208
|
+
if (total != null) span.setAttribute("neatlogs.llm.token_count.total", total);
|
|
209
|
+
else if (input != null && output != null) span.setAttribute("neatlogs.llm.token_count.total", input + output);
|
|
210
|
+
if (cached != null) span.setAttribute("neatlogs.llm.token_count.cache_read", cached);
|
|
211
|
+
}
|
|
212
|
+
function finalizeLlm(span, result) {
|
|
213
|
+
const text = result?.text ?? result?.output_text ?? result?.content ?? extractOpenResponsesText(result) ?? result?.choices?.[0]?.message?.content ?? result?.message?.content;
|
|
214
|
+
if (text) {
|
|
215
|
+
span.setAttribute("neatlogs.llm.output_messages.0.role", "assistant");
|
|
216
|
+
span.setAttribute("neatlogs.llm.output_messages.0.content", String(text));
|
|
217
|
+
span.setAttribute("output.value", String(text));
|
|
218
|
+
}
|
|
219
|
+
const toolCalls = result?.toolCalls ?? result?.tool_calls ?? result?.choices?.[0]?.message?.tool_calls ?? result?.message?.tool_calls;
|
|
220
|
+
if (Array.isArray(toolCalls)) {
|
|
221
|
+
toolCalls.forEach((tc, j) => {
|
|
222
|
+
span.setAttribute(`neatlogs.llm.tool_calls.${j}.id`, tc?.id ?? "");
|
|
223
|
+
span.setAttribute(`neatlogs.llm.tool_calls.${j}.name`, tc?.function?.name ?? tc?.name ?? "");
|
|
224
|
+
const args = tc?.function?.arguments ?? tc?.arguments;
|
|
225
|
+
span.setAttribute(`neatlogs.llm.tool_calls.${j}.arguments`, typeof args === "string" ? args : safeStringify(args ?? {}));
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
const model = result?.model ?? result?.response?.model;
|
|
229
|
+
if (model) span.setAttribute("neatlogs.llm.model_name", String(model));
|
|
230
|
+
const responseId = result?.id ?? result?.response?.id;
|
|
231
|
+
if (responseId) span.setAttribute("neatlogs.llm.response_id", String(responseId));
|
|
232
|
+
const finishReason = result?.finishReason ?? result?.finish_reason ?? result?.choices?.[0]?.finish_reason;
|
|
233
|
+
if (finishReason) span.setAttribute("neatlogs.llm.finish_reason", String(finishReason));
|
|
234
|
+
setOpenResponsesUsage(span, result);
|
|
235
|
+
span.setStatus({ code: import_api.SpanStatusCode.OK });
|
|
236
|
+
span.end();
|
|
237
|
+
}
|
|
238
|
+
function extractOpenResponsesText(result) {
|
|
239
|
+
const output = result?.output;
|
|
240
|
+
if (!Array.isArray(output)) return void 0;
|
|
241
|
+
const parts = [];
|
|
242
|
+
for (const item of output) {
|
|
243
|
+
if (item?.type === "message" && Array.isArray(item.content)) {
|
|
244
|
+
for (const c of item.content) {
|
|
245
|
+
if ((c?.type === "output_text" || c?.type === "text") && typeof c.text === "string") parts.push(c.text);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return parts.length ? parts.join("") : void 0;
|
|
250
|
+
}
|
|
251
|
+
function safeStringify(value) {
|
|
252
|
+
if (typeof value === "string") return value;
|
|
253
|
+
try {
|
|
254
|
+
return JSON.stringify(value) ?? "";
|
|
255
|
+
} catch {
|
|
256
|
+
return "";
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
function recordError(span, err) {
|
|
260
|
+
if (err instanceof Error) {
|
|
261
|
+
span.setStatus({ code: import_api.SpanStatusCode.ERROR, message: err.message });
|
|
262
|
+
span.recordException(err);
|
|
263
|
+
} else {
|
|
264
|
+
span.setStatus({ code: import_api.SpanStatusCode.ERROR, message: String(err) });
|
|
265
|
+
}
|
|
266
|
+
span.end();
|
|
267
|
+
}
|
|
268
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
269
|
+
0 && (module.exports = {
|
|
270
|
+
wrapCallModel,
|
|
271
|
+
wrapOpenRouterAgent
|
|
272
|
+
});
|
|
273
|
+
//# sourceMappingURL=openrouter-agent.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/openrouter-agent.ts"],"sourcesContent":["/**\n * Neatlogs OpenRouter Agent integration.\n *\n * Wraps the `@openrouter/agent` SDK so each `client.callModel(...)` is traced.\n * The OpenRouter client returns a `ModelResult`; telemetry is finalized when the\n * result is consumed (via `getText()` / awaiting it / iterating it), matching the\n * SDK's lazy-evaluation model — a result that is never consumed ships no span.\n *\n * Usage:\n * import { init } from 'neatlogs';\n * import { wrapOpenRouterAgent } from 'neatlogs/openrouter-agent';\n * import { OpenRouter } from '@openrouter/agent';\n *\n * await init({ apiKey, workflowName });\n * const openrouter = wrapOpenRouterAgent(new OpenRouter({ apiKey: process.env.OPENROUTER_API_KEY }));\n * const result = openrouter.callModel({ model: 'openai/gpt-4o', messages: [...] });\n * const text = await result.getText();\n *\n * Also exports wrapCallModel() for the standalone callModel helper:\n * const trackedCallModel = wrapCallModel(callModel);\n * const result = trackedCallModel(openrouter, { model, messages });\n */\n\nimport { trace, context as otelContext, SpanStatusCode, type Span } from '@opentelemetry/api';\n\nconst TRACER_NAME = 'neatlogs.openrouter_agent';\nconst PROVIDER = 'openrouter';\n\n/**\n * Wrap an OpenRouter client instance so `callModel` emits LLM spans. Returns a\n * Proxy over the client; all other methods pass through unchanged.\n */\nexport function wrapOpenRouterAgent<T extends object>(client: T): T {\n const c = client as any;\n if (!c || c._neatlogsWrapped) return client;\n\n return new Proxy(client, {\n get(obj, prop, receiver) {\n const value = Reflect.get(obj, prop, receiver);\n if (prop === 'callModel' && typeof value === 'function') {\n return tracedCallModel(value.bind(obj));\n }\n return value;\n },\n }) as T;\n}\n\n/**\n * Wrap the standalone `callModel(client, opts)` helper. The wrapped function is\n * invoked with the client passed in: `trackedCallModel(openrouter, {...})`.\n */\nexport function wrapCallModel<F extends (...args: any[]) => any>(callModel: F): F {\n return function (this: any, clientArg: any, opts: any, ...rest: any[]): any {\n const span = startLlmSpan(opts);\n const ctx = trace.setSpan(otelContext.active(), span);\n const result = otelContext.with(ctx, () => callModel.call(this, clientArg, opts, ...rest));\n return instrumentModelResult(result, span);\n } as unknown as F;\n}\n\n// ---------------------------------------------------------------------------\n// callModel wrapping\n// ---------------------------------------------------------------------------\n\nfunction tracedCallModel(original: (...args: any[]) => any) {\n return function (opts: any, ...rest: any[]): any {\n const span = startLlmSpan(opts);\n const ctx = trace.setSpan(otelContext.active(), span);\n const result = otelContext.with(ctx, () => original(opts, ...rest));\n return instrumentModelResult(result, span);\n };\n}\n\nfunction startLlmSpan(opts: any): Span {\n const tracer = trace.getTracer(TRACER_NAME);\n const model = opts?.model ?? '';\n\n const span = tracer.startSpan('openrouter.call_model', {\n attributes: {\n 'neatlogs.span.kind': 'LLM',\n 'neatlogs.llm.provider': PROVIDER,\n 'neatlogs.llm.system': PROVIDER,\n 'neatlogs.llm.model_name': model,\n },\n }, otelContext.active());\n\n // The @openrouter/agent callModel input is `input` — either a string prompt or\n // a messages array (it also accepts `messages`/`instructions` in some shapes).\n // Capture whichever is present as indexed input messages + the flat input.value\n // blob (the canonical UI-rendered field, per attribute-mapping.json).\n const messages: any[] = Array.isArray(opts?.messages)\n ? opts.messages\n : Array.isArray(opts?.input)\n ? opts.input\n : [];\n if (messages.length) {\n messages.forEach((msg, i) => {\n span.setAttribute(`neatlogs.llm.input_messages.${i}.role`, msg?.role ?? 'user');\n const content = msg?.content;\n span.setAttribute(\n `neatlogs.llm.input_messages.${i}.content`,\n typeof content === 'string' ? content : safeStringify(content),\n );\n });\n span.setAttribute('input.value', safeStringify({ messages }));\n } else if (typeof opts?.input === 'string') {\n span.setAttribute('neatlogs.llm.input_messages.0.role', 'user');\n span.setAttribute('neatlogs.llm.input_messages.0.content', opts.input);\n span.setAttribute('input.value', opts.input);\n }\n if (typeof opts?.instructions === 'string' && opts.instructions) {\n span.setAttribute('neatlogs.llm.system_prompt', opts.instructions);\n }\n\n if (Array.isArray(opts?.tools)) {\n for (let i = 0; i < opts.tools.length; i++) {\n const t = opts.tools[i] ?? {};\n const name = t.name ?? t.function?.name;\n if (name) span.setAttribute(`neatlogs.llm.tools.${i}.name`, name);\n const desc = t.description ?? t.function?.description;\n if (desc) span.setAttribute(`neatlogs.llm.tools.${i}.description`, desc);\n }\n }\n\n // @openrouter/agent's callModel request uses camelCase sampling params\n // (topP, maxOutputTokens, frequencyPenalty, presencePenalty, topK) derived\n // from the OpenResponses ResponsesRequest. Accept both camelCase and the\n // snake_case OpenAI-style variants so params are captured regardless of how\n // the caller spelled them.\n const temperature = opts?.temperature;\n const topP = opts?.top_p ?? opts?.topP;\n const maxTokens = opts?.max_tokens ?? opts?.maxTokens ?? opts?.max_output_tokens ?? opts?.maxOutputTokens;\n const frequencyPenalty = opts?.frequency_penalty ?? opts?.frequencyPenalty;\n const presencePenalty = opts?.presence_penalty ?? opts?.presencePenalty;\n const topK = opts?.top_k ?? opts?.topK;\n\n if (temperature != null) span.setAttribute('neatlogs.llm.temperature', temperature);\n if (topP != null) span.setAttribute('neatlogs.llm.top_p', topP);\n if (maxTokens != null) span.setAttribute('neatlogs.llm.max_tokens', maxTokens);\n\n // The backend reads invocation params ONLY from this JSON-string blob (parsed\n // into metadata.model_settings, which the UI renders). Individual attrs above\n // are kept for other consumers but are NOT what the UI shows. Only include\n // keys that are actually present so we never emit nulls.\n const params: Record<string, unknown> = {};\n if (temperature != null) params.temperature = temperature;\n if (topP != null) params.top_p = topP;\n if (maxTokens != null) params.max_tokens = maxTokens;\n if (frequencyPenalty != null) params.frequency_penalty = frequencyPenalty;\n if (presencePenalty != null) params.presence_penalty = presencePenalty;\n if (topK != null) params.top_k = topK;\n if (Object.keys(params).length) {\n span.setAttribute('neatlogs.llm.invocation_parameters', JSON.stringify(params));\n }\n\n return span;\n}\n\n/**\n * Wrap a ModelResult so the span is finalized exactly once, when the caller\n * consumes the result. We patch the common consumption methods (getText,\n * getMessage, then) so finalization happens on first use; a result that is\n * never consumed never finalizes (matching SDK semantics).\n */\nfunction instrumentModelResult(result: any, span: Span): any {\n if (!result || (typeof result !== 'object' && typeof result !== 'function')) {\n // Synchronous/primitive return — nothing to defer; close immediately.\n span.setStatus({ code: SpanStatusCode.OK });\n span.end();\n return result;\n }\n\n let finalized = false;\n const finalizeFromResult = (resolved: any) => {\n if (finalized) return;\n finalized = true;\n try {\n finalizeLlm(span, resolved);\n } catch {\n span.setStatus({ code: SpanStatusCode.OK });\n span.end();\n }\n };\n const finalizeError = (err: unknown) => {\n if (finalized) return;\n finalized = true;\n recordError(span, err);\n };\n\n // The @openrouter/agent ModelResult is consumed via getText()/getResponse()/\n // getTextStream(). Wrap with a Proxy to intercept whichever the caller uses\n // first; finalize the span from that, and grab usage via getResponse().\n return new Proxy(result, {\n get(obj, prop, receiver) {\n const value = Reflect.get(obj, prop, receiver);\n\n if (prop === 'getResponse' && typeof value === 'function') {\n return function (...args: any[]) {\n return Promise.resolve(value.apply(obj, args)).then(\n (resp: any) => {\n finalizeFromResult(resp); // full OpenResponsesResult (text + usage)\n return resp;\n },\n (err: any) => {\n finalizeError(err);\n throw err;\n },\n );\n };\n }\n\n if (prop === 'getText' && typeof value === 'function') {\n return function (...args: any[]) {\n return Promise.resolve(value.apply(obj, args)).then(\n async (textStr: any) => {\n // getText() resolves to a bare string (no usage). Usage lives on\n // getResponse(); fetch it FIRST (the SDK supports concurrent\n // consumption) so the span carries tokens — then finalize ONCE.\n // Finalizing ends the span, so late attributes would be dropped.\n let resp: any;\n if (typeof obj?.getResponse === 'function') {\n try {\n resp = await obj.getResponse();\n } catch {\n resp = undefined;\n }\n }\n finalizeFromResult(resp ? { ...resp, text: textStr } : { text: textStr });\n return textStr;\n },\n (err: any) => {\n finalizeError(err);\n throw err;\n },\n );\n };\n }\n\n if (prop === 'then' && typeof value === 'function') {\n // Result is awaitable directly: await result.\n return function (onFulfilled?: any, onRejected?: any) {\n return value.call(\n obj,\n (resolved: any) => {\n finalizeFromResult(typeof resolved === 'string' ? { text: resolved } : (resolved ?? obj));\n return onFulfilled ? onFulfilled(resolved) : resolved;\n },\n (err: any) => {\n finalizeError(err);\n return onRejected ? onRejected(err) : Promise.reject(err);\n },\n );\n };\n }\n\n return value;\n },\n });\n}\n\n/** Stamp usage from an @openrouter/agent OpenResponsesResult (inputTokens/outputTokens/...). */\nfunction setOpenResponsesUsage(span: Span, resp: any): void {\n const u = resp?.usage ?? resp;\n if (!u) return;\n const input = u.inputTokens ?? u.input_tokens ?? u.prompt_tokens;\n const output = u.outputTokens ?? u.output_tokens ?? u.completion_tokens;\n const total = u.totalTokens ?? u.total_tokens;\n const cached = u.cachedTokens ?? u.cached_tokens;\n if (input != null) span.setAttribute('neatlogs.llm.token_count.prompt', input);\n if (output != null) span.setAttribute('neatlogs.llm.token_count.completion', output);\n if (total != null) span.setAttribute('neatlogs.llm.token_count.total', total);\n else if (input != null && output != null) span.setAttribute('neatlogs.llm.token_count.total', input + output);\n if (cached != null) span.setAttribute('neatlogs.llm.token_count.cache_read', cached);\n}\n\nfunction finalizeLlm(span: Span, result: any): void {\n // Text output. getText() gives a bare string (wrapped here as {text}); the\n // OpenResponsesResult from getResponse() carries text under output[]/output_text.\n const text =\n result?.text ??\n result?.output_text ??\n result?.content ??\n extractOpenResponsesText(result) ??\n result?.choices?.[0]?.message?.content ??\n result?.message?.content;\n if (text) {\n span.setAttribute('neatlogs.llm.output_messages.0.role', 'assistant');\n span.setAttribute('neatlogs.llm.output_messages.0.content', String(text));\n span.setAttribute('output.value', String(text));\n }\n\n // Tool calls (OpenAI-compatible shape; OpenResponses uses output[] function_call items).\n const toolCalls =\n result?.toolCalls ??\n result?.tool_calls ??\n result?.choices?.[0]?.message?.tool_calls ??\n result?.message?.tool_calls;\n if (Array.isArray(toolCalls)) {\n toolCalls.forEach((tc: any, j: number) => {\n span.setAttribute(`neatlogs.llm.tool_calls.${j}.id`, tc?.id ?? '');\n span.setAttribute(`neatlogs.llm.tool_calls.${j}.name`, tc?.function?.name ?? tc?.name ?? '');\n const args = tc?.function?.arguments ?? tc?.arguments;\n span.setAttribute(`neatlogs.llm.tool_calls.${j}.arguments`, typeof args === 'string' ? args : safeStringify(args ?? {}));\n });\n }\n\n const model = result?.model ?? result?.response?.model;\n if (model) span.setAttribute('neatlogs.llm.model_name', String(model));\n\n const responseId = result?.id ?? result?.response?.id;\n if (responseId) span.setAttribute('neatlogs.llm.response_id', String(responseId));\n\n const finishReason = result?.finishReason ?? result?.finish_reason ?? result?.choices?.[0]?.finish_reason;\n if (finishReason) span.setAttribute('neatlogs.llm.finish_reason', String(finishReason));\n\n // Usage — OpenResponsesResult uses inputTokens/outputTokens; also handle the\n // OpenAI-compatible prompt_tokens/completion_tokens shape.\n setOpenResponsesUsage(span, result);\n\n span.setStatus({ code: SpanStatusCode.OK });\n span.end();\n}\n\n/** Pull assistant text out of an OpenResponses-style result (output[] of message items). */\nfunction extractOpenResponsesText(result: any): string | undefined {\n const output = result?.output;\n if (!Array.isArray(output)) return undefined;\n const parts: string[] = [];\n for (const item of output) {\n if (item?.type === 'message' && Array.isArray(item.content)) {\n for (const c of item.content) {\n if ((c?.type === 'output_text' || c?.type === 'text') && typeof c.text === 'string') parts.push(c.text);\n }\n }\n }\n return parts.length ? parts.join('') : undefined;\n}\n\nfunction safeStringify(value: unknown): string {\n if (typeof value === 'string') return value;\n try {\n return JSON.stringify(value) ?? '';\n } catch {\n return '';\n }\n}\n\nfunction recordError(span: Span, err: unknown): void {\n if (err instanceof Error) {\n span.setStatus({ code: SpanStatusCode.ERROR, message: err.message });\n span.recordException(err);\n } else {\n span.setStatus({ code: SpanStatusCode.ERROR, message: String(err) });\n }\n span.end();\n}\n"],"mappings":"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","names":["otelContext"]}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Neatlogs OpenRouter Agent integration.
|
|
3
|
+
*
|
|
4
|
+
* Wraps the `@openrouter/agent` SDK so each `client.callModel(...)` is traced.
|
|
5
|
+
* The OpenRouter client returns a `ModelResult`; telemetry is finalized when the
|
|
6
|
+
* result is consumed (via `getText()` / awaiting it / iterating it), matching the
|
|
7
|
+
* SDK's lazy-evaluation model — a result that is never consumed ships no span.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* import { init } from 'neatlogs';
|
|
11
|
+
* import { wrapOpenRouterAgent } from 'neatlogs/openrouter-agent';
|
|
12
|
+
* import { OpenRouter } from '@openrouter/agent';
|
|
13
|
+
*
|
|
14
|
+
* await init({ apiKey, workflowName });
|
|
15
|
+
* const openrouter = wrapOpenRouterAgent(new OpenRouter({ apiKey: process.env.OPENROUTER_API_KEY }));
|
|
16
|
+
* const result = openrouter.callModel({ model: 'openai/gpt-4o', messages: [...] });
|
|
17
|
+
* const text = await result.getText();
|
|
18
|
+
*
|
|
19
|
+
* Also exports wrapCallModel() for the standalone callModel helper:
|
|
20
|
+
* const trackedCallModel = wrapCallModel(callModel);
|
|
21
|
+
* const result = trackedCallModel(openrouter, { model, messages });
|
|
22
|
+
*/
|
|
23
|
+
/**
|
|
24
|
+
* Wrap an OpenRouter client instance so `callModel` emits LLM spans. Returns a
|
|
25
|
+
* Proxy over the client; all other methods pass through unchanged.
|
|
26
|
+
*/
|
|
27
|
+
declare function wrapOpenRouterAgent<T extends object>(client: T): T;
|
|
28
|
+
/**
|
|
29
|
+
* Wrap the standalone `callModel(client, opts)` helper. The wrapped function is
|
|
30
|
+
* invoked with the client passed in: `trackedCallModel(openrouter, {...})`.
|
|
31
|
+
*/
|
|
32
|
+
declare function wrapCallModel<F extends (...args: any[]) => any>(callModel: F): F;
|
|
33
|
+
|
|
34
|
+
export { wrapCallModel, wrapOpenRouterAgent };
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
// src/openrouter-agent.ts
|
|
2
|
+
import { trace, context as otelContext, SpanStatusCode } from "@opentelemetry/api";
|
|
3
|
+
var TRACER_NAME = "neatlogs.openrouter_agent";
|
|
4
|
+
var PROVIDER = "openrouter";
|
|
5
|
+
function wrapOpenRouterAgent(client) {
|
|
6
|
+
const c = client;
|
|
7
|
+
if (!c || c._neatlogsWrapped) return client;
|
|
8
|
+
return new Proxy(client, {
|
|
9
|
+
get(obj, prop, receiver) {
|
|
10
|
+
const value = Reflect.get(obj, prop, receiver);
|
|
11
|
+
if (prop === "callModel" && typeof value === "function") {
|
|
12
|
+
return tracedCallModel(value.bind(obj));
|
|
13
|
+
}
|
|
14
|
+
return value;
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
function wrapCallModel(callModel) {
|
|
19
|
+
return function(clientArg, opts, ...rest) {
|
|
20
|
+
const span = startLlmSpan(opts);
|
|
21
|
+
const ctx = trace.setSpan(otelContext.active(), span);
|
|
22
|
+
const result = otelContext.with(ctx, () => callModel.call(this, clientArg, opts, ...rest));
|
|
23
|
+
return instrumentModelResult(result, span);
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function tracedCallModel(original) {
|
|
27
|
+
return function(opts, ...rest) {
|
|
28
|
+
const span = startLlmSpan(opts);
|
|
29
|
+
const ctx = trace.setSpan(otelContext.active(), span);
|
|
30
|
+
const result = otelContext.with(ctx, () => original(opts, ...rest));
|
|
31
|
+
return instrumentModelResult(result, span);
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function startLlmSpan(opts) {
|
|
35
|
+
const tracer = trace.getTracer(TRACER_NAME);
|
|
36
|
+
const model = opts?.model ?? "";
|
|
37
|
+
const span = tracer.startSpan("openrouter.call_model", {
|
|
38
|
+
attributes: {
|
|
39
|
+
"neatlogs.span.kind": "LLM",
|
|
40
|
+
"neatlogs.llm.provider": PROVIDER,
|
|
41
|
+
"neatlogs.llm.system": PROVIDER,
|
|
42
|
+
"neatlogs.llm.model_name": model
|
|
43
|
+
}
|
|
44
|
+
}, otelContext.active());
|
|
45
|
+
const messages = Array.isArray(opts?.messages) ? opts.messages : Array.isArray(opts?.input) ? opts.input : [];
|
|
46
|
+
if (messages.length) {
|
|
47
|
+
messages.forEach((msg, i) => {
|
|
48
|
+
span.setAttribute(`neatlogs.llm.input_messages.${i}.role`, msg?.role ?? "user");
|
|
49
|
+
const content = msg?.content;
|
|
50
|
+
span.setAttribute(
|
|
51
|
+
`neatlogs.llm.input_messages.${i}.content`,
|
|
52
|
+
typeof content === "string" ? content : safeStringify(content)
|
|
53
|
+
);
|
|
54
|
+
});
|
|
55
|
+
span.setAttribute("input.value", safeStringify({ messages }));
|
|
56
|
+
} else if (typeof opts?.input === "string") {
|
|
57
|
+
span.setAttribute("neatlogs.llm.input_messages.0.role", "user");
|
|
58
|
+
span.setAttribute("neatlogs.llm.input_messages.0.content", opts.input);
|
|
59
|
+
span.setAttribute("input.value", opts.input);
|
|
60
|
+
}
|
|
61
|
+
if (typeof opts?.instructions === "string" && opts.instructions) {
|
|
62
|
+
span.setAttribute("neatlogs.llm.system_prompt", opts.instructions);
|
|
63
|
+
}
|
|
64
|
+
if (Array.isArray(opts?.tools)) {
|
|
65
|
+
for (let i = 0; i < opts.tools.length; i++) {
|
|
66
|
+
const t = opts.tools[i] ?? {};
|
|
67
|
+
const name = t.name ?? t.function?.name;
|
|
68
|
+
if (name) span.setAttribute(`neatlogs.llm.tools.${i}.name`, name);
|
|
69
|
+
const desc = t.description ?? t.function?.description;
|
|
70
|
+
if (desc) span.setAttribute(`neatlogs.llm.tools.${i}.description`, desc);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
const temperature = opts?.temperature;
|
|
74
|
+
const topP = opts?.top_p ?? opts?.topP;
|
|
75
|
+
const maxTokens = opts?.max_tokens ?? opts?.maxTokens ?? opts?.max_output_tokens ?? opts?.maxOutputTokens;
|
|
76
|
+
const frequencyPenalty = opts?.frequency_penalty ?? opts?.frequencyPenalty;
|
|
77
|
+
const presencePenalty = opts?.presence_penalty ?? opts?.presencePenalty;
|
|
78
|
+
const topK = opts?.top_k ?? opts?.topK;
|
|
79
|
+
if (temperature != null) span.setAttribute("neatlogs.llm.temperature", temperature);
|
|
80
|
+
if (topP != null) span.setAttribute("neatlogs.llm.top_p", topP);
|
|
81
|
+
if (maxTokens != null) span.setAttribute("neatlogs.llm.max_tokens", maxTokens);
|
|
82
|
+
const params = {};
|
|
83
|
+
if (temperature != null) params.temperature = temperature;
|
|
84
|
+
if (topP != null) params.top_p = topP;
|
|
85
|
+
if (maxTokens != null) params.max_tokens = maxTokens;
|
|
86
|
+
if (frequencyPenalty != null) params.frequency_penalty = frequencyPenalty;
|
|
87
|
+
if (presencePenalty != null) params.presence_penalty = presencePenalty;
|
|
88
|
+
if (topK != null) params.top_k = topK;
|
|
89
|
+
if (Object.keys(params).length) {
|
|
90
|
+
span.setAttribute("neatlogs.llm.invocation_parameters", JSON.stringify(params));
|
|
91
|
+
}
|
|
92
|
+
return span;
|
|
93
|
+
}
|
|
94
|
+
function instrumentModelResult(result, span) {
|
|
95
|
+
if (!result || typeof result !== "object" && typeof result !== "function") {
|
|
96
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
97
|
+
span.end();
|
|
98
|
+
return result;
|
|
99
|
+
}
|
|
100
|
+
let finalized = false;
|
|
101
|
+
const finalizeFromResult = (resolved) => {
|
|
102
|
+
if (finalized) return;
|
|
103
|
+
finalized = true;
|
|
104
|
+
try {
|
|
105
|
+
finalizeLlm(span, resolved);
|
|
106
|
+
} catch {
|
|
107
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
108
|
+
span.end();
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
const finalizeError = (err) => {
|
|
112
|
+
if (finalized) return;
|
|
113
|
+
finalized = true;
|
|
114
|
+
recordError(span, err);
|
|
115
|
+
};
|
|
116
|
+
return new Proxy(result, {
|
|
117
|
+
get(obj, prop, receiver) {
|
|
118
|
+
const value = Reflect.get(obj, prop, receiver);
|
|
119
|
+
if (prop === "getResponse" && typeof value === "function") {
|
|
120
|
+
return function(...args) {
|
|
121
|
+
return Promise.resolve(value.apply(obj, args)).then(
|
|
122
|
+
(resp) => {
|
|
123
|
+
finalizeFromResult(resp);
|
|
124
|
+
return resp;
|
|
125
|
+
},
|
|
126
|
+
(err) => {
|
|
127
|
+
finalizeError(err);
|
|
128
|
+
throw err;
|
|
129
|
+
}
|
|
130
|
+
);
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
if (prop === "getText" && typeof value === "function") {
|
|
134
|
+
return function(...args) {
|
|
135
|
+
return Promise.resolve(value.apply(obj, args)).then(
|
|
136
|
+
async (textStr) => {
|
|
137
|
+
let resp;
|
|
138
|
+
if (typeof obj?.getResponse === "function") {
|
|
139
|
+
try {
|
|
140
|
+
resp = await obj.getResponse();
|
|
141
|
+
} catch {
|
|
142
|
+
resp = void 0;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
finalizeFromResult(resp ? { ...resp, text: textStr } : { text: textStr });
|
|
146
|
+
return textStr;
|
|
147
|
+
},
|
|
148
|
+
(err) => {
|
|
149
|
+
finalizeError(err);
|
|
150
|
+
throw err;
|
|
151
|
+
}
|
|
152
|
+
);
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
if (prop === "then" && typeof value === "function") {
|
|
156
|
+
return function(onFulfilled, onRejected) {
|
|
157
|
+
return value.call(
|
|
158
|
+
obj,
|
|
159
|
+
(resolved) => {
|
|
160
|
+
finalizeFromResult(typeof resolved === "string" ? { text: resolved } : resolved ?? obj);
|
|
161
|
+
return onFulfilled ? onFulfilled(resolved) : resolved;
|
|
162
|
+
},
|
|
163
|
+
(err) => {
|
|
164
|
+
finalizeError(err);
|
|
165
|
+
return onRejected ? onRejected(err) : Promise.reject(err);
|
|
166
|
+
}
|
|
167
|
+
);
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
return value;
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
function setOpenResponsesUsage(span, resp) {
|
|
175
|
+
const u = resp?.usage ?? resp;
|
|
176
|
+
if (!u) return;
|
|
177
|
+
const input = u.inputTokens ?? u.input_tokens ?? u.prompt_tokens;
|
|
178
|
+
const output = u.outputTokens ?? u.output_tokens ?? u.completion_tokens;
|
|
179
|
+
const total = u.totalTokens ?? u.total_tokens;
|
|
180
|
+
const cached = u.cachedTokens ?? u.cached_tokens;
|
|
181
|
+
if (input != null) span.setAttribute("neatlogs.llm.token_count.prompt", input);
|
|
182
|
+
if (output != null) span.setAttribute("neatlogs.llm.token_count.completion", output);
|
|
183
|
+
if (total != null) span.setAttribute("neatlogs.llm.token_count.total", total);
|
|
184
|
+
else if (input != null && output != null) span.setAttribute("neatlogs.llm.token_count.total", input + output);
|
|
185
|
+
if (cached != null) span.setAttribute("neatlogs.llm.token_count.cache_read", cached);
|
|
186
|
+
}
|
|
187
|
+
function finalizeLlm(span, result) {
|
|
188
|
+
const text = result?.text ?? result?.output_text ?? result?.content ?? extractOpenResponsesText(result) ?? result?.choices?.[0]?.message?.content ?? result?.message?.content;
|
|
189
|
+
if (text) {
|
|
190
|
+
span.setAttribute("neatlogs.llm.output_messages.0.role", "assistant");
|
|
191
|
+
span.setAttribute("neatlogs.llm.output_messages.0.content", String(text));
|
|
192
|
+
span.setAttribute("output.value", String(text));
|
|
193
|
+
}
|
|
194
|
+
const toolCalls = result?.toolCalls ?? result?.tool_calls ?? result?.choices?.[0]?.message?.tool_calls ?? result?.message?.tool_calls;
|
|
195
|
+
if (Array.isArray(toolCalls)) {
|
|
196
|
+
toolCalls.forEach((tc, j) => {
|
|
197
|
+
span.setAttribute(`neatlogs.llm.tool_calls.${j}.id`, tc?.id ?? "");
|
|
198
|
+
span.setAttribute(`neatlogs.llm.tool_calls.${j}.name`, tc?.function?.name ?? tc?.name ?? "");
|
|
199
|
+
const args = tc?.function?.arguments ?? tc?.arguments;
|
|
200
|
+
span.setAttribute(`neatlogs.llm.tool_calls.${j}.arguments`, typeof args === "string" ? args : safeStringify(args ?? {}));
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
const model = result?.model ?? result?.response?.model;
|
|
204
|
+
if (model) span.setAttribute("neatlogs.llm.model_name", String(model));
|
|
205
|
+
const responseId = result?.id ?? result?.response?.id;
|
|
206
|
+
if (responseId) span.setAttribute("neatlogs.llm.response_id", String(responseId));
|
|
207
|
+
const finishReason = result?.finishReason ?? result?.finish_reason ?? result?.choices?.[0]?.finish_reason;
|
|
208
|
+
if (finishReason) span.setAttribute("neatlogs.llm.finish_reason", String(finishReason));
|
|
209
|
+
setOpenResponsesUsage(span, result);
|
|
210
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
211
|
+
span.end();
|
|
212
|
+
}
|
|
213
|
+
function extractOpenResponsesText(result) {
|
|
214
|
+
const output = result?.output;
|
|
215
|
+
if (!Array.isArray(output)) return void 0;
|
|
216
|
+
const parts = [];
|
|
217
|
+
for (const item of output) {
|
|
218
|
+
if (item?.type === "message" && Array.isArray(item.content)) {
|
|
219
|
+
for (const c of item.content) {
|
|
220
|
+
if ((c?.type === "output_text" || c?.type === "text") && typeof c.text === "string") parts.push(c.text);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return parts.length ? parts.join("") : void 0;
|
|
225
|
+
}
|
|
226
|
+
function safeStringify(value) {
|
|
227
|
+
if (typeof value === "string") return value;
|
|
228
|
+
try {
|
|
229
|
+
return JSON.stringify(value) ?? "";
|
|
230
|
+
} catch {
|
|
231
|
+
return "";
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
function recordError(span, err) {
|
|
235
|
+
if (err instanceof Error) {
|
|
236
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: err.message });
|
|
237
|
+
span.recordException(err);
|
|
238
|
+
} else {
|
|
239
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: String(err) });
|
|
240
|
+
}
|
|
241
|
+
span.end();
|
|
242
|
+
}
|
|
243
|
+
export {
|
|
244
|
+
wrapCallModel,
|
|
245
|
+
wrapOpenRouterAgent
|
|
246
|
+
};
|
|
247
|
+
//# sourceMappingURL=openrouter-agent.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/openrouter-agent.ts"],"sourcesContent":["/**\n * Neatlogs OpenRouter Agent integration.\n *\n * Wraps the `@openrouter/agent` SDK so each `client.callModel(...)` is traced.\n * The OpenRouter client returns a `ModelResult`; telemetry is finalized when the\n * result is consumed (via `getText()` / awaiting it / iterating it), matching the\n * SDK's lazy-evaluation model — a result that is never consumed ships no span.\n *\n * Usage:\n * import { init } from 'neatlogs';\n * import { wrapOpenRouterAgent } from 'neatlogs/openrouter-agent';\n * import { OpenRouter } from '@openrouter/agent';\n *\n * await init({ apiKey, workflowName });\n * const openrouter = wrapOpenRouterAgent(new OpenRouter({ apiKey: process.env.OPENROUTER_API_KEY }));\n * const result = openrouter.callModel({ model: 'openai/gpt-4o', messages: [...] });\n * const text = await result.getText();\n *\n * Also exports wrapCallModel() for the standalone callModel helper:\n * const trackedCallModel = wrapCallModel(callModel);\n * const result = trackedCallModel(openrouter, { model, messages });\n */\n\nimport { trace, context as otelContext, SpanStatusCode, type Span } from '@opentelemetry/api';\n\nconst TRACER_NAME = 'neatlogs.openrouter_agent';\nconst PROVIDER = 'openrouter';\n\n/**\n * Wrap an OpenRouter client instance so `callModel` emits LLM spans. Returns a\n * Proxy over the client; all other methods pass through unchanged.\n */\nexport function wrapOpenRouterAgent<T extends object>(client: T): T {\n const c = client as any;\n if (!c || c._neatlogsWrapped) return client;\n\n return new Proxy(client, {\n get(obj, prop, receiver) {\n const value = Reflect.get(obj, prop, receiver);\n if (prop === 'callModel' && typeof value === 'function') {\n return tracedCallModel(value.bind(obj));\n }\n return value;\n },\n }) as T;\n}\n\n/**\n * Wrap the standalone `callModel(client, opts)` helper. The wrapped function is\n * invoked with the client passed in: `trackedCallModel(openrouter, {...})`.\n */\nexport function wrapCallModel<F extends (...args: any[]) => any>(callModel: F): F {\n return function (this: any, clientArg: any, opts: any, ...rest: any[]): any {\n const span = startLlmSpan(opts);\n const ctx = trace.setSpan(otelContext.active(), span);\n const result = otelContext.with(ctx, () => callModel.call(this, clientArg, opts, ...rest));\n return instrumentModelResult(result, span);\n } as unknown as F;\n}\n\n// ---------------------------------------------------------------------------\n// callModel wrapping\n// ---------------------------------------------------------------------------\n\nfunction tracedCallModel(original: (...args: any[]) => any) {\n return function (opts: any, ...rest: any[]): any {\n const span = startLlmSpan(opts);\n const ctx = trace.setSpan(otelContext.active(), span);\n const result = otelContext.with(ctx, () => original(opts, ...rest));\n return instrumentModelResult(result, span);\n };\n}\n\nfunction startLlmSpan(opts: any): Span {\n const tracer = trace.getTracer(TRACER_NAME);\n const model = opts?.model ?? '';\n\n const span = tracer.startSpan('openrouter.call_model', {\n attributes: {\n 'neatlogs.span.kind': 'LLM',\n 'neatlogs.llm.provider': PROVIDER,\n 'neatlogs.llm.system': PROVIDER,\n 'neatlogs.llm.model_name': model,\n },\n }, otelContext.active());\n\n // The @openrouter/agent callModel input is `input` — either a string prompt or\n // a messages array (it also accepts `messages`/`instructions` in some shapes).\n // Capture whichever is present as indexed input messages + the flat input.value\n // blob (the canonical UI-rendered field, per attribute-mapping.json).\n const messages: any[] = Array.isArray(opts?.messages)\n ? opts.messages\n : Array.isArray(opts?.input)\n ? opts.input\n : [];\n if (messages.length) {\n messages.forEach((msg, i) => {\n span.setAttribute(`neatlogs.llm.input_messages.${i}.role`, msg?.role ?? 'user');\n const content = msg?.content;\n span.setAttribute(\n `neatlogs.llm.input_messages.${i}.content`,\n typeof content === 'string' ? content : safeStringify(content),\n );\n });\n span.setAttribute('input.value', safeStringify({ messages }));\n } else if (typeof opts?.input === 'string') {\n span.setAttribute('neatlogs.llm.input_messages.0.role', 'user');\n span.setAttribute('neatlogs.llm.input_messages.0.content', opts.input);\n span.setAttribute('input.value', opts.input);\n }\n if (typeof opts?.instructions === 'string' && opts.instructions) {\n span.setAttribute('neatlogs.llm.system_prompt', opts.instructions);\n }\n\n if (Array.isArray(opts?.tools)) {\n for (let i = 0; i < opts.tools.length; i++) {\n const t = opts.tools[i] ?? {};\n const name = t.name ?? t.function?.name;\n if (name) span.setAttribute(`neatlogs.llm.tools.${i}.name`, name);\n const desc = t.description ?? t.function?.description;\n if (desc) span.setAttribute(`neatlogs.llm.tools.${i}.description`, desc);\n }\n }\n\n // @openrouter/agent's callModel request uses camelCase sampling params\n // (topP, maxOutputTokens, frequencyPenalty, presencePenalty, topK) derived\n // from the OpenResponses ResponsesRequest. Accept both camelCase and the\n // snake_case OpenAI-style variants so params are captured regardless of how\n // the caller spelled them.\n const temperature = opts?.temperature;\n const topP = opts?.top_p ?? opts?.topP;\n const maxTokens = opts?.max_tokens ?? opts?.maxTokens ?? opts?.max_output_tokens ?? opts?.maxOutputTokens;\n const frequencyPenalty = opts?.frequency_penalty ?? opts?.frequencyPenalty;\n const presencePenalty = opts?.presence_penalty ?? opts?.presencePenalty;\n const topK = opts?.top_k ?? opts?.topK;\n\n if (temperature != null) span.setAttribute('neatlogs.llm.temperature', temperature);\n if (topP != null) span.setAttribute('neatlogs.llm.top_p', topP);\n if (maxTokens != null) span.setAttribute('neatlogs.llm.max_tokens', maxTokens);\n\n // The backend reads invocation params ONLY from this JSON-string blob (parsed\n // into metadata.model_settings, which the UI renders). Individual attrs above\n // are kept for other consumers but are NOT what the UI shows. Only include\n // keys that are actually present so we never emit nulls.\n const params: Record<string, unknown> = {};\n if (temperature != null) params.temperature = temperature;\n if (topP != null) params.top_p = topP;\n if (maxTokens != null) params.max_tokens = maxTokens;\n if (frequencyPenalty != null) params.frequency_penalty = frequencyPenalty;\n if (presencePenalty != null) params.presence_penalty = presencePenalty;\n if (topK != null) params.top_k = topK;\n if (Object.keys(params).length) {\n span.setAttribute('neatlogs.llm.invocation_parameters', JSON.stringify(params));\n }\n\n return span;\n}\n\n/**\n * Wrap a ModelResult so the span is finalized exactly once, when the caller\n * consumes the result. We patch the common consumption methods (getText,\n * getMessage, then) so finalization happens on first use; a result that is\n * never consumed never finalizes (matching SDK semantics).\n */\nfunction instrumentModelResult(result: any, span: Span): any {\n if (!result || (typeof result !== 'object' && typeof result !== 'function')) {\n // Synchronous/primitive return — nothing to defer; close immediately.\n span.setStatus({ code: SpanStatusCode.OK });\n span.end();\n return result;\n }\n\n let finalized = false;\n const finalizeFromResult = (resolved: any) => {\n if (finalized) return;\n finalized = true;\n try {\n finalizeLlm(span, resolved);\n } catch {\n span.setStatus({ code: SpanStatusCode.OK });\n span.end();\n }\n };\n const finalizeError = (err: unknown) => {\n if (finalized) return;\n finalized = true;\n recordError(span, err);\n };\n\n // The @openrouter/agent ModelResult is consumed via getText()/getResponse()/\n // getTextStream(). Wrap with a Proxy to intercept whichever the caller uses\n // first; finalize the span from that, and grab usage via getResponse().\n return new Proxy(result, {\n get(obj, prop, receiver) {\n const value = Reflect.get(obj, prop, receiver);\n\n if (prop === 'getResponse' && typeof value === 'function') {\n return function (...args: any[]) {\n return Promise.resolve(value.apply(obj, args)).then(\n (resp: any) => {\n finalizeFromResult(resp); // full OpenResponsesResult (text + usage)\n return resp;\n },\n (err: any) => {\n finalizeError(err);\n throw err;\n },\n );\n };\n }\n\n if (prop === 'getText' && typeof value === 'function') {\n return function (...args: any[]) {\n return Promise.resolve(value.apply(obj, args)).then(\n async (textStr: any) => {\n // getText() resolves to a bare string (no usage). Usage lives on\n // getResponse(); fetch it FIRST (the SDK supports concurrent\n // consumption) so the span carries tokens — then finalize ONCE.\n // Finalizing ends the span, so late attributes would be dropped.\n let resp: any;\n if (typeof obj?.getResponse === 'function') {\n try {\n resp = await obj.getResponse();\n } catch {\n resp = undefined;\n }\n }\n finalizeFromResult(resp ? { ...resp, text: textStr } : { text: textStr });\n return textStr;\n },\n (err: any) => {\n finalizeError(err);\n throw err;\n },\n );\n };\n }\n\n if (prop === 'then' && typeof value === 'function') {\n // Result is awaitable directly: await result.\n return function (onFulfilled?: any, onRejected?: any) {\n return value.call(\n obj,\n (resolved: any) => {\n finalizeFromResult(typeof resolved === 'string' ? { text: resolved } : (resolved ?? obj));\n return onFulfilled ? onFulfilled(resolved) : resolved;\n },\n (err: any) => {\n finalizeError(err);\n return onRejected ? onRejected(err) : Promise.reject(err);\n },\n );\n };\n }\n\n return value;\n },\n });\n}\n\n/** Stamp usage from an @openrouter/agent OpenResponsesResult (inputTokens/outputTokens/...). */\nfunction setOpenResponsesUsage(span: Span, resp: any): void {\n const u = resp?.usage ?? resp;\n if (!u) return;\n const input = u.inputTokens ?? u.input_tokens ?? u.prompt_tokens;\n const output = u.outputTokens ?? u.output_tokens ?? u.completion_tokens;\n const total = u.totalTokens ?? u.total_tokens;\n const cached = u.cachedTokens ?? u.cached_tokens;\n if (input != null) span.setAttribute('neatlogs.llm.token_count.prompt', input);\n if (output != null) span.setAttribute('neatlogs.llm.token_count.completion', output);\n if (total != null) span.setAttribute('neatlogs.llm.token_count.total', total);\n else if (input != null && output != null) span.setAttribute('neatlogs.llm.token_count.total', input + output);\n if (cached != null) span.setAttribute('neatlogs.llm.token_count.cache_read', cached);\n}\n\nfunction finalizeLlm(span: Span, result: any): void {\n // Text output. getText() gives a bare string (wrapped here as {text}); the\n // OpenResponsesResult from getResponse() carries text under output[]/output_text.\n const text =\n result?.text ??\n result?.output_text ??\n result?.content ??\n extractOpenResponsesText(result) ??\n result?.choices?.[0]?.message?.content ??\n result?.message?.content;\n if (text) {\n span.setAttribute('neatlogs.llm.output_messages.0.role', 'assistant');\n span.setAttribute('neatlogs.llm.output_messages.0.content', String(text));\n span.setAttribute('output.value', String(text));\n }\n\n // Tool calls (OpenAI-compatible shape; OpenResponses uses output[] function_call items).\n const toolCalls =\n result?.toolCalls ??\n result?.tool_calls ??\n result?.choices?.[0]?.message?.tool_calls ??\n result?.message?.tool_calls;\n if (Array.isArray(toolCalls)) {\n toolCalls.forEach((tc: any, j: number) => {\n span.setAttribute(`neatlogs.llm.tool_calls.${j}.id`, tc?.id ?? '');\n span.setAttribute(`neatlogs.llm.tool_calls.${j}.name`, tc?.function?.name ?? tc?.name ?? '');\n const args = tc?.function?.arguments ?? tc?.arguments;\n span.setAttribute(`neatlogs.llm.tool_calls.${j}.arguments`, typeof args === 'string' ? args : safeStringify(args ?? {}));\n });\n }\n\n const model = result?.model ?? result?.response?.model;\n if (model) span.setAttribute('neatlogs.llm.model_name', String(model));\n\n const responseId = result?.id ?? result?.response?.id;\n if (responseId) span.setAttribute('neatlogs.llm.response_id', String(responseId));\n\n const finishReason = result?.finishReason ?? result?.finish_reason ?? result?.choices?.[0]?.finish_reason;\n if (finishReason) span.setAttribute('neatlogs.llm.finish_reason', String(finishReason));\n\n // Usage — OpenResponsesResult uses inputTokens/outputTokens; also handle the\n // OpenAI-compatible prompt_tokens/completion_tokens shape.\n setOpenResponsesUsage(span, result);\n\n span.setStatus({ code: SpanStatusCode.OK });\n span.end();\n}\n\n/** Pull assistant text out of an OpenResponses-style result (output[] of message items). */\nfunction extractOpenResponsesText(result: any): string | undefined {\n const output = result?.output;\n if (!Array.isArray(output)) return undefined;\n const parts: string[] = [];\n for (const item of output) {\n if (item?.type === 'message' && Array.isArray(item.content)) {\n for (const c of item.content) {\n if ((c?.type === 'output_text' || c?.type === 'text') && typeof c.text === 'string') parts.push(c.text);\n }\n }\n }\n return parts.length ? parts.join('') : undefined;\n}\n\nfunction safeStringify(value: unknown): string {\n if (typeof value === 'string') return value;\n try {\n return JSON.stringify(value) ?? '';\n } catch {\n return '';\n }\n}\n\nfunction recordError(span: Span, err: unknown): void {\n if (err instanceof Error) {\n span.setStatus({ code: SpanStatusCode.ERROR, message: err.message });\n span.recordException(err);\n } else {\n span.setStatus({ code: SpanStatusCode.ERROR, message: String(err) });\n }\n span.end();\n}\n"],"mappings":"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","names":[]}
|