@firstflow/core 0.0.6 → 0.0.9
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/anthropic.d.mts +9 -0
- package/dist/anthropic.d.ts +9 -0
- package/dist/anthropic.mjs +3 -2
- package/dist/{chunk-RZMKPIBO.mjs → chunk-KAH2MUTQ.mjs} +4 -2
- package/dist/{chunk-X3T7X4JQ.mjs → chunk-QBEGXT76.mjs} +12 -407
- package/dist/chunk-WQZUOUMF.mjs +404 -0
- package/dist/index.mjs +5 -3
- package/dist/langchain.d.mts +56 -0
- package/dist/langchain.d.ts +56 -0
- package/dist/langchain.js +1661 -0
- package/dist/langchain.mjs +277 -0
- package/dist/openai.d.mts +9 -0
- package/dist/openai.d.ts +9 -0
- package/dist/openai.mjs +3 -2
- package/package.json +22 -3
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getRuntime
|
|
3
|
+
} from "./chunk-QBEGXT76.mjs";
|
|
4
|
+
|
|
5
|
+
// src/langchain.ts
|
|
6
|
+
import { BaseCallbackHandler } from "@langchain/core/callbacks/base";
|
|
7
|
+
function asString(v) {
|
|
8
|
+
return typeof v === "string" ? v.trim() : "";
|
|
9
|
+
}
|
|
10
|
+
var warnedIncompleteTagging = false;
|
|
11
|
+
function warnIncompleteOnce() {
|
|
12
|
+
if (warnedIncompleteTagging) return;
|
|
13
|
+
warnedIncompleteTagging = true;
|
|
14
|
+
console.warn(
|
|
15
|
+
"[@firstflow/core/langchain] A LangChain run was tagged with some but not all of `firstflowAgentId`, `sessionId`, `userId` \u2014 the turn was NOT observed. Provide all three in the run metadata to track it."
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
function readIdentity(metadata) {
|
|
19
|
+
if (!metadata) return void 0;
|
|
20
|
+
const nested = typeof metadata.firstflow === "object" && metadata.firstflow !== null ? metadata.firstflow : {};
|
|
21
|
+
const agentId = asString(metadata.firstflowAgentId ?? nested.agentId);
|
|
22
|
+
const sessionId = asString(metadata.sessionId ?? nested.sessionId);
|
|
23
|
+
const userId = asString(metadata.userId ?? nested.userId);
|
|
24
|
+
if (!agentId && !sessionId && !userId) return void 0;
|
|
25
|
+
if (!agentId || !sessionId || !userId) {
|
|
26
|
+
warnIncompleteOnce();
|
|
27
|
+
return void 0;
|
|
28
|
+
}
|
|
29
|
+
return { agentId, sessionId, userId };
|
|
30
|
+
}
|
|
31
|
+
var KNOWN_PROVIDERS = /* @__PURE__ */ new Set(["openai", "anthropic"]);
|
|
32
|
+
function roleOf(msg) {
|
|
33
|
+
if (!msg || typeof msg !== "object") return void 0;
|
|
34
|
+
const m = msg;
|
|
35
|
+
let t;
|
|
36
|
+
if (typeof m._getType === "function") t = m._getType();
|
|
37
|
+
else if (typeof m.getType === "function") t = m.getType();
|
|
38
|
+
else if (typeof m.role === "string") t = m.role;
|
|
39
|
+
else if (typeof m.type === "string") t = m.type;
|
|
40
|
+
switch (t) {
|
|
41
|
+
case "human":
|
|
42
|
+
case "user":
|
|
43
|
+
return "user";
|
|
44
|
+
case "ai":
|
|
45
|
+
case "assistant":
|
|
46
|
+
return "assistant";
|
|
47
|
+
case "tool":
|
|
48
|
+
case "function":
|
|
49
|
+
return "tool";
|
|
50
|
+
default:
|
|
51
|
+
return void 0;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function contentToText(content) {
|
|
55
|
+
if (typeof content === "string") return content;
|
|
56
|
+
if (!Array.isArray(content)) return "";
|
|
57
|
+
let acc = "";
|
|
58
|
+
for (const part of content) {
|
|
59
|
+
if (typeof part === "string") {
|
|
60
|
+
acc += part;
|
|
61
|
+
} else if (part && typeof part === "object") {
|
|
62
|
+
const p = part;
|
|
63
|
+
if (p.type === "text" && typeof p.text === "string") acc += p.text;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return acc;
|
|
67
|
+
}
|
|
68
|
+
function toRecentMessages(messages) {
|
|
69
|
+
if (!Array.isArray(messages)) return [];
|
|
70
|
+
const out = [];
|
|
71
|
+
for (const msg of messages) {
|
|
72
|
+
const role = roleOf(msg);
|
|
73
|
+
if (role !== "user" && role !== "assistant") continue;
|
|
74
|
+
const content = contentToText(msg.content).trim();
|
|
75
|
+
if (!content) continue;
|
|
76
|
+
out.push({ role, content });
|
|
77
|
+
}
|
|
78
|
+
return out;
|
|
79
|
+
}
|
|
80
|
+
function turnStartUserMessage(messages) {
|
|
81
|
+
if (!Array.isArray(messages) || messages.length === 0) return void 0;
|
|
82
|
+
const last = messages[messages.length - 1];
|
|
83
|
+
if (roleOf(last) !== "user") return void 0;
|
|
84
|
+
const text = contentToText(last.content).trim();
|
|
85
|
+
return text || void 0;
|
|
86
|
+
}
|
|
87
|
+
function extractModel(extraParams, metadata) {
|
|
88
|
+
const inv = extraParams?.invocation_params ?? {};
|
|
89
|
+
const fromInv = inv.model ?? inv.model_name ?? inv.modelName;
|
|
90
|
+
if (typeof fromInv === "string" && fromInv) return fromInv;
|
|
91
|
+
const fromMeta = metadata?.ls_model_name;
|
|
92
|
+
return typeof fromMeta === "string" && fromMeta ? fromMeta : void 0;
|
|
93
|
+
}
|
|
94
|
+
function extractProvider(metadata) {
|
|
95
|
+
const p = metadata?.ls_provider;
|
|
96
|
+
return typeof p === "string" && KNOWN_PROVIDERS.has(p) ? p : void 0;
|
|
97
|
+
}
|
|
98
|
+
function extractEndMeta(output) {
|
|
99
|
+
const out = output ?? {};
|
|
100
|
+
const generations = out.generations;
|
|
101
|
+
let text = "";
|
|
102
|
+
const meta = {};
|
|
103
|
+
if (Array.isArray(generations)) {
|
|
104
|
+
for (const promptGens of generations) {
|
|
105
|
+
if (!Array.isArray(promptGens)) continue;
|
|
106
|
+
for (const gen of promptGens) {
|
|
107
|
+
if (!gen || typeof gen !== "object") continue;
|
|
108
|
+
const g = gen;
|
|
109
|
+
if (typeof g.text === "string" && g.text) text += g.text;
|
|
110
|
+
const message = g.message;
|
|
111
|
+
if (message) {
|
|
112
|
+
if (!text) text += contentToText(message.content);
|
|
113
|
+
const usage = message.usage_metadata;
|
|
114
|
+
if (usage) {
|
|
115
|
+
if (typeof usage.input_tokens === "number") meta.inputTokens = usage.input_tokens;
|
|
116
|
+
if (typeof usage.output_tokens === "number") meta.outputTokens = usage.output_tokens;
|
|
117
|
+
const inDetails = usage.input_token_details;
|
|
118
|
+
if (typeof inDetails?.cache_read === "number") meta.cacheReadTokens = inDetails.cache_read;
|
|
119
|
+
if (typeof inDetails?.cache_creation === "number")
|
|
120
|
+
meta.cacheCreationTokens = inDetails.cache_creation;
|
|
121
|
+
}
|
|
122
|
+
const respMeta = message.response_metadata;
|
|
123
|
+
const stop = respMeta?.stop_reason ?? respMeta?.finish_reason;
|
|
124
|
+
if (typeof stop === "string" && meta.finishReason === void 0) meta.finishReason = stop;
|
|
125
|
+
}
|
|
126
|
+
const genInfo = g.generationInfo;
|
|
127
|
+
const fr = genInfo?.finish_reason ?? genInfo?.finishReason;
|
|
128
|
+
if (typeof fr === "string" && meta.finishReason === void 0) meta.finishReason = fr;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (meta.inputTokens === void 0 || meta.outputTokens === void 0) {
|
|
133
|
+
const llmOutput = out.llmOutput ?? {};
|
|
134
|
+
const usage = llmOutput.tokenUsage ?? llmOutput.usage ?? {};
|
|
135
|
+
const input = usage.promptTokens ?? usage.input_tokens ?? usage.prompt_tokens;
|
|
136
|
+
const outp = usage.completionTokens ?? usage.output_tokens ?? usage.completion_tokens;
|
|
137
|
+
if (meta.inputTokens === void 0 && typeof input === "number") meta.inputTokens = input;
|
|
138
|
+
if (meta.outputTokens === void 0 && typeof outp === "number") meta.outputTokens = outp;
|
|
139
|
+
}
|
|
140
|
+
return { text, meta };
|
|
141
|
+
}
|
|
142
|
+
function safeObserve(fn) {
|
|
143
|
+
try {
|
|
144
|
+
fn();
|
|
145
|
+
} catch (err) {
|
|
146
|
+
warnRuntimeOnce(err);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
var warnedRuntime = false;
|
|
150
|
+
function warnRuntimeOnce(err) {
|
|
151
|
+
if (warnedRuntime) return;
|
|
152
|
+
warnedRuntime = true;
|
|
153
|
+
console.warn(
|
|
154
|
+
`[@firstflow/core/langchain] Telemetry disabled: ${err instanceof Error ? err.message : String(err)}`
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
var FirstflowCallbackHandler = class extends BaseCallbackHandler {
|
|
158
|
+
name = "firstflow";
|
|
159
|
+
runs = /* @__PURE__ */ new Map();
|
|
160
|
+
// -- starts ----------------------------------------------------------------
|
|
161
|
+
handleChatModelStart(_llm, messages, runId, _parentRunId, extraParams, _tags, metadata) {
|
|
162
|
+
const identity = readIdentity(metadata);
|
|
163
|
+
if (!identity) return;
|
|
164
|
+
const prompt = Array.isArray(messages) ? messages[0] : messages;
|
|
165
|
+
this.begin(runId, identity, prompt, extraParams, metadata);
|
|
166
|
+
}
|
|
167
|
+
handleLLMStart(_llm, prompts, runId, _parentRunId, extraParams, _tags, metadata) {
|
|
168
|
+
const identity = readIdentity(metadata);
|
|
169
|
+
if (!identity) return;
|
|
170
|
+
const list = Array.isArray(prompts) ? prompts : [];
|
|
171
|
+
const last = list.length ? list[list.length - 1] : void 0;
|
|
172
|
+
const pseudo = typeof last === "string" ? [{ role: "user", content: last }] : [];
|
|
173
|
+
this.begin(runId, identity, pseudo, extraParams, metadata);
|
|
174
|
+
}
|
|
175
|
+
begin(runId, identity, prompt, extraParams, metadata) {
|
|
176
|
+
const recentMessages = toRecentMessages(prompt);
|
|
177
|
+
const model = extractModel(extraParams, metadata);
|
|
178
|
+
const provider = extractProvider(metadata);
|
|
179
|
+
this.runs.set(runId, {
|
|
180
|
+
identity,
|
|
181
|
+
startTime: Date.now(),
|
|
182
|
+
model,
|
|
183
|
+
provider,
|
|
184
|
+
recentMessages
|
|
185
|
+
});
|
|
186
|
+
const userContent = turnStartUserMessage(prompt);
|
|
187
|
+
if (!userContent) return;
|
|
188
|
+
safeObserve(
|
|
189
|
+
() => getRuntime().observe({
|
|
190
|
+
agentId: identity.agentId,
|
|
191
|
+
conversationId: identity.sessionId,
|
|
192
|
+
userId: identity.userId,
|
|
193
|
+
role: "user",
|
|
194
|
+
content: userContent,
|
|
195
|
+
model,
|
|
196
|
+
provider,
|
|
197
|
+
...recentMessages.length ? { recentMessages } : {}
|
|
198
|
+
})
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
// -- streaming -------------------------------------------------------------
|
|
202
|
+
handleLLMNewToken(_token, _idx, runId) {
|
|
203
|
+
const state = this.runs.get(runId);
|
|
204
|
+
if (state && state.firstTokenTime === void 0) state.firstTokenTime = Date.now();
|
|
205
|
+
}
|
|
206
|
+
// -- ends ------------------------------------------------------------------
|
|
207
|
+
handleLLMEnd(output, runId) {
|
|
208
|
+
const state = this.runs.get(runId);
|
|
209
|
+
if (!state) return;
|
|
210
|
+
this.runs.delete(runId);
|
|
211
|
+
const { text, meta } = extractEndMeta(output);
|
|
212
|
+
const { identity, startTime, firstTokenTime, model, provider, recentMessages } = state;
|
|
213
|
+
safeObserve(
|
|
214
|
+
() => getRuntime().observe({
|
|
215
|
+
agentId: identity.agentId,
|
|
216
|
+
conversationId: identity.sessionId,
|
|
217
|
+
userId: identity.userId,
|
|
218
|
+
role: "assistant",
|
|
219
|
+
content: text,
|
|
220
|
+
model,
|
|
221
|
+
provider,
|
|
222
|
+
...meta,
|
|
223
|
+
latencyMs: Date.now() - startTime,
|
|
224
|
+
...firstTokenTime !== void 0 ? { timeToFirstTokenMs: firstTokenTime - startTime } : {},
|
|
225
|
+
httpStatus: 200,
|
|
226
|
+
...recentMessages.length ? { recentMessages } : {}
|
|
227
|
+
})
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
handleLLMError(err, runId) {
|
|
231
|
+
const state = this.runs.get(runId);
|
|
232
|
+
if (!state) return;
|
|
233
|
+
this.runs.delete(runId);
|
|
234
|
+
const { identity, startTime, model, provider } = state;
|
|
235
|
+
const status = err?.status;
|
|
236
|
+
safeObserve(
|
|
237
|
+
() => getRuntime().observe({
|
|
238
|
+
agentId: identity.agentId,
|
|
239
|
+
conversationId: identity.sessionId,
|
|
240
|
+
userId: identity.userId,
|
|
241
|
+
role: "assistant",
|
|
242
|
+
content: "",
|
|
243
|
+
isError: true,
|
|
244
|
+
error: err instanceof Error ? err.message : String(err),
|
|
245
|
+
model,
|
|
246
|
+
provider,
|
|
247
|
+
latencyMs: Date.now() - startTime,
|
|
248
|
+
httpStatus: typeof status === "number" ? status : 500
|
|
249
|
+
})
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
var _shared;
|
|
254
|
+
function firstflowCallback() {
|
|
255
|
+
if (!_shared) _shared = new FirstflowCallbackHandler();
|
|
256
|
+
return _shared;
|
|
257
|
+
}
|
|
258
|
+
function withFirstflow(identity, config) {
|
|
259
|
+
const handler = firstflowCallback();
|
|
260
|
+
const existing = Array.isArray(config?.callbacks) ? config.callbacks : config?.callbacks ? [config.callbacks] : [];
|
|
261
|
+
const callbacks = existing.includes(handler) ? existing : [...existing, handler];
|
|
262
|
+
return {
|
|
263
|
+
...config,
|
|
264
|
+
callbacks,
|
|
265
|
+
metadata: {
|
|
266
|
+
...config?.metadata ?? {},
|
|
267
|
+
firstflowAgentId: identity.agentId,
|
|
268
|
+
sessionId: identity.sessionId,
|
|
269
|
+
userId: identity.userId
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
export {
|
|
274
|
+
FirstflowCallbackHandler,
|
|
275
|
+
firstflowCallback,
|
|
276
|
+
withFirstflow
|
|
277
|
+
};
|
package/dist/openai.d.mts
CHANGED
|
@@ -15,5 +15,14 @@ declare module "openai/resources/chat/completions/completions" {
|
|
|
15
15
|
|
|
16
16
|
/** Drop-in `OpenAI` client with Firstflow observability built in. */
|
|
17
17
|
declare const OpenAI: typeof RealOpenAI;
|
|
18
|
+
/**
|
|
19
|
+
* Instance type of the wrapped client, so consumers can annotate naturally:
|
|
20
|
+
*
|
|
21
|
+
* private readonly client: OpenAI = new OpenAI();
|
|
22
|
+
*
|
|
23
|
+
* `OpenAI` lives in both the value and type namespaces (the `const` above is the
|
|
24
|
+
* value; this alias is the type), mirroring how the real SDK's class works.
|
|
25
|
+
*/
|
|
26
|
+
type OpenAI = RealOpenAI;
|
|
18
27
|
|
|
19
28
|
export { OpenAI };
|
package/dist/openai.d.ts
CHANGED
|
@@ -15,5 +15,14 @@ declare module "openai/resources/chat/completions/completions" {
|
|
|
15
15
|
|
|
16
16
|
/** Drop-in `OpenAI` client with Firstflow observability built in. */
|
|
17
17
|
declare const OpenAI: typeof RealOpenAI;
|
|
18
|
+
/**
|
|
19
|
+
* Instance type of the wrapped client, so consumers can annotate naturally:
|
|
20
|
+
*
|
|
21
|
+
* private readonly client: OpenAI = new OpenAI();
|
|
22
|
+
*
|
|
23
|
+
* `OpenAI` lives in both the value and type namespaces (the `const` above is the
|
|
24
|
+
* value; this alias is the type), mirroring how the real SDK's class works.
|
|
25
|
+
*/
|
|
26
|
+
type OpenAI = RealOpenAI;
|
|
18
27
|
|
|
19
28
|
export { OpenAI };
|
package/dist/openai.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@firstflow/core",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.9",
|
|
4
4
|
"description": "Firstflow Node SDK — cloud forwarding, LLM wrap, self-owned analytics",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -20,12 +20,24 @@
|
|
|
20
20
|
"types": "./dist/anthropic.d.ts",
|
|
21
21
|
"import": "./dist/anthropic.mjs",
|
|
22
22
|
"require": "./dist/anthropic.js"
|
|
23
|
+
},
|
|
24
|
+
"./langchain": {
|
|
25
|
+
"types": "./dist/langchain.d.ts",
|
|
26
|
+
"import": "./dist/langchain.mjs",
|
|
27
|
+
"require": "./dist/langchain.js"
|
|
23
28
|
}
|
|
24
29
|
},
|
|
25
30
|
"typesVersions": {
|
|
26
31
|
"*": {
|
|
27
|
-
"openai": [
|
|
28
|
-
|
|
32
|
+
"openai": [
|
|
33
|
+
"./dist/openai.d.ts"
|
|
34
|
+
],
|
|
35
|
+
"anthropic": [
|
|
36
|
+
"./dist/anthropic.d.ts"
|
|
37
|
+
],
|
|
38
|
+
"langchain": [
|
|
39
|
+
"./dist/langchain.d.ts"
|
|
40
|
+
]
|
|
29
41
|
}
|
|
30
42
|
},
|
|
31
43
|
"files": [
|
|
@@ -55,6 +67,7 @@
|
|
|
55
67
|
},
|
|
56
68
|
"peerDependencies": {
|
|
57
69
|
"@anthropic-ai/sdk": ">=0.36.0",
|
|
70
|
+
"@langchain/core": ">=0.3.0",
|
|
58
71
|
"openai": ">=4.0.0"
|
|
59
72
|
},
|
|
60
73
|
"peerDependenciesMeta": {
|
|
@@ -63,10 +76,14 @@
|
|
|
63
76
|
},
|
|
64
77
|
"@anthropic-ai/sdk": {
|
|
65
78
|
"optional": true
|
|
79
|
+
},
|
|
80
|
+
"@langchain/core": {
|
|
81
|
+
"optional": true
|
|
66
82
|
}
|
|
67
83
|
},
|
|
68
84
|
"devDependencies": {
|
|
69
85
|
"@anthropic-ai/sdk": "^0.52.0",
|
|
86
|
+
"@langchain/core": "^0.3.0",
|
|
70
87
|
"@types/node": "^22.0.0",
|
|
71
88
|
"openai": "^4.104.0",
|
|
72
89
|
"tsup": "^8.0.0",
|
|
@@ -79,6 +96,8 @@
|
|
|
79
96
|
"openai",
|
|
80
97
|
"anthropic",
|
|
81
98
|
"claude",
|
|
99
|
+
"langchain",
|
|
100
|
+
"langgraph",
|
|
82
101
|
"opentelemetry"
|
|
83
102
|
]
|
|
84
103
|
}
|