@agentfield/sdk 0.1.142-rc.8 → 0.1.142-rc.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/index.d.ts CHANGED
@@ -354,6 +354,7 @@ declare class AIClient {
354
354
  resolveModelChoice(options?: AIRequestOptions): {
355
355
  provider: NonNullable<AIConfig['provider']>;
356
356
  modelName: string;
357
+ routingProvider: string;
357
358
  };
358
359
  private buildModel;
359
360
  private buildPrompt;
@@ -609,6 +610,8 @@ declare const USAGE_ENVELOPE_KEY = "__agentfield_usage__";
609
610
  /** A single LLM (or harness) call usage record. */
610
611
  interface CostEntry {
611
612
  model: string;
613
+ usageStatus?: 'missing';
614
+ routingProvider?: string;
612
615
  inputTokens: number;
613
616
  outputTokens: number;
614
617
  totalTokens: number;
@@ -632,6 +635,8 @@ interface CostEntry {
632
635
  /** Input accepted by {@link CostTracker.record}. */
633
636
  interface CostEntryInit {
634
637
  model: string;
638
+ usageStatus?: 'missing';
639
+ routingProvider?: string;
635
640
  inputTokens?: number;
636
641
  outputTokens?: number;
637
642
  totalTokens?: number;
@@ -646,6 +651,8 @@ interface CostEntryInit {
646
651
  }
647
652
  /** Wire form of a single usage entry (snake_case cross-language contract). */
648
653
  interface UsageEntryWire {
654
+ usage_status?: 'missing';
655
+ routing_provider?: string;
649
656
  source: string;
650
657
  provider: string | null;
651
658
  model: string;
@@ -1211,6 +1218,7 @@ declare function buildToolConfig(toolsParam: ToolsOption, agent: Agent): Promise
1211
1218
  declare function executeToolCallLoop(agent: Agent, prompt: string, toolMap: ToolSet, config: ToolCallConfig, needsLazyHydration: boolean, buildModel: () => any, options?: AIRequestOptions, modelChoice?: {
1212
1219
  provider?: string;
1213
1220
  modelName?: string;
1221
+ routingProvider?: string;
1214
1222
  }): Promise<{
1215
1223
  text: string;
1216
1224
  trace: ToolCallTrace;
@@ -1959,7 +1967,7 @@ declare const PROVIDER_SPECS: {
1959
1967
  };
1960
1968
  };
1961
1969
  type HarnessProviderName = keyof typeof PROVIDER_SPECS;
1962
- declare const SUPPORTED_PROVIDER_NAMES: readonly ("aforge" | "claude-code" | "codex" | "gemini" | "opencode" | "pi" | "omp")[];
1970
+ declare const SUPPORTED_PROVIDER_NAMES: readonly ("gemini" | "aforge" | "claude-code" | "codex" | "opencode" | "pi" | "omp")[];
1963
1971
  interface ProviderHealth {
1964
1972
  provider: HarnessProviderName;
1965
1973
  binary: string | null;
package/dist/index.js CHANGED
@@ -2750,6 +2750,8 @@ var CostTracker = class {
2750
2750
  record(init) {
2751
2751
  this.entries.push({
2752
2752
  model: init.model,
2753
+ usageStatus: init.usageStatus,
2754
+ routingProvider: init.routingProvider,
2753
2755
  inputTokens: toCount(init.inputTokens),
2754
2756
  outputTokens: toCount(init.outputTokens),
2755
2757
  totalTokens: toCount(init.totalTokens),
@@ -2809,6 +2811,8 @@ var CostTracker = class {
2809
2811
  cost_usd: e.costUsd,
2810
2812
  cost_source: e.costSource
2811
2813
  });
2814
+ if (e.routingProvider) entries[entries.length - 1].routing_provider = e.routingProvider;
2815
+ if (e.usageStatus) entries[entries.length - 1].usage_status = e.usageStatus;
2812
2816
  totalInput += e.inputTokens;
2813
2817
  totalOutput += e.outputTokens;
2814
2818
  totalTokens += entryTotal;
@@ -2937,7 +2941,19 @@ function recordAiSdkUsage(params) {
2937
2941
  const tokens = extractTokenUsage(params.source.totalUsage ?? params.source.usage);
2938
2942
  const cost = extractProviderCostUsd(params.source);
2939
2943
  const hasTokens = tokens.inputTokens > 0 || tokens.outputTokens > 0 || tokens.totalTokens > 0 || tokens.cacheReadTokens > 0 || tokens.cacheCreationTokens > 0;
2940
- if (!hasTokens && cost === null) return;
2944
+ const receipt = params.source.totalUsage ?? params.source.usage;
2945
+ const hasTokenReceipt = isRecord(receipt) && ["inputTokens", "outputTokens", "totalTokens"].some(
2946
+ (key) => typeof receipt[key] === "number" && Number.isFinite(receipt[key])
2947
+ );
2948
+ if (!hasTokens && cost === null && !hasTokenReceipt) {
2949
+ tracker.record({
2950
+ model: params.model,
2951
+ provider: params.provider ?? "unknown",
2952
+ routingProvider: params.routingProvider ?? params.provider ?? "unknown",
2953
+ usageStatus: "missing"
2954
+ });
2955
+ return;
2956
+ }
2941
2957
  tracker.record({
2942
2958
  model: params.model,
2943
2959
  ...tokens,
@@ -2945,6 +2961,7 @@ function recordAiSdkUsage(params) {
2945
2961
  costSource: cost !== null ? "provider" : null,
2946
2962
  reasonerName: params.reasonerName !== void 0 ? params.reasonerName : current?.metadata.reasonerId ?? null,
2947
2963
  source: "llm",
2964
+ routingProvider: params.routingProvider ?? params.provider ?? void 0,
2948
2965
  provider: params.provider ?? void 0
2949
2966
  });
2950
2967
  } catch {
@@ -3175,7 +3192,7 @@ async function executeToolCallLoop(agent, prompt, toolMap, config, needsLazyHydr
3175
3192
  const usageModel = modelChoice?.modelName ?? options.model;
3176
3193
  const recordLoopUsage = (result2) => {
3177
3194
  if (usageModel) {
3178
- recordAiSdkUsage({ source: result2, model: usageModel, provider: modelChoice?.provider });
3195
+ recordAiSdkUsage({ source: result2, model: usageModel, provider: modelChoice?.provider, routingProvider: modelChoice?.routingProvider });
3179
3196
  }
3180
3197
  };
3181
3198
  const trace = {
@@ -3700,6 +3717,26 @@ function withOpenRouterUsageInclude(baseFetch) {
3700
3717
  return impl(input, init);
3701
3718
  };
3702
3719
  }
3720
+
3721
+ // src/usage/routingProvider.ts
3722
+ function routingProvider(provider, endpoint) {
3723
+ if (endpoint !== void 0 && endpoint !== null) {
3724
+ try {
3725
+ const url = new URL(endpoint);
3726
+ if (!["https:", "http:"].includes(url.protocol)) return "unknown";
3727
+ const host = url.hostname.toLowerCase();
3728
+ if (host === "openrouter.ai") return "openrouter";
3729
+ if (host === "api.openai.com") return "openai";
3730
+ if (host === "api.anthropic.com") return "anthropic";
3731
+ if (host === "generativelanguage.googleapis.com") return "google";
3732
+ return "other";
3733
+ } catch {
3734
+ return "unknown";
3735
+ }
3736
+ }
3737
+ const normalized = provider === "gemini" ? "google" : provider?.toLowerCase();
3738
+ return ["openrouter", "openai", "anthropic", "google", "bedrock", "azure", "ollama"].includes(normalized ?? "") ? normalized : "unknown";
3739
+ }
3703
3740
  var IMAGE_MIME_TYPES = {
3704
3741
  ".jpg": "image/jpeg",
3705
3742
  ".jpeg": "image/jpeg",
@@ -4024,7 +4061,7 @@ var AIClient = class {
4024
4061
  };
4025
4062
  }
4026
4063
  async generate(prompt, options = {}) {
4027
- const { provider, modelName } = this.resolveModelChoice(options);
4064
+ const { provider, modelName, routingProvider: routingProvider2 } = this.resolveModelChoice(options);
4028
4065
  const model = this.buildModel(options);
4029
4066
  const requestPrompt = this.buildPrompt(prompt, options.content);
4030
4067
  if (options.schema) {
@@ -4040,7 +4077,7 @@ var AIClient = class {
4040
4077
  experimental_repairText: async ({ text: text2 }) => repairJsonText(text2)
4041
4078
  });
4042
4079
  const response2 = await this.withRateLimitRetry(call2);
4043
- recordAiSdkUsage({ source: response2, model: modelName, provider });
4080
+ recordAiSdkUsage({ source: response2, model: modelName, provider, routingProvider: routingProvider2 });
4044
4081
  return response2.object;
4045
4082
  }
4046
4083
  const call = async () => generateText({
@@ -4051,23 +4088,38 @@ var AIClient = class {
4051
4088
  maxOutputTokens: options.maxTokens ?? this.config.maxTokens
4052
4089
  });
4053
4090
  const response = await this.withRateLimitRetry(call);
4054
- recordAiSdkUsage({ source: response, model: modelName, provider });
4091
+ recordAiSdkUsage({ source: response, model: modelName, provider, routingProvider: routingProvider2 });
4055
4092
  return response.text;
4056
4093
  }
4057
- // NOTE: stream() usage is deliberately NOT captured. The AI SDK's
4058
- // streamResult.usage/.totalUsage promises "automatically consume the
4059
- // stream": attaching to them would force full background consumption of a
4060
- // stream the caller may abandon early, changing stream semantics.
4094
+ // Observe usage only after the caller consumes the stream. Merely creating
4095
+ // the stream must not start background consumption of an abandoned response.
4061
4096
  async stream(prompt, options = {}) {
4062
- const model = this.buildModel(options);
4097
+ const { provider, modelName, routingProvider: routingProvider2 } = this.resolveModelChoice(options);
4098
+ const tracker = ExecutionContext.getCurrent()?.costTracker;
4063
4099
  const streamResult = streamText({
4064
- model,
4100
+ model: this.buildModel(options),
4065
4101
  prompt: this.buildPrompt(prompt, options.content),
4066
4102
  system: options.system,
4067
4103
  temperature: options.temperature ?? this.config.temperature,
4068
4104
  maxOutputTokens: options.maxTokens ?? this.config.maxTokens
4069
4105
  });
4070
- return streamResult.textStream;
4106
+ if (!tracker) return streamResult.textStream;
4107
+ return (async function* () {
4108
+ let completed = false;
4109
+ try {
4110
+ for await (const text2 of streamResult.textStream) yield text2;
4111
+ try {
4112
+ const usage = await streamResult.totalUsage;
4113
+ if (usage) recordAiSdkUsage({ source: { totalUsage: usage }, model: modelName, provider, routingProvider: routingProvider2, tracker });
4114
+ else tracker.record({ model: modelName, provider, routingProvider: routingProvider2, usageStatus: "missing" });
4115
+ } catch {
4116
+ tracker.record({ model: modelName, provider, routingProvider: routingProvider2, usageStatus: "missing" });
4117
+ }
4118
+ completed = true;
4119
+ } finally {
4120
+ if (!completed) tracker.record({ model: modelName, provider, routingProvider: routingProvider2, usageStatus: "missing" });
4121
+ }
4122
+ })();
4071
4123
  }
4072
4124
  async embed(value, options = {}) {
4073
4125
  const model = this.buildEmbeddingModel(options);
@@ -4104,7 +4156,8 @@ var AIClient = class {
4104
4156
  resolveModelChoice(options = {}) {
4105
4157
  return {
4106
4158
  provider: options.provider ?? this.config.provider ?? "openai",
4107
- modelName: options.model ?? this.config.model ?? "gpt-4o"
4159
+ modelName: options.model ?? this.config.model ?? "gpt-4o",
4160
+ routingProvider: routingProvider(options.provider ?? this.config.provider ?? "openai", this.config.baseUrl)
4108
4161
  };
4109
4162
  }
4110
4163
  buildModel(options) {