@agentfield/sdk 0.1.142-rc.7 → 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
@@ -218,8 +218,9 @@ function batchProbeInvocation(command) {
218
218
  });
219
219
  return {
220
220
  file: process.env.ComSpec ?? "cmd.exe",
221
- // /s strips only the outermost quote pair, leaving each "%VAR%" quoted.
222
- // /v:off keeps ! in a path literal even if delayed expansion is enabled.
221
+ // /s removes the outer quote pair of the fixed template, so each "%VAR%"
222
+ // expansion stays quoted.
223
+ // /v:off disables delayed ! expansion; /d disables AutoRun.
223
224
  args: ["/d", "/v:off", "/s", "/c", `"${tokens.join(" ")}"`],
224
225
  env
225
226
  };
@@ -227,20 +228,40 @@ function batchProbeInvocation(command) {
227
228
  async function defaultVersionProbe(command) {
228
229
  const { execFile } = await import('child_process');
229
230
  return new Promise((resolve3, reject) => {
230
- const batch = isWindowsBatchFile(command[0]);
231
- const { file, args, env } = batch ? batchProbeInvocation(command) : { file: command[0], args: command.slice(1), env: process.env };
232
- execFile(
233
- file,
234
- args,
235
- { timeout: 2e3, windowsHide: true, windowsVerbatimArguments: batch, env },
236
- (error, stdout, stderr) => {
237
- if (error) {
238
- reject(error);
239
- return;
231
+ const onProbeOutput = (error, stdout, stderr) => {
232
+ if (error) {
233
+ reject(error);
234
+ return;
235
+ }
236
+ const output = String(stdout || stderr).trim();
237
+ resolve3(output ? output.split(/\r?\n/, 1)[0] : "unknown");
238
+ };
239
+ if (isWindowsBatchFile(command[0])) {
240
+ for (const value of command) {
241
+ for (let index = 0; index < value.length; index += 1) {
242
+ const codePoint = value.charCodeAt(index);
243
+ if (codePoint === 34 || codePoint < 32) {
244
+ reject(new TypeError(
245
+ `Windows batch probe values must not contain double quotes or control characters; offending code point ${codePoint} in ${JSON.stringify(value)}`
246
+ ));
247
+ return;
248
+ }
240
249
  }
241
- const output = String(stdout || stderr).trim();
242
- resolve3(output ? output.split(/\r?\n/, 1)[0] : "unknown");
243
250
  }
251
+ const { file, args, env } = batchProbeInvocation(command);
252
+ execFile(
253
+ file,
254
+ args,
255
+ { timeout: 2e3, windowsHide: true, windowsVerbatimArguments: true, env },
256
+ onProbeOutput
257
+ );
258
+ return;
259
+ }
260
+ execFile(
261
+ command[0],
262
+ command.slice(1),
263
+ { timeout: 2e3, windowsHide: true, windowsVerbatimArguments: false },
264
+ onProbeOutput
244
265
  );
245
266
  });
246
267
  }
@@ -2729,6 +2750,8 @@ var CostTracker = class {
2729
2750
  record(init) {
2730
2751
  this.entries.push({
2731
2752
  model: init.model,
2753
+ usageStatus: init.usageStatus,
2754
+ routingProvider: init.routingProvider,
2732
2755
  inputTokens: toCount(init.inputTokens),
2733
2756
  outputTokens: toCount(init.outputTokens),
2734
2757
  totalTokens: toCount(init.totalTokens),
@@ -2788,6 +2811,8 @@ var CostTracker = class {
2788
2811
  cost_usd: e.costUsd,
2789
2812
  cost_source: e.costSource
2790
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;
2791
2816
  totalInput += e.inputTokens;
2792
2817
  totalOutput += e.outputTokens;
2793
2818
  totalTokens += entryTotal;
@@ -2916,7 +2941,19 @@ function recordAiSdkUsage(params) {
2916
2941
  const tokens = extractTokenUsage(params.source.totalUsage ?? params.source.usage);
2917
2942
  const cost = extractProviderCostUsd(params.source);
2918
2943
  const hasTokens = tokens.inputTokens > 0 || tokens.outputTokens > 0 || tokens.totalTokens > 0 || tokens.cacheReadTokens > 0 || tokens.cacheCreationTokens > 0;
2919
- 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
+ }
2920
2957
  tracker.record({
2921
2958
  model: params.model,
2922
2959
  ...tokens,
@@ -2924,6 +2961,7 @@ function recordAiSdkUsage(params) {
2924
2961
  costSource: cost !== null ? "provider" : null,
2925
2962
  reasonerName: params.reasonerName !== void 0 ? params.reasonerName : current?.metadata.reasonerId ?? null,
2926
2963
  source: "llm",
2964
+ routingProvider: params.routingProvider ?? params.provider ?? void 0,
2927
2965
  provider: params.provider ?? void 0
2928
2966
  });
2929
2967
  } catch {
@@ -3154,7 +3192,7 @@ async function executeToolCallLoop(agent, prompt, toolMap, config, needsLazyHydr
3154
3192
  const usageModel = modelChoice?.modelName ?? options.model;
3155
3193
  const recordLoopUsage = (result2) => {
3156
3194
  if (usageModel) {
3157
- recordAiSdkUsage({ source: result2, model: usageModel, provider: modelChoice?.provider });
3195
+ recordAiSdkUsage({ source: result2, model: usageModel, provider: modelChoice?.provider, routingProvider: modelChoice?.routingProvider });
3158
3196
  }
3159
3197
  };
3160
3198
  const trace = {
@@ -3679,6 +3717,26 @@ function withOpenRouterUsageInclude(baseFetch) {
3679
3717
  return impl(input, init);
3680
3718
  };
3681
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
+ }
3682
3740
  var IMAGE_MIME_TYPES = {
3683
3741
  ".jpg": "image/jpeg",
3684
3742
  ".jpeg": "image/jpeg",
@@ -4003,7 +4061,7 @@ var AIClient = class {
4003
4061
  };
4004
4062
  }
4005
4063
  async generate(prompt, options = {}) {
4006
- const { provider, modelName } = this.resolveModelChoice(options);
4064
+ const { provider, modelName, routingProvider: routingProvider2 } = this.resolveModelChoice(options);
4007
4065
  const model = this.buildModel(options);
4008
4066
  const requestPrompt = this.buildPrompt(prompt, options.content);
4009
4067
  if (options.schema) {
@@ -4019,7 +4077,7 @@ var AIClient = class {
4019
4077
  experimental_repairText: async ({ text: text2 }) => repairJsonText(text2)
4020
4078
  });
4021
4079
  const response2 = await this.withRateLimitRetry(call2);
4022
- recordAiSdkUsage({ source: response2, model: modelName, provider });
4080
+ recordAiSdkUsage({ source: response2, model: modelName, provider, routingProvider: routingProvider2 });
4023
4081
  return response2.object;
4024
4082
  }
4025
4083
  const call = async () => generateText({
@@ -4030,23 +4088,38 @@ var AIClient = class {
4030
4088
  maxOutputTokens: options.maxTokens ?? this.config.maxTokens
4031
4089
  });
4032
4090
  const response = await this.withRateLimitRetry(call);
4033
- recordAiSdkUsage({ source: response, model: modelName, provider });
4091
+ recordAiSdkUsage({ source: response, model: modelName, provider, routingProvider: routingProvider2 });
4034
4092
  return response.text;
4035
4093
  }
4036
- // NOTE: stream() usage is deliberately NOT captured. The AI SDK's
4037
- // streamResult.usage/.totalUsage promises "automatically consume the
4038
- // stream": attaching to them would force full background consumption of a
4039
- // 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.
4040
4096
  async stream(prompt, options = {}) {
4041
- const model = this.buildModel(options);
4097
+ const { provider, modelName, routingProvider: routingProvider2 } = this.resolveModelChoice(options);
4098
+ const tracker = ExecutionContext.getCurrent()?.costTracker;
4042
4099
  const streamResult = streamText({
4043
- model,
4100
+ model: this.buildModel(options),
4044
4101
  prompt: this.buildPrompt(prompt, options.content),
4045
4102
  system: options.system,
4046
4103
  temperature: options.temperature ?? this.config.temperature,
4047
4104
  maxOutputTokens: options.maxTokens ?? this.config.maxTokens
4048
4105
  });
4049
- 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
+ })();
4050
4123
  }
4051
4124
  async embed(value, options = {}) {
4052
4125
  const model = this.buildEmbeddingModel(options);
@@ -4083,7 +4156,8 @@ var AIClient = class {
4083
4156
  resolveModelChoice(options = {}) {
4084
4157
  return {
4085
4158
  provider: options.provider ?? this.config.provider ?? "openai",
4086
- 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)
4087
4161
  };
4088
4162
  }
4089
4163
  buildModel(options) {