@carbon-js/sdk 0.0.7 → 0.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.
Files changed (43) hide show
  1. package/dist/ai/anthropic/event-factory.d.mts +2 -0
  2. package/dist/ai/anthropic/event-factory.mjs +4 -2
  3. package/dist/ai/anthropic/fns/message-create.d.mts +1 -0
  4. package/dist/ai/anthropic/fns/message-create.mjs +2 -1
  5. package/dist/ai/anthropic/fns/message-stream.d.mts +1 -0
  6. package/dist/ai/anthropic/fns/message-stream.mjs +2 -1
  7. package/dist/ai/anthropic/wrap.d.mts +1 -0
  8. package/dist/ai/openai/event-factory.d.mts +2 -0
  9. package/dist/ai/openai/event-factory.mjs +6 -4
  10. package/dist/ai/openai/fns/chat-completions-create.d.mts +1 -0
  11. package/dist/ai/openai/fns/chat-completions-create.mjs +2 -1
  12. package/dist/ai/openai/fns/chat-completions-run-tools.d.mts +1 -0
  13. package/dist/ai/openai/fns/chat-completions-run-tools.mjs +3 -2
  14. package/dist/ai/openai/fns/chat-completions-stream.d.mts +1 -0
  15. package/dist/ai/openai/fns/chat-completions-stream.mjs +2 -1
  16. package/dist/ai/openai/fns/response-create.d.mts +1 -0
  17. package/dist/ai/openai/fns/response-create.mjs +2 -1
  18. package/dist/ai/openai/fns/response-stream.d.mts +1 -0
  19. package/dist/ai/openai/fns/response-stream.mjs +2 -1
  20. package/dist/ai/openai/wrap.d.mts +1 -0
  21. package/dist/ai/vercel/event-factory.d.mts +2 -1
  22. package/dist/ai/vercel/event-factory.mjs +9 -5
  23. package/dist/ai/vercel/fns/tool-loop-agent.d.mts +1 -0
  24. package/dist/ai/vercel/recorder.d.mts +4 -0
  25. package/dist/ai/vercel/recorder.mjs +15 -5
  26. package/dist/ai/vercel/utils/telemetry.d.mts +1 -0
  27. package/dist/ai/vercel/utils/telemetry.mjs +15 -12
  28. package/dist/ai/vercel/wrap.d.mts +1 -0
  29. package/dist/ai.d.mts +1 -0
  30. package/dist/core/carbon.d.mts +3 -0
  31. package/dist/core/carbon.mjs +18 -0
  32. package/dist/core/tools/wrap-tool.mjs +1 -1
  33. package/dist/core/utils/build-events.mjs +1 -1
  34. package/dist/core/utils/call-scope.d.mts +11 -0
  35. package/dist/core/utils/call-scope.mjs +15 -0
  36. package/dist/core/utils/instrumentation.d.mts +2 -2
  37. package/dist/core/utils/instrumentation.mjs +3 -3
  38. package/dist/index.d.mts +1 -0
  39. package/dist/internal/schema/events.d.mts +19 -17
  40. package/dist/internal/schema/events.mjs +11 -4
  41. package/dist/lib/constants.d.mts +3 -0
  42. package/dist/lib/constants.mjs +5 -0
  43. package/package.json +1 -1
@@ -6,8 +6,10 @@ import 'zod';
6
6
  type T_LlmOutput = NonNullable<T_LLMEvent["properties"]["llm"]["output"]>;
7
7
  declare class AnthropicEventFactory {
8
8
  private readonly carbonObject?;
9
+ private readonly baseURL?;
9
10
  constructor(args: {
10
11
  carbonObject?: T_CarbonObject;
12
+ baseURL?: string;
11
13
  });
12
14
  createMessageEvents(args: {
13
15
  body: MessageCreateParamsBase;
@@ -4,8 +4,10 @@ import { createSdkInstrumentation } from "../../core/utils/instrumentation.mjs";
4
4
  import { stringify } from "../../utils/stringify.mjs";
5
5
  class AnthropicEventFactory {
6
6
  carbonObject;
7
+ baseURL;
7
8
  constructor(args) {
8
9
  this.carbonObject = args.carbonObject;
10
+ this.baseURL = args.baseURL;
9
11
  }
10
12
  createMessageEvents(args) {
11
13
  const usage = toAnthropicAiUsage({ usage: args.message?.usage });
@@ -17,10 +19,10 @@ class AnthropicEventFactory {
17
19
  instrumentation: createSdkInstrumentation({
18
20
  sourceFunction: args.sourceFunction,
19
21
  sourcePackage: "@anthropic-ai/sdk",
20
- sourceProvider: "anthropic"
22
+ source: "anthropic"
21
23
  }),
22
24
  llm: {
23
- gateway: "anthropic",
25
+ ...this.baseURL ? { apiUrl: this.baseURL } : {},
24
26
  model: String(args.message?.model ?? args.body.model ?? "unknown"),
25
27
  input: {
26
28
  prompt: stringify({ value: args.body.messages }),
@@ -6,6 +6,7 @@ import 'zod';
6
6
  import '../../../core/runtime.mjs';
7
7
  import '../../../core/transport/types.mjs';
8
8
  import '../../../core/tools/wrap-tool.mjs';
9
+ import '../../../core/utils/call-scope.mjs';
9
10
 
10
11
  declare function createWrappedMessageCreate(args: {
11
12
  client: Anthropic;
@@ -5,7 +5,8 @@ function createWrappedMessageCreate(args) {
5
5
  return ((body, requestOptions) => {
6
6
  const { carbon: carbonObject, ...anthropicBody } = body;
7
7
  const factory = new AnthropicEventFactory({
8
- carbonObject
8
+ carbonObject,
9
+ baseURL: args.client.baseURL
9
10
  });
10
11
  const startTimeMs = Date.now();
11
12
  let messagePromise;
@@ -6,6 +6,7 @@ import 'zod';
6
6
  import '../../../core/runtime.mjs';
7
7
  import '../../../core/transport/types.mjs';
8
8
  import '../../../core/tools/wrap-tool.mjs';
9
+ import '../../../core/utils/call-scope.mjs';
9
10
 
10
11
  declare function createWrappedMessageStream(args: {
11
12
  client: Anthropic;
@@ -5,7 +5,8 @@ function createWrappedMessageStream(args) {
5
5
  return ((body, requestOptions) => {
6
6
  const { carbon: carbonObject, ...anthropicBody } = body;
7
7
  const factory = new AnthropicEventFactory({
8
- carbonObject
8
+ carbonObject,
9
+ baseURL: args.client.baseURL
9
10
  });
10
11
  const startTimeMs = Date.now();
11
12
  let messageStream;
@@ -7,6 +7,7 @@ import 'zod';
7
7
  import '../../core/runtime.mjs';
8
8
  import '../../core/transport/types.mjs';
9
9
  import '../../core/tools/wrap-tool.mjs';
10
+ import '../../core/utils/call-scope.mjs';
10
11
 
11
12
  declare const wrapAnthropicSdk: (client: Anthropic, carbon: Carbon) => T_WrappedAnthropicSdk;
12
13
 
@@ -7,8 +7,10 @@ import 'zod';
7
7
  type T_LlmOutput = NonNullable<T_LLMEvent["properties"]["llm"]["output"]>;
8
8
  declare class OpenAIEventFactory {
9
9
  private readonly carbonObject?;
10
+ private readonly baseURL?;
10
11
  constructor(args: {
11
12
  carbonObject?: T_CarbonObject;
13
+ baseURL?: string;
12
14
  });
13
15
  createResponseEvents(args: {
14
16
  body: Partial<ResponseCreateParamsBase>;
@@ -4,8 +4,10 @@ import { createSdkInstrumentation } from "../../core/utils/instrumentation.mjs";
4
4
  import { stringify } from "../../utils/stringify.mjs";
5
5
  class OpenAIEventFactory {
6
6
  carbonObject;
7
+ baseURL;
7
8
  constructor(args) {
8
9
  this.carbonObject = args.carbonObject;
10
+ this.baseURL = args.baseURL;
9
11
  }
10
12
  createResponseEvents(args) {
11
13
  const usage = toResponseAiUsage({ usage: args.response?.usage });
@@ -17,10 +19,10 @@ class OpenAIEventFactory {
17
19
  instrumentation: createSdkInstrumentation({
18
20
  sourceFunction: args.sourceFunction,
19
21
  sourcePackage: "openai",
20
- sourceProvider: "openai"
22
+ source: "openai"
21
23
  }),
22
24
  llm: {
23
- gateway: "openai",
25
+ ...this.baseURL ? { apiUrl: this.baseURL } : {},
24
26
  model: String(args.response?.model ?? args.body.model ?? "unknown"),
25
27
  input: this.createResponseLlmInput({ body: args.body }),
26
28
  output: {
@@ -49,10 +51,10 @@ class OpenAIEventFactory {
49
51
  instrumentation: createSdkInstrumentation({
50
52
  sourceFunction: args.sourceFunction,
51
53
  sourcePackage: "openai",
52
- sourceProvider: "openai"
54
+ source: "openai"
53
55
  }),
54
56
  llm: {
55
- gateway: "openai",
57
+ ...this.baseURL ? { apiUrl: this.baseURL } : {},
56
58
  model: String(args.completion?.model ?? args.body.model ?? "unknown"),
57
59
  input: this.createChatCompletionLlmInput({ body: args.body }),
58
60
  output: {
@@ -6,6 +6,7 @@ import 'zod';
6
6
  import '../../../core/runtime.mjs';
7
7
  import '../../../core/transport/types.mjs';
8
8
  import '../../../core/tools/wrap-tool.mjs';
9
+ import '../../../core/utils/call-scope.mjs';
9
10
 
10
11
  declare function createWrappedChatCompletionCreate(args: {
11
12
  client: OpenAI;
@@ -6,7 +6,8 @@ function createWrappedChatCompletionCreate(args) {
6
6
  return ((body, requestOptions) => {
7
7
  const { carbon: carbonObject, [SKIP_CAPTURE_FIELD]: skipCapture, ...openAiBody } = body;
8
8
  const factory = new OpenAIEventFactory({
9
- carbonObject
9
+ carbonObject,
10
+ baseURL: args.client.baseURL
10
11
  });
11
12
  const startTimeMs = Date.now();
12
13
  const bodyWithUsage = openAiBody.stream ? {
@@ -6,6 +6,7 @@ import 'zod';
6
6
  import '../../../core/runtime.mjs';
7
7
  import '../../../core/transport/types.mjs';
8
8
  import '../../../core/tools/wrap-tool.mjs';
9
+ import '../../../core/utils/call-scope.mjs';
9
10
 
10
11
  declare function createWrappedChatCompletionRunTools(args: {
11
12
  client: OpenAI;
@@ -17,7 +17,8 @@ function createWrappedChatCompletionRunTools(args) {
17
17
  [SKIP_CAPTURE_FIELD]: true
18
18
  };
19
19
  const factory = new OpenAIEventFactory({
20
- carbonObject: tracedCarbonObject
20
+ carbonObject: tracedCarbonObject,
21
+ baseURL: args.client.baseURL
21
22
  });
22
23
  const startTimeMs = Date.now();
23
24
  let runner;
@@ -87,7 +88,7 @@ function attachRunToolsCapture(args) {
87
88
  instrumentation: createSdkInstrumentation({
88
89
  sourceFunction: "chat.completions.runTools",
89
90
  sourcePackage: "openai",
90
- sourceProvider: "openai"
91
+ source: "openai"
91
92
  }),
92
93
  startTimeMs: toolCall?.startTimeMs ?? endTimeMs,
93
94
  status: createOkStatus(),
@@ -6,6 +6,7 @@ import 'zod';
6
6
  import '../../../core/runtime.mjs';
7
7
  import '../../../core/transport/types.mjs';
8
8
  import '../../../core/tools/wrap-tool.mjs';
9
+ import '../../../core/utils/call-scope.mjs';
9
10
 
10
11
  declare function createWrappedChatCompletionStream(args: {
11
12
  client: OpenAI;
@@ -12,7 +12,8 @@ function createWrappedChatCompletionStream(args) {
12
12
  }
13
13
  };
14
14
  const factory = new OpenAIEventFactory({
15
- carbonObject
15
+ carbonObject,
16
+ baseURL: args.client.baseURL
16
17
  });
17
18
  const startTimeMs = Date.now();
18
19
  let completionStream;
@@ -6,6 +6,7 @@ import 'zod';
6
6
  import '../../../core/runtime.mjs';
7
7
  import '../../../core/transport/types.mjs';
8
8
  import '../../../core/tools/wrap-tool.mjs';
9
+ import '../../../core/utils/call-scope.mjs';
9
10
 
10
11
  declare function createWrappedResponseCreate(args: {
11
12
  client: OpenAI;
@@ -5,7 +5,8 @@ function createWrappedResponseCreate(args) {
5
5
  return ((body, requestOptions) => {
6
6
  const { carbon: carbonObject, ...openAiBody } = body;
7
7
  const factory = new OpenAIEventFactory({
8
- carbonObject
8
+ carbonObject,
9
+ baseURL: args.client.baseURL
9
10
  });
10
11
  const startTimeMs = Date.now();
11
12
  let responsePromise;
@@ -6,6 +6,7 @@ import 'zod';
6
6
  import '../../../core/runtime.mjs';
7
7
  import '../../../core/transport/types.mjs';
8
8
  import '../../../core/tools/wrap-tool.mjs';
9
+ import '../../../core/utils/call-scope.mjs';
9
10
 
10
11
  declare function createWrappedResponseStream(args: {
11
12
  client: OpenAI;
@@ -5,7 +5,8 @@ function createWrappedResponseStream(args) {
5
5
  return ((body, requestOptions) => {
6
6
  const { carbon: carbonObject, ...openAiBody } = body;
7
7
  const factory = new OpenAIEventFactory({
8
- carbonObject
8
+ carbonObject,
9
+ baseURL: args.client.baseURL
9
10
  });
10
11
  const startTimeMs = Date.now();
11
12
  let responseStream;
@@ -7,6 +7,7 @@ import 'zod';
7
7
  import '../../core/runtime.mjs';
8
8
  import '../../core/transport/types.mjs';
9
9
  import '../../core/tools/wrap-tool.mjs';
10
+ import '../../core/utils/call-scope.mjs';
10
11
 
11
12
  declare const wrapOpenAISdk: (client: OpenAI, carbon: Carbon) => T_WrappedOpenAISdk;
12
13
 
@@ -6,7 +6,7 @@ import 'zod';
6
6
  type T_VercelRecordingMode = "generate" | "stream";
7
7
  type T_LlmInputEvent = OnStartEvent | OnFinishEvent | OnStepStartEvent | OnStepFinishEvent | OnToolCallStartEvent | OnToolCallFinishEvent;
8
8
  type T_PendingLlmEvent = Pick<T_LLMEvent, "id" | "startTimeMs"> & {
9
- gateway: NonNullable<T_LLMEvent["properties"]["llm"]["gateway"]>;
9
+ apiUrl?: NonNullable<T_LLMEvent["properties"]["llm"]["apiUrl"]>;
10
10
  input: NonNullable<T_LLMEvent["properties"]["llm"]["input"]>;
11
11
  model: T_LLMEvent["properties"]["llm"]["model"];
12
12
  };
@@ -36,6 +36,7 @@ declare class VercelEventFactory {
36
36
  startTimeMs?: number;
37
37
  }): T_PendingToolEvent;
38
38
  createCompletedLlmEvent(args: {
39
+ apiUrl?: string;
39
40
  endTimeMs: number;
40
41
  event?: OnFinishEvent | OnStepFinishEvent;
41
42
  pendingLlm: T_PendingLlmEvent;
@@ -1,6 +1,7 @@
1
1
  import { buildLlmEvent, buildToolEvent } from "../../core/utils/build-events.mjs";
2
2
  import { EMPTY_AI_USAGE } from "../../core/utils/build-events.mjs";
3
3
  import { createSdkInstrumentation } from "../../core/utils/instrumentation.mjs";
4
+ import { CONSTANTS } from "../../lib/constants.mjs";
4
5
  import { generateId } from "../../utils/ids.mjs";
5
6
  import { stringify } from "../../utils/stringify.mjs";
6
7
  class VercelEventFactory {
@@ -22,7 +23,6 @@ class VercelEventFactory {
22
23
  system: "",
23
24
  tools: []
24
25
  },
25
- gateway: "unknown",
26
26
  model: "unknown",
27
27
  startTimeMs: args.startTimeMs ?? Date.now()
28
28
  };
@@ -41,7 +41,10 @@ class VercelEventFactory {
41
41
  tools: "tools" in args.event ? args.event.tools : void 0
42
42
  })
43
43
  },
44
- gateway: args.event.model?.provider ?? "unknown",
44
+ // The Vercel AI Gateway is the only serving base URL we can name without an instrumented fetch;
45
+ // every other provider's base URL is captured via carbon.fetch, like the OpenAI/Anthropic
46
+ // wrappers. Ingest resolves the provider + cost from this apiUrl.
47
+ ...args.event.model?.provider === "gateway" ? { apiUrl: CONSTANTS.vercel.gatewayBaseUrl } : {},
45
48
  model: args.event.model?.modelId ?? "unknown",
46
49
  startTimeMs: args.startTimeMs ?? Date.now()
47
50
  };
@@ -55,6 +58,7 @@ class VercelEventFactory {
55
58
  };
56
59
  }
57
60
  createCompletedLlmEvent(args) {
61
+ const apiUrl = args.pendingLlm.apiUrl ?? args.apiUrl;
58
62
  return buildLlmEvent({
59
63
  context: this.carbonObject?.context,
60
64
  additionalProperties: this.carbonObject?.additionalProperties,
@@ -63,10 +67,10 @@ class VercelEventFactory {
63
67
  instrumentation: createSdkInstrumentation({
64
68
  sourceFunction: this.sourceFunction,
65
69
  sourcePackage: "ai",
66
- sourceProvider: "vercel"
70
+ source: "vercel"
67
71
  }),
68
72
  llm: {
69
- gateway: args.pendingLlm.gateway,
73
+ ...apiUrl ? { apiUrl } : {},
70
74
  model: args.pendingLlm.model,
71
75
  input: args.pendingLlm.input,
72
76
  output: {
@@ -94,7 +98,7 @@ class VercelEventFactory {
94
98
  instrumentation: createSdkInstrumentation({
95
99
  sourceFunction: this.sourceFunction,
96
100
  sourcePackage: "ai",
97
- sourceProvider: "vercel"
101
+ source: "vercel"
98
102
  }),
99
103
  startTimeMs: args.pendingTool.startTimeMs,
100
104
  status: args.status,
@@ -7,6 +7,7 @@ import 'zod';
7
7
  import '../../../core/runtime.mjs';
8
8
  import '../../../core/transport/types.mjs';
9
9
  import '../../../core/tools/wrap-tool.mjs';
10
+ import '../../../core/utils/call-scope.mjs';
10
11
 
11
12
  type T_CreateWrappedToolLoopAgent = {
12
13
  ToolLoopAgent: typeof vercelSdk.ToolLoopAgent;
@@ -7,6 +7,7 @@ import '../../internal/schema/events.mjs';
7
7
  import 'zod';
8
8
  import '../../core/transport/types.mjs';
9
9
  import '../../core/tools/wrap-tool.mjs';
10
+ import '../../core/utils/call-scope.mjs';
10
11
 
11
12
  declare class VercelRecorder {
12
13
  private readonly completedEvents;
@@ -14,6 +15,7 @@ declare class VercelRecorder {
14
15
  private readonly llmEventsByStep;
15
16
  private readonly carbon;
16
17
  private readonly toolEventsById;
18
+ private apiUrl;
17
19
  private isRecorded;
18
20
  private startTimeMs;
19
21
  constructor(args: {
@@ -22,6 +24,8 @@ declare class VercelRecorder {
22
24
  mode: T_VercelRecordingMode;
23
25
  sourceFunction: string;
24
26
  });
27
+ recordApiUrl(apiUrl: string): void;
28
+ private completeLlm;
25
29
  createIntegration(): TelemetryIntegration;
26
30
  recordError(args: {
27
31
  error: unknown;
@@ -9,6 +9,7 @@ class VercelRecorder {
9
9
  llmEventsByStep = /* @__PURE__ */ new Map();
10
10
  carbon;
11
11
  toolEventsById = /* @__PURE__ */ new Map();
12
+ apiUrl;
12
13
  isRecorded = false;
13
14
  startTimeMs = Date.now();
14
15
  constructor(args) {
@@ -19,6 +20,15 @@ class VercelRecorder {
19
20
  });
20
21
  this.carbon = args.carbon;
21
22
  }
23
+ // T_CallScope: a Carbon-capturing provider fetch records the request URL here; the completed
24
+ // event carries it as `apiUrl` so ingest resolves the serving provider via genai. First request
25
+ // wins (retries and multi-step calls share one provider).
26
+ recordApiUrl(apiUrl) {
27
+ this.apiUrl ??= apiUrl;
28
+ }
29
+ completeLlm(args) {
30
+ return this.factory.createCompletedLlmEvent({ ...args, apiUrl: this.apiUrl });
31
+ }
22
32
  createIntegration() {
23
33
  return bindTelemetryIntegration({
24
34
  onStart: () => {
@@ -64,7 +74,7 @@ class VercelRecorder {
64
74
  this.toolEventsById.clear();
65
75
  for (const pendingLlm of this.llmEventsByStep.values()) {
66
76
  this.completedEvents.push(
67
- this.factory.createCompletedLlmEvent({
77
+ this.completeLlm({
68
78
  endTimeMs,
69
79
  pendingLlm,
70
80
  status
@@ -74,7 +84,7 @@ class VercelRecorder {
74
84
  this.llmEventsByStep.clear();
75
85
  if (this.completedEvents.length === 0) {
76
86
  this.completedEvents.push(
77
- this.factory.createCompletedLlmEvent({
87
+ this.completeLlm({
78
88
  endTimeMs,
79
89
  pendingLlm: this.factory.createFallbackPendingLlmEvent({
80
90
  startTimeMs: this.startTimeMs
@@ -107,7 +117,7 @@ class VercelRecorder {
107
117
  const endTimeMs = Date.now();
108
118
  for (const [stepNumber, pendingLlm] of this.llmEventsByStep) {
109
119
  this.completedEvents.push(
110
- this.factory.createCompletedLlmEvent({
120
+ this.completeLlm({
111
121
  endTimeMs,
112
122
  event: event.steps.find((step) => step.stepNumber === stepNumber) ?? event,
113
123
  pendingLlm,
@@ -118,7 +128,7 @@ class VercelRecorder {
118
128
  this.llmEventsByStep.clear();
119
129
  if (!this.completedEvents.some((completedEvent) => completedEvent.type === "llm")) {
120
130
  this.completedEvents.push(
121
- this.factory.createCompletedLlmEvent({
131
+ this.completeLlm({
122
132
  endTimeMs,
123
133
  event,
124
134
  pendingLlm: this.factory.createPendingLlmEvent({
@@ -179,7 +189,7 @@ class VercelRecorder {
179
189
  event: args.event
180
190
  });
181
191
  this.completedEvents.push(
182
- this.factory.createCompletedLlmEvent({
192
+ this.completeLlm({
183
193
  endTimeMs: args.endTimeMs,
184
194
  event: args.event,
185
195
  pendingLlm,
@@ -6,6 +6,7 @@ import '../../../internal/schema/events.mjs';
6
6
  import 'zod';
7
7
  import '../../../core/transport/types.mjs';
8
8
  import '../../../core/tools/wrap-tool.mjs';
9
+ import '../../../core/utils/call-scope.mjs';
9
10
  import '../event-factory.mjs';
10
11
  import 'ai';
11
12
 
@@ -1,3 +1,4 @@
1
+ import { CARBON_CALL_SCOPE } from "../../../core/utils/call-scope.mjs";
1
2
  import { isPromiseLike } from "../../../utils/promise.mjs";
2
3
  import { VercelRecorder } from "../recorder.mjs";
3
4
  const runWithCarbonTelemetry = (args) => {
@@ -12,19 +13,21 @@ const runWithCarbonTelemetry = (args) => {
12
13
  args: aiArgs,
13
14
  recorder
14
15
  });
15
- try {
16
- const result = args.run(aiArgsWithCarbonTelemetry);
17
- if (isPromiseLike({ value: result })) {
18
- return Promise.resolve(result).catch(async (error) => {
19
- recorder.recordError({ error });
20
- throw error;
21
- });
16
+ return CARBON_CALL_SCOPE.run(recorder, () => {
17
+ try {
18
+ const result = args.run(aiArgsWithCarbonTelemetry);
19
+ if (isPromiseLike({ value: result })) {
20
+ return Promise.resolve(result).catch(async (error) => {
21
+ recorder.recordError({ error });
22
+ throw error;
23
+ });
24
+ }
25
+ return result;
26
+ } catch (error) {
27
+ recorder.recordError({ error });
28
+ throw error;
22
29
  }
23
- return result;
24
- } catch (error) {
25
- recorder.recordError({ error });
26
- throw error;
27
- }
30
+ });
28
31
  };
29
32
  const addCarbonTelemetry = (args) => {
30
33
  const telemetry = args.args.experimental_telemetry;
@@ -7,6 +7,7 @@ import 'zod';
7
7
  import '../../core/runtime.mjs';
8
8
  import '../../core/transport/types.mjs';
9
9
  import '../../core/tools/wrap-tool.mjs';
10
+ import '../../core/utils/call-scope.mjs';
10
11
 
11
12
  declare const wrapVercelSdk: (ai: typeof vercelSdk, carbon: Carbon) => T_WrappedVercelSdk;
12
13
 
package/dist/ai.d.mts CHANGED
@@ -8,6 +8,7 @@ import 'zod';
8
8
  import './core/runtime.mjs';
9
9
  import './core/transport/types.mjs';
10
10
  import './core/tools/wrap-tool.mjs';
11
+ import './core/utils/call-scope.mjs';
11
12
  import './ai/anthropic/types.mjs';
12
13
  import '@anthropic-ai/sdk';
13
14
  import './ai/openai/types.mjs';
@@ -1,6 +1,7 @@
1
1
  import { T_CarbonObject } from './schema/carbon-object.mjs';
2
2
  import { T_CarbonRuntimeOptions } from './runtime.mjs';
3
3
  import { T_WrapToolArgs } from './tools/wrap-tool.mjs';
4
+ import { T_FetchFn } from './utils/call-scope.mjs';
4
5
  import { T as T_LLMEvent, b as T_ToolEvent, a as T_Event } from '../internal/schema/events.mjs';
5
6
  import './transport/types.mjs';
6
7
  import 'zod';
@@ -21,6 +22,8 @@ declare class Carbon {
21
22
  }): void;
22
23
  createTraceId(): `${string}-${string}-${string}-${string}-${string}`;
23
24
  wrapTool<T_Args extends unknown[], T_Result>(args: T_WrapToolArgs<T_Args, T_Result>): (...callArgs: [...T_Args, (T_CarbonObject | undefined)?]) => T_Result;
25
+ readonly fetch: T_FetchFn;
26
+ wrapFetch(inner: T_FetchFn): T_FetchFn;
24
27
  flushPendingEvents(): Promise<void>;
25
28
  }
26
29
 
@@ -1,5 +1,6 @@
1
1
  import { CarbonRuntime } from "./runtime.mjs";
2
2
  import { createWrappedTool } from "./tools/wrap-tool.mjs";
3
+ import { CARBON_CALL_SCOPE, urlFromFetchInput } from "./utils/call-scope.mjs";
3
4
  import { generateId } from "../utils/ids.mjs";
4
5
  class Carbon {
5
6
  runtime;
@@ -26,6 +27,23 @@ class Carbon {
26
27
  }
27
28
  });
28
29
  }
30
+ // A drop-in `fetch` that records the real request URL of the in-flight wrapped call (so ingest
31
+ // resolves the serving provider from the captured base URL via genai), then delegates to the
32
+ // global fetch. Pass it to any AI SDK provider: `createOpenAI({ fetch: carbon.fetch })`. Resolves
33
+ // `globalThis.fetch` at call time so it composes with whatever else patched it (Next, Sentry, …).
34
+ fetch = (input, init) => {
35
+ CARBON_CALL_SCOPE.recordApiUrl(urlFromFetchInput(input));
36
+ return globalThis.fetch(input, init);
37
+ };
38
+ // Compose Carbon's URL capture with a customer's own fetch — the provider `fetch` option REPLACES
39
+ // the global fetch, so a customer who already passes one wraps it here:
40
+ // `createOpenAI({ fetch: carbon.wrapFetch(myFetch) })`.
41
+ wrapFetch(inner) {
42
+ return (input, init) => {
43
+ CARBON_CALL_SCOPE.recordApiUrl(urlFromFetchInput(input));
44
+ return inner(input, init);
45
+ };
46
+ }
29
47
  flushPendingEvents() {
30
48
  return this.runtime.shutdown();
31
49
  }
@@ -90,7 +90,7 @@ function captureToolResult(args) {
90
90
  instrumentation: createSdkInstrumentation({
91
91
  sourceFunction: "wrapTool",
92
92
  sourcePackage: "ai",
93
- sourceProvider: "vercel"
93
+ source: "vercel"
94
94
  }),
95
95
  startTimeMs: args.startTimeMs,
96
96
  status: args.status ?? createOkStatus(),
@@ -81,7 +81,7 @@ function buildBaseEvent(args) {
81
81
  instrumentation: args.instrumentation ?? createSdkInstrumentation({
82
82
  sourceFunction: "manual",
83
83
  sourcePackage: "ai",
84
- sourceProvider: "vercel"
84
+ source: "vercel"
85
85
  }),
86
86
  startTimeMs: args.startTimeMs,
87
87
  status: args.status,
@@ -0,0 +1,11 @@
1
+ type T_CallScope = {
2
+ recordApiUrl: (apiUrl: string) => void;
3
+ };
4
+ declare const CARBON_CALL_SCOPE: {
5
+ run<T_Result>(scope: T_CallScope, fn: () => T_Result): T_Result;
6
+ recordApiUrl(apiUrl: string): void;
7
+ };
8
+ type T_FetchFn = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
9
+ declare const urlFromFetchInput: (input: RequestInfo | URL) => string;
10
+
11
+ export { CARBON_CALL_SCOPE, type T_CallScope, type T_FetchFn, urlFromFetchInput };
@@ -0,0 +1,15 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
2
+ const storage = new AsyncLocalStorage();
3
+ const CARBON_CALL_SCOPE = {
4
+ run(scope, fn) {
5
+ return storage.run(scope, fn);
6
+ },
7
+ recordApiUrl(apiUrl) {
8
+ storage.getStore()?.recordApiUrl(apiUrl);
9
+ }
10
+ };
11
+ const urlFromFetchInput = (input) => typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
12
+ export {
13
+ CARBON_CALL_SCOPE,
14
+ urlFromFetchInput
15
+ };
@@ -1,12 +1,12 @@
1
1
  import { f as T_EventBase } from '../../internal/schema/events.mjs';
2
2
  import 'zod';
3
3
 
4
- type T_SourceProvider = T_EventBase["instrumentation"]["sourceProvider"];
4
+ type T_Source = T_EventBase["instrumentation"]["source"];
5
5
  type T_SourcePackage = T_EventBase["instrumentation"]["sourcePackage"];
6
6
  declare function createSdkInstrumentation(args: {
7
7
  sourceFunction?: string;
8
8
  sourcePackage: T_SourcePackage;
9
- sourceProvider: T_SourceProvider;
9
+ source: T_Source;
10
10
  }): T_EventBase["instrumentation"];
11
11
 
12
12
  export { createSdkInstrumentation };
@@ -1,10 +1,10 @@
1
1
  function createSdkInstrumentation(args) {
2
2
  return {
3
- provider: "carbon",
4
- providerPackage: "sdk",
3
+ vendor: "carbon",
4
+ vendorPackage: "sdk",
5
5
  sourceFunction: args.sourceFunction,
6
6
  sourcePackage: args.sourcePackage,
7
- sourceProvider: args.sourceProvider
7
+ source: args.source
8
8
  };
9
9
  }
10
10
  export {
package/dist/index.d.mts CHANGED
@@ -6,5 +6,6 @@ export { CarbonIngestError, HttpTransport } from './core/transport/http-transpor
6
6
  export { MemoryTransport } from './core/transport/memory-transport.mjs';
7
7
  export { T_EventBatch, T_EventTransport } from './core/transport/types.mjs';
8
8
  export { T_WrapToolArgs } from './core/tools/wrap-tool.mjs';
9
+ import './core/utils/call-scope.mjs';
9
10
  import './internal/schema/events.mjs';
10
11
  import 'zod';
@@ -44,9 +44,9 @@ declare const Z_EventBase: z.ZodObject<{
44
44
  }, z.core.$strip>>;
45
45
  }, z.core.$strip>;
46
46
  instrumentation: z.ZodObject<{
47
- provider: z.ZodUnion<readonly [z.ZodLiteral<"carbon">]>;
48
- providerPackage: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"sdk">]>>;
49
- sourceProvider: z.ZodUnion<readonly [z.ZodLiteral<"openai">, z.ZodLiteral<"anthropic">, z.ZodLiteral<"vercel">]>;
47
+ vendor: z.ZodUnion<readonly [z.ZodLiteral<"carbon">]>;
48
+ vendorPackage: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"sdk">]>>;
49
+ source: z.ZodUnion<readonly [z.ZodLiteral<"openai">, z.ZodLiteral<"anthropic">, z.ZodLiteral<"vercel">]>;
50
50
  sourcePackage: z.ZodUnion<readonly [z.ZodLiteral<"openai">, z.ZodLiteral<"@anthropic-ai/sdk">, z.ZodLiteral<"ai">]>;
51
51
  sourceFunction: z.ZodOptional<z.ZodString>;
52
52
  }, z.core.$strip>;
@@ -77,9 +77,9 @@ declare const Z_LLMEvent: z.ZodObject<{
77
77
  }, z.core.$strip>>;
78
78
  }, z.core.$strip>;
79
79
  instrumentation: z.ZodObject<{
80
- provider: z.ZodUnion<readonly [z.ZodLiteral<"carbon">]>;
81
- providerPackage: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"sdk">]>>;
82
- sourceProvider: z.ZodUnion<readonly [z.ZodLiteral<"openai">, z.ZodLiteral<"anthropic">, z.ZodLiteral<"vercel">]>;
80
+ vendor: z.ZodUnion<readonly [z.ZodLiteral<"carbon">]>;
81
+ vendorPackage: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"sdk">]>>;
82
+ source: z.ZodUnion<readonly [z.ZodLiteral<"openai">, z.ZodLiteral<"anthropic">, z.ZodLiteral<"vercel">]>;
83
83
  sourcePackage: z.ZodUnion<readonly [z.ZodLiteral<"openai">, z.ZodLiteral<"@anthropic-ai/sdk">, z.ZodLiteral<"ai">]>;
84
84
  sourceFunction: z.ZodOptional<z.ZodString>;
85
85
  }, z.core.$strip>;
@@ -93,8 +93,9 @@ declare const Z_LLMEvent: z.ZodObject<{
93
93
  type: z.ZodLiteral<"llm">;
94
94
  properties: z.ZodObject<{
95
95
  llm: z.ZodObject<{
96
- gateway: z.ZodOptional<z.ZodString>;
97
96
  provider: z.ZodOptional<z.ZodString>;
97
+ apiUrl: z.ZodOptional<z.ZodString>;
98
+ creator: z.ZodOptional<z.ZodString>;
98
99
  model: z.ZodString;
99
100
  input: z.ZodOptional<z.ZodObject<{
100
101
  system: z.ZodString;
@@ -159,9 +160,9 @@ declare const Z_ToolEvent: z.ZodObject<{
159
160
  }, z.core.$strip>>;
160
161
  }, z.core.$strip>;
161
162
  instrumentation: z.ZodObject<{
162
- provider: z.ZodUnion<readonly [z.ZodLiteral<"carbon">]>;
163
- providerPackage: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"sdk">]>>;
164
- sourceProvider: z.ZodUnion<readonly [z.ZodLiteral<"openai">, z.ZodLiteral<"anthropic">, z.ZodLiteral<"vercel">]>;
163
+ vendor: z.ZodUnion<readonly [z.ZodLiteral<"carbon">]>;
164
+ vendorPackage: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"sdk">]>>;
165
+ source: z.ZodUnion<readonly [z.ZodLiteral<"openai">, z.ZodLiteral<"anthropic">, z.ZodLiteral<"vercel">]>;
165
166
  sourcePackage: z.ZodUnion<readonly [z.ZodLiteral<"openai">, z.ZodLiteral<"@anthropic-ai/sdk">, z.ZodLiteral<"ai">]>;
166
167
  sourceFunction: z.ZodOptional<z.ZodString>;
167
168
  }, z.core.$strip>;
@@ -200,9 +201,9 @@ declare const Z_Event: z.ZodDiscriminatedUnion<[z.ZodObject<{
200
201
  }, z.core.$strip>>;
201
202
  }, z.core.$strip>;
202
203
  instrumentation: z.ZodObject<{
203
- provider: z.ZodUnion<readonly [z.ZodLiteral<"carbon">]>;
204
- providerPackage: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"sdk">]>>;
205
- sourceProvider: z.ZodUnion<readonly [z.ZodLiteral<"openai">, z.ZodLiteral<"anthropic">, z.ZodLiteral<"vercel">]>;
204
+ vendor: z.ZodUnion<readonly [z.ZodLiteral<"carbon">]>;
205
+ vendorPackage: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"sdk">]>>;
206
+ source: z.ZodUnion<readonly [z.ZodLiteral<"openai">, z.ZodLiteral<"anthropic">, z.ZodLiteral<"vercel">]>;
206
207
  sourcePackage: z.ZodUnion<readonly [z.ZodLiteral<"openai">, z.ZodLiteral<"@anthropic-ai/sdk">, z.ZodLiteral<"ai">]>;
207
208
  sourceFunction: z.ZodOptional<z.ZodString>;
208
209
  }, z.core.$strip>;
@@ -216,8 +217,9 @@ declare const Z_Event: z.ZodDiscriminatedUnion<[z.ZodObject<{
216
217
  type: z.ZodLiteral<"llm">;
217
218
  properties: z.ZodObject<{
218
219
  llm: z.ZodObject<{
219
- gateway: z.ZodOptional<z.ZodString>;
220
220
  provider: z.ZodOptional<z.ZodString>;
221
+ apiUrl: z.ZodOptional<z.ZodString>;
222
+ creator: z.ZodOptional<z.ZodString>;
221
223
  model: z.ZodString;
222
224
  input: z.ZodOptional<z.ZodObject<{
223
225
  system: z.ZodString;
@@ -281,9 +283,9 @@ declare const Z_Event: z.ZodDiscriminatedUnion<[z.ZodObject<{
281
283
  }, z.core.$strip>>;
282
284
  }, z.core.$strip>;
283
285
  instrumentation: z.ZodObject<{
284
- provider: z.ZodUnion<readonly [z.ZodLiteral<"carbon">]>;
285
- providerPackage: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"sdk">]>>;
286
- sourceProvider: z.ZodUnion<readonly [z.ZodLiteral<"openai">, z.ZodLiteral<"anthropic">, z.ZodLiteral<"vercel">]>;
286
+ vendor: z.ZodUnion<readonly [z.ZodLiteral<"carbon">]>;
287
+ vendorPackage: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"sdk">]>>;
288
+ source: z.ZodUnion<readonly [z.ZodLiteral<"openai">, z.ZodLiteral<"anthropic">, z.ZodLiteral<"vercel">]>;
287
289
  sourcePackage: z.ZodUnion<readonly [z.ZodLiteral<"openai">, z.ZodLiteral<"@anthropic-ai/sdk">, z.ZodLiteral<"ai">]>;
288
290
  sourceFunction: z.ZodOptional<z.ZodString>;
289
291
  }, z.core.$strip>;
@@ -27,9 +27,11 @@ const Z_EventContext = z.object({
27
27
  userId: z.string().optional()
28
28
  });
29
29
  const Z_EventInstrumentation = z.object({
30
- provider: z.union([z.literal("carbon")]),
31
- providerPackage: z.union([z.literal("sdk")]).optional(),
32
- sourceProvider: z.union([z.literal("openai"), z.literal("anthropic"), z.literal("vercel")]),
30
+ // The telemetry vendor — who produced this event. Always Carbon.
31
+ vendor: z.union([z.literal("carbon")]),
32
+ vendorPackage: z.union([z.literal("sdk")]).optional(),
33
+ // The SDK family the customer instrumented — the wrapper that captured the call.
34
+ source: z.union([z.literal("openai"), z.literal("anthropic"), z.literal("vercel")]),
33
35
  sourcePackage: z.union([z.literal("openai"), z.literal("@anthropic-ai/sdk"), z.literal("ai")]),
34
36
  sourceFunction: z.string().optional()
35
37
  });
@@ -58,8 +60,13 @@ const Z_LLMEvent = Z_EventBase.extend({
58
60
  type: z.literal("llm"),
59
61
  properties: z.object({
60
62
  llm: z.object({
61
- gateway: z.string().optional(),
63
+ // The serving platform that executed the request (e.g. "openai", "openrouter", "vercel"),
64
+ // resolved at ingest from `apiUrl`. Industry term: "provider".
62
65
  provider: z.string().optional(),
66
+ // The API base URL the SDK captured; ingest resolves `provider` from it.
67
+ apiUrl: z.string().optional(),
68
+ // The model's maker / author (e.g. "anthropic", "alibaba"), resolved at ingest from the model id.
69
+ creator: z.string().optional(),
63
70
  model: z.string(),
64
71
  input: z.object({
65
72
  system: z.string(),
@@ -15,6 +15,9 @@ type T_Constants = {
15
15
  errors: {
16
16
  warnThrottleMs: number;
17
17
  };
18
+ vercel: {
19
+ gatewayBaseUrl: string;
20
+ };
18
21
  };
19
22
  declare const CONSTANTS: T_Constants;
20
23
 
@@ -14,6 +14,11 @@ const CONSTANTS = {
14
14
  },
15
15
  errors: {
16
16
  warnThrottleMs: 6e4
17
+ },
18
+ // The Vercel AI Gateway base URL the wrapper stamps for gateway calls; ingest resolves the serving
19
+ // provider + cost from it (its api_pattern matches in the model catalog).
20
+ vercel: {
21
+ gatewayBaseUrl: "https://ai-gateway.vercel.sh/v1"
17
22
  }
18
23
  };
19
24
  export {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@carbon-js/sdk",
3
- "version": "0.0.7",
3
+ "version": "0.0.8",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "exports": {