@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.
@@ -15,5 +15,14 @@ declare module "@anthropic-ai/sdk/resources/messages/messages" {
15
15
 
16
16
  /** Drop-in `Anthropic` client with Firstflow observability built in. */
17
17
  declare const Anthropic: typeof RealAnthropic;
18
+ /**
19
+ * Instance type of the wrapped client, so consumers can annotate naturally:
20
+ *
21
+ * private readonly client: Anthropic = new Anthropic();
22
+ *
23
+ * `Anthropic` lives in both the value and type namespaces (the `const` above is
24
+ * the value; this alias is the type), mirroring how the real SDK's class works.
25
+ */
26
+ type Anthropic = RealAnthropic;
18
27
 
19
28
  export { Anthropic };
@@ -15,5 +15,14 @@ declare module "@anthropic-ai/sdk/resources/messages/messages" {
15
15
 
16
16
  /** Drop-in `Anthropic` client with Firstflow observability built in. */
17
17
  declare const Anthropic: typeof RealAnthropic;
18
+ /**
19
+ * Instance type of the wrapped client, so consumers can annotate naturally:
20
+ *
21
+ * private readonly client: Anthropic = new Anthropic();
22
+ *
23
+ * `Anthropic` lives in both the value and type namespaces (the `const` above is
24
+ * the value; this alias is the type), mirroring how the real SDK's class works.
25
+ */
26
+ type Anthropic = RealAnthropic;
18
27
 
19
28
  export { Anthropic };
@@ -1,7 +1,8 @@
1
1
  import {
2
2
  wrapProvider
3
- } from "./chunk-RZMKPIBO.mjs";
4
- import "./chunk-X3T7X4JQ.mjs";
3
+ } from "./chunk-KAH2MUTQ.mjs";
4
+ import "./chunk-WQZUOUMF.mjs";
5
+ import "./chunk-QBEGXT76.mjs";
5
6
 
6
7
  // src/anthropic.ts
7
8
  import RealAnthropic from "@anthropic-ai/sdk";
@@ -1,7 +1,9 @@
1
1
  import {
2
- getRuntime,
3
2
  wrapClient
4
- } from "./chunk-X3T7X4JQ.mjs";
3
+ } from "./chunk-WQZUOUMF.mjs";
4
+ import {
5
+ getRuntime
6
+ } from "./chunk-QBEGXT76.mjs";
5
7
 
6
8
  // src/_wrap-provider.ts
7
9
  function wrapProvider(Real) {
@@ -1,3 +1,13 @@
1
+ // src/constants.ts
2
+ var DEFAULT_FIRSTFLOW_BASE_URL = "https://api.firstflow.app";
3
+ var FIRSTFLOW_SERVER_PACKAGE_VERSION = "0.0.1-alpha.0";
4
+
5
+ // src/runtime.ts
6
+ import { NodeSDK } from "@opentelemetry/sdk-node";
7
+ import { resourceFromAttributes } from "@opentelemetry/resources";
8
+ import { OpenAIInstrumentation } from "@opentelemetry/instrumentation-openai";
9
+ import { AnthropicInstrumentation } from "@traceloop/instrumentation-anthropic";
10
+
1
11
  // node_modules/@opentelemetry/api/build/esm/version.js
2
12
  var VERSION = "1.9.1";
3
13
 
@@ -500,412 +510,6 @@ var PropagationAPI = class _PropagationAPI {
500
510
  // node_modules/@opentelemetry/api/build/esm/propagation-api.js
501
511
  var propagation = PropagationAPI.getInstance();
502
512
 
503
- // src/wrap.ts
504
- var INSTRUMENTED_PATHS = /* @__PURE__ */ new Set([
505
- "chat.completions.create",
506
- "embeddings.create",
507
- "responses.create",
508
- "messages.create"
509
- ]);
510
- var warnedIncompleteTagging = false;
511
- function parseFirstflowMeta(args) {
512
- const nextArgs = [...args];
513
- for (let i = 0; i < nextArgs.length; i++) {
514
- const a = nextArgs[i];
515
- if (typeof a !== "object" || a === null) continue;
516
- const rec = a;
517
- if (!("firstflowAgentId" in rec) && !("sessionId" in rec) && !("userId" in rec)) {
518
- continue;
519
- }
520
- const agentId = typeof rec.firstflowAgentId === "string" ? rec.firstflowAgentId.trim() : "";
521
- const sessionId = typeof rec.sessionId === "string" ? rec.sessionId.trim() : "";
522
- const userId = typeof rec.userId === "string" ? rec.userId.trim() : "";
523
- const { firstflowAgentId: _a, sessionId: _s, userId: _u, ...rest } = rec;
524
- nextArgs[i] = rest;
525
- if (!agentId || !sessionId || !userId) {
526
- if (!warnedIncompleteTagging) {
527
- warnedIncompleteTagging = true;
528
- console.warn(
529
- "[@firstflow/core] LLM call tagged with some but not all of `firstflowAgentId`, `sessionId`, `userId` \u2014 the call was NOT observed. Provide all three to track it."
530
- );
531
- }
532
- return { nextArgs };
533
- }
534
- return { nextArgs, meta: { agentId, userId, conversationId: sessionId } };
535
- }
536
- return { nextArgs };
537
- }
538
- function extractModel(nextArgs) {
539
- const body = nextArgs[0];
540
- if (!body || typeof body !== "object") return void 0;
541
- const m = body.model;
542
- return typeof m === "string" ? m : void 0;
543
- }
544
- function detectProvider(path) {
545
- return path === "messages.create" ? "anthropic" : "openai";
546
- }
547
- function injectOpenAiStreamUsage(nextArgs) {
548
- const body = nextArgs[0];
549
- if (!body || typeof body !== "object") return nextArgs;
550
- const b = body;
551
- if (b.stream !== true) return nextArgs;
552
- const existing = b.stream_options ?? {};
553
- if (existing.include_usage === true) return nextArgs;
554
- const patched = [...nextArgs];
555
- patched[0] = { ...b, stream_options: { ...existing, include_usage: true } };
556
- return patched;
557
- }
558
- function extractOpenAiResponseMeta(res) {
559
- const usage = res.usage;
560
- const details = usage?.prompt_tokens_details;
561
- const choice = Array.isArray(res.choices) ? res.choices[0] : void 0;
562
- return {
563
- inputTokens: typeof usage?.prompt_tokens === "number" ? usage.prompt_tokens : void 0,
564
- outputTokens: typeof usage?.completion_tokens === "number" ? usage.completion_tokens : void 0,
565
- cacheReadTokens: typeof details?.cached_tokens === "number" ? details.cached_tokens : void 0,
566
- finishReason: typeof choice?.finish_reason === "string" ? choice.finish_reason : void 0
567
- };
568
- }
569
- function extractAnthropicResponseMeta(res) {
570
- const usage = res.usage;
571
- return {
572
- inputTokens: typeof usage?.input_tokens === "number" ? usage.input_tokens : void 0,
573
- outputTokens: typeof usage?.output_tokens === "number" ? usage.output_tokens : void 0,
574
- cacheReadTokens: typeof usage?.cache_read_input_tokens === "number" ? usage.cache_read_input_tokens : void 0,
575
- cacheCreationTokens: typeof usage?.cache_creation_input_tokens === "number" ? usage.cache_creation_input_tokens : void 0,
576
- finishReason: typeof res.stop_reason === "string" ? res.stop_reason : void 0
577
- };
578
- }
579
- function accumOpenAiStreamChunkMeta(chunk, acc) {
580
- if (!chunk || typeof chunk !== "object") return;
581
- const c = chunk;
582
- if (c.usage && typeof c.usage === "object") {
583
- const usage = c.usage;
584
- const details = usage.prompt_tokens_details;
585
- if (typeof usage.prompt_tokens === "number") acc.inputTokens = usage.prompt_tokens;
586
- if (typeof usage.completion_tokens === "number") acc.outputTokens = usage.completion_tokens;
587
- if (typeof details?.cached_tokens === "number") acc.cacheReadTokens = details.cached_tokens;
588
- }
589
- if (Array.isArray(c.choices) && c.choices[0]) {
590
- const choice = c.choices[0];
591
- if (typeof choice.finish_reason === "string" && choice.finish_reason) {
592
- acc.finishReason = choice.finish_reason;
593
- }
594
- }
595
- }
596
- function accumAnthropicStreamChunkMeta(chunk, acc) {
597
- if (!chunk || typeof chunk !== "object") return;
598
- const c = chunk;
599
- if (c.type === "message_start") {
600
- const msg = c.message;
601
- const usage = msg?.usage;
602
- if (typeof usage?.input_tokens === "number") acc.inputTokens = usage.input_tokens;
603
- if (typeof usage?.cache_read_input_tokens === "number")
604
- acc.cacheReadTokens = usage.cache_read_input_tokens;
605
- if (typeof usage?.cache_creation_input_tokens === "number")
606
- acc.cacheCreationTokens = usage.cache_creation_input_tokens;
607
- }
608
- if (c.type === "message_delta") {
609
- const usage = c.usage;
610
- const delta = c.delta;
611
- if (typeof usage?.output_tokens === "number") acc.outputTokens = usage.output_tokens;
612
- if (typeof delta?.stop_reason === "string") acc.finishReason = delta.stop_reason;
613
- }
614
- }
615
- function isAsyncIterable(v) {
616
- return typeof v === "object" && v !== null && Symbol.asyncIterator in v && typeof v[Symbol.asyncIterator] === "function";
617
- }
618
- function streamChunkText(chunk) {
619
- if (!chunk || typeof chunk !== "object") return "";
620
- const c = chunk;
621
- if (Array.isArray(c.choices)) {
622
- const choice = c.choices[0];
623
- if (choice && typeof choice === "object") {
624
- const delta = choice.delta;
625
- if (delta && typeof delta === "object") {
626
- const content = delta.content;
627
- if (typeof content === "string") return content;
628
- }
629
- }
630
- }
631
- if (c.type === "content_block_delta") {
632
- const delta = c.delta;
633
- if (delta && typeof delta === "object") {
634
- const d = delta;
635
- if (d.type === "text_delta" && typeof d.text === "string") return d.text;
636
- }
637
- }
638
- return "";
639
- }
640
- function wrapAsyncIterableForAssistantHook(stream, hook, provider, startTime) {
641
- return {
642
- async *[Symbol.asyncIterator]() {
643
- let content = "";
644
- let firstChunkTime = -1;
645
- let streamError;
646
- const acc = {};
647
- const accumChunkMeta = provider === "anthropic" ? accumAnthropicStreamChunkMeta : accumOpenAiStreamChunkMeta;
648
- try {
649
- for await (const chunk of stream) {
650
- if (firstChunkTime === -1) firstChunkTime = Date.now();
651
- content += streamChunkText(chunk);
652
- accumChunkMeta(chunk, acc);
653
- yield chunk;
654
- }
655
- } catch (err) {
656
- streamError = err;
657
- throw err;
658
- } finally {
659
- hook(content, {
660
- ...acc,
661
- httpStatus: streamError != null ? streamError.status ?? 500 : 200,
662
- latencyMs: Date.now() - startTime,
663
- timeToFirstTokenMs: firstChunkTime === -1 ? void 0 : firstChunkTime - startTime
664
- });
665
- }
666
- }
667
- };
668
- }
669
- function firstArgBodyStream(nextArgs) {
670
- const body = nextArgs[0];
671
- return !!(body && typeof body === "object" && body !== null && body.stream === true);
672
- }
673
- function extractAnthropicAssistantText(res) {
674
- const content = res.content;
675
- if (!Array.isArray(content)) return "";
676
- let acc = "";
677
- for (const item of content) {
678
- if (item && typeof item === "object" && !Array.isArray(item)) {
679
- const o = item;
680
- if (o.type === "text" && typeof o.text === "string") {
681
- acc += o.text;
682
- }
683
- }
684
- }
685
- return acc;
686
- }
687
- function extractOpenAiAssistantText(res) {
688
- if (!Array.isArray(res.choices) || !res.choices[0] || typeof res.choices[0] !== "object") {
689
- return "";
690
- }
691
- const message = res.choices[0].message;
692
- return typeof message?.content === "string" ? message.content : "";
693
- }
694
- function isThenable(v) {
695
- return !!v && (typeof v === "object" || typeof v === "function") && "then" in v && typeof v.then === "function";
696
- }
697
- function messageContentToText(content) {
698
- if (typeof content === "string") return content;
699
- if (!Array.isArray(content)) return "";
700
- let acc = "";
701
- for (const part of content) {
702
- if (part && typeof part === "object" && !Array.isArray(part)) {
703
- const p = part;
704
- if (p.type === "text" && typeof p.text === "string") acc += p.text;
705
- }
706
- }
707
- return acc;
708
- }
709
- function extractRecentMessages(nextArgs) {
710
- const body = nextArgs[0];
711
- if (!body || typeof body !== "object") return [];
712
- const messages = body.messages;
713
- if (!Array.isArray(messages)) return [];
714
- const out = [];
715
- for (const m of messages) {
716
- if (!m || typeof m !== "object") continue;
717
- const rec = m;
718
- if (rec.role !== "user" && rec.role !== "assistant") continue;
719
- const content = messageContentToText(rec.content).trim();
720
- if (!content) continue;
721
- out.push({ role: rec.role, content });
722
- }
723
- return out;
724
- }
725
- function extractTurnStartUserMessage(nextArgs) {
726
- const body = nextArgs[0];
727
- if (!body || typeof body !== "object") return void 0;
728
- const messages = body.messages;
729
- if (!Array.isArray(messages) || messages.length === 0) return void 0;
730
- const last = messages[messages.length - 1];
731
- if (!last || typeof last !== "object") return void 0;
732
- const rec = last;
733
- if (rec.role !== "user") return void 0;
734
- const text = messageContentToText(rec.content).trim();
735
- return text || void 0;
736
- }
737
- function withFirstflowBaggage(meta, fn) {
738
- const base = propagation.getBaggage(context.active()) ?? propagation.createBaggage();
739
- const bag = base.setEntry("firstflow.user_id", { value: meta.userId }).setEntry("firstflow.agent_id", { value: meta.agentId });
740
- return context.with(propagation.setBaggage(context.active(), bag), fn);
741
- }
742
- function maybeObserveUserMessage(path, nextArgs, meta, ctx) {
743
- if (!ctx.onUserMessage) return;
744
- if (path !== "chat.completions.create" && path !== "messages.create") return;
745
- if (!meta.conversationId) return;
746
- const content = extractTurnStartUserMessage(nextArgs);
747
- if (!content) return;
748
- const recentMessages = extractRecentMessages(nextArgs);
749
- ctx.onUserMessage({
750
- agentId: meta.agentId,
751
- conversationId: meta.conversationId,
752
- userId: meta.userId,
753
- role: "user",
754
- content,
755
- model: extractModel(nextArgs),
756
- provider: detectProvider(path),
757
- ...recentMessages.length ? { recentMessages } : {}
758
- });
759
- }
760
- function maybeAttachAssistantHook(path, nextArgs, result, meta, ctx, startTime) {
761
- if (!ctx.onAssistantComplete) return void 0;
762
- if (path !== "chat.completions.create" && path !== "messages.create") {
763
- return void 0;
764
- }
765
- const convId = meta.conversationId;
766
- const recentMessages = extractRecentMessages(nextArgs);
767
- const model = extractModel(nextArgs);
768
- const provider = detectProvider(path);
769
- const extractFull = path === "chat.completions.create" ? extractOpenAiAssistantText : extractAnthropicAssistantText;
770
- const extractMeta = path === "chat.completions.create" ? extractOpenAiResponseMeta : extractAnthropicResponseMeta;
771
- const emit = (content, llmMeta) => {
772
- if (!convId) return;
773
- ctx.onAssistantComplete?.({
774
- agentId: meta.agentId,
775
- conversationId: convId,
776
- userId: meta.userId,
777
- role: "assistant",
778
- content,
779
- model,
780
- provider,
781
- ...llmMeta,
782
- ...recentMessages.length ? { recentMessages } : {}
783
- });
784
- };
785
- if (firstArgBodyStream(nextArgs)) {
786
- const wrapStream = (stream) => wrapAsyncIterableForAssistantHook(
787
- stream,
788
- (content, streamMeta) => emit(content, streamMeta),
789
- provider,
790
- startTime
791
- );
792
- if (isAsyncIterable(result)) {
793
- return wrapStream(result);
794
- }
795
- if (isThenable(result)) {
796
- return result.then(
797
- (resolved) => isAsyncIterable(resolved) ? wrapStream(resolved) : resolved
798
- );
799
- }
800
- return void 0;
801
- }
802
- if (isThenable(result)) {
803
- return result.then((res) => {
804
- const latencyMs = Date.now() - startTime;
805
- const resMeta = extractMeta(res ?? {});
806
- emit(extractFull(res ?? {}), { ...resMeta, latencyMs, httpStatus: 200 });
807
- return res;
808
- });
809
- }
810
- return void 0;
811
- }
812
- function wrapInstrumentedInvocation(path, original, thisArg, args, ctx) {
813
- const parsed = parseFirstflowMeta(args);
814
- const { meta } = parsed;
815
- let nextArgs = parsed.nextArgs;
816
- if (path === "chat.completions.create") {
817
- nextArgs = injectOpenAiStreamUsage(nextArgs);
818
- }
819
- const startTime = Date.now();
820
- const run = () => {
821
- if (meta) maybeObserveUserMessage(path, nextArgs, meta, ctx);
822
- let result;
823
- try {
824
- result = Reflect.apply(original, thisArg, nextArgs);
825
- } catch (err) {
826
- if (meta?.conversationId && ctx.onError) {
827
- ctx.onError({
828
- agentId: meta.agentId,
829
- conversationId: meta.conversationId,
830
- userId: meta.userId,
831
- isError: true,
832
- error: err instanceof Error ? err.message : String(err),
833
- model: extractModel(nextArgs),
834
- provider: detectProvider(path),
835
- latencyMs: Date.now() - startTime,
836
- httpStatus: err.status
837
- });
838
- }
839
- throw err;
840
- }
841
- if (isThenable(result) && meta?.conversationId && ctx.onError) {
842
- result = result.catch((err) => {
843
- ctx.onError({
844
- agentId: meta.agentId,
845
- conversationId: meta.conversationId,
846
- userId: meta.userId,
847
- isError: true,
848
- error: err instanceof Error ? err.message : String(err),
849
- model: extractModel(nextArgs),
850
- provider: detectProvider(path),
851
- latencyMs: Date.now() - startTime,
852
- httpStatus: err.status
853
- });
854
- return Promise.reject(err);
855
- });
856
- }
857
- if (meta) {
858
- const hooked = maybeAttachAssistantHook(path, nextArgs, result, meta, ctx, startTime);
859
- if (hooked !== void 0) return hooked;
860
- }
861
- return result;
862
- };
863
- if (meta) {
864
- return withFirstflowBaggage(meta, run);
865
- }
866
- return run();
867
- }
868
- function createNestedProxy(target, path, ctx) {
869
- return new Proxy(target, {
870
- get(_t, prop, receiver) {
871
- const key = String(prop);
872
- const value = Reflect.get(target, prop, receiver);
873
- if (typeof value === "function") {
874
- const joined = [...path, key].join(".");
875
- return (...fnArgs) => {
876
- if (INSTRUMENTED_PATHS.has(joined)) {
877
- return wrapInstrumentedInvocation(
878
- joined,
879
- value,
880
- target,
881
- fnArgs,
882
- ctx
883
- );
884
- }
885
- return Reflect.apply(value, target, fnArgs);
886
- };
887
- }
888
- if (value !== null && typeof value === "object") {
889
- return createNestedProxy(value, [...path, key], ctx);
890
- }
891
- return value;
892
- }
893
- });
894
- }
895
- function wrapClient(client, ctx) {
896
- return createNestedProxy(client, [], ctx);
897
- }
898
-
899
- // src/constants.ts
900
- var DEFAULT_FIRSTFLOW_BASE_URL = "https://api.firstflow.app";
901
- var FIRSTFLOW_SERVER_PACKAGE_VERSION = "0.0.1-alpha.0";
902
-
903
- // src/runtime.ts
904
- import { NodeSDK } from "@opentelemetry/sdk-node";
905
- import { resourceFromAttributes } from "@opentelemetry/resources";
906
- import { OpenAIInstrumentation } from "@opentelemetry/instrumentation-openai";
907
- import { AnthropicInstrumentation } from "@traceloop/instrumentation-anthropic";
908
-
909
513
  // src/firstflow-span-processor.ts
910
514
  var FLUSH_SIZE = 20;
911
515
  var FLUSH_INTERVAL_MS = 2e3;
@@ -1751,7 +1355,8 @@ function getRuntime() {
1751
1355
  }
1752
1356
 
1753
1357
  export {
1754
- wrapClient,
1358
+ context,
1359
+ propagation,
1755
1360
  DEFAULT_FIRSTFLOW_BASE_URL,
1756
1361
  FIRSTFLOW_SERVER_PACKAGE_VERSION,
1757
1362
  getRuntime