@hiper2d/ai-agents 0.6.0 → 0.7.0

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.mjs CHANGED
@@ -805,6 +805,7 @@ var LLM_CONSTANTS = {
805
805
  GLM: "glm",
806
806
  GLM_FLASH: "glm-flash",
807
807
  FUGU_ULTRA: "fugu-ultra",
808
+ FUGU_MAX: "fugu-max",
808
809
  // Qwen (QwenCloud/DashScope). Stable picker ids without the version, matching the gpt/gemini
809
810
  // pattern, so future repoints don't orphan persisted ids.
810
811
  QWEN_MAX: "qwen-max",
@@ -1050,13 +1051,37 @@ var SupportedAiModels = {
1050
1051
  // so the rate is not even guaranteed stable, and its cache hit rate was 9.3% — effectively
1051
1052
  // zero, since every hit came from a duplicate call seconds apart rather than turn-to-turn
1052
1053
  // prefix reuse. Ultra costs the same and is predictable.
1054
+ //
1055
+ // reasoningEffort is PINNED on both entries because the server default differs per model:
1056
+ // fugu-ultra defaults to `xhigh` (console.sakana.ai/models), fugu-max to `high`. Until
1057
+ // 2026-09-20 the agent sent no effort at all, so every Ultra turn ran at xhigh. Measured that
1058
+ // day on a 2k-token three-day game prompt (two samples each, chat completions, json mode):
1059
+ // ultra xhigh 148s / 265s $0.34 / $0.50 a turn (orchestration tokens included)
1060
+ // ultra high 65s / 78s $0.16 / $0.17
1061
+ // max high 6s / 10s $0.006 / $0.008
1062
+ // max xhigh 41s / 47s $0.02
1063
+ // `max_tokens` is not a latency lever for Ultra: Sakana applies it to the final response only,
1064
+ // the orchestrator "still uses maximum token limit". Effort is the only knob.
1053
1065
  [LLM_CONSTANTS.FUGU_ULTRA]: {
1054
1066
  displayName: "Sakana Fugu Ultra",
1055
1067
  modelApiName: "fugu-ultra",
1056
1068
  apiKeyName: API_KEY_CONSTANTS.FUGU,
1057
1069
  hasThinking: false,
1070
+ reasoningEffort: "high",
1058
1071
  tags: ["extremely-slow", "expensive"]
1059
1072
  },
1073
+ // Fugu Max (2026-09-20, fugu-max → fugu-max-v1.0): Sakana's "largest pool of models on the
1074
+ // cost–performance Pareto frontier" — the everyday Fugu at 5x less than Ultra's output rate
1075
+ // and a fraction of its latency. It reasons (reasoning_tokens 80-500 a turn) but, like
1076
+ // Ultra, never returns reasoning_content, hence hasThinking: false. No orchestration
1077
+ // tokens observed on any call.
1078
+ [LLM_CONSTANTS.FUGU_MAX]: {
1079
+ displayName: "Sakana Fugu Max",
1080
+ modelApiName: "fugu-max",
1081
+ apiKeyName: API_KEY_CONSTANTS.FUGU,
1082
+ hasThinking: false,
1083
+ reasoningEffort: "high"
1084
+ },
1060
1085
  // Qwen models (QwenCloud, OpenAI-compatible endpoint). Added 2026-08-05 straight into the
1061
1086
  // thinking-only catalog: their API has an `enable_thinking` toggle, we always send true, and
1062
1087
  // thinking arrives in `reasoning_content` (verified live against all three, non-streaming).
@@ -1344,9 +1369,12 @@ var MODEL_PRICING = {
1344
1369
  extendedContextCacheHitPrice: 1,
1345
1370
  extendedContextThresholdTokens: 2e5
1346
1371
  },
1347
- // Sakana Fugu models. Base `fugu` was retired 2026-08-04 — it had no published price and
1348
- // measured out at these same ultra rates, so it has no pricing entry.
1349
- // fugu-ultra has published pricing. Above 272K context the rates roughly double.
1372
+ // Sakana Fugu models (console.sakana.ai/pricing, read 2026-09-20). Base `fugu` was retired
1373
+ // 2026-08-04 — it had no published price and measured out at these same ultra rates, so it
1374
+ // has no pricing entry. fugu-ultra: above 272K context the rates roughly double. Ultra also
1375
+ // reports "orchestration" tokens (its internal expert calls) in prompt_tokens_details /
1376
+ // completion_tokens_details, billed at these same input/output rates — FuguAgent folds them
1377
+ // into the token counts before pricing.
1350
1378
  [SupportedAiModels[LLM_CONSTANTS.FUGU_ULTRA].modelApiName]: {
1351
1379
  inputPrice: 5,
1352
1380
  outputPrice: 30,
@@ -1356,6 +1384,12 @@ var MODEL_PRICING = {
1356
1384
  extendedContextCacheHitPrice: 1,
1357
1385
  extendedContextThresholdTokens: 272e3
1358
1386
  },
1387
+ // fugu-max: flat rates, no context tiers.
1388
+ [SupportedAiModels[LLM_CONSTANTS.FUGU_MAX].modelApiName]: {
1389
+ inputPrice: 2,
1390
+ outputPrice: 6,
1391
+ cacheHitPrice: 0.25
1392
+ },
1359
1393
  // Qwen models. Rates from the official pricing page (qwencloud.com/pricing/api, read
1360
1394
  // 2026-08-30 — the page is client-rendered, so it was read by eye, not WebFetch):
1361
1395
  // qwen3.8-max $2/$6 with implicit-cache hits at $0.25; qwen3.8-flash $0.15/$0.47, hits
@@ -1472,7 +1506,7 @@ var ANTHROPIC_REASONING_EFFORTS = ["low", "medium", "high", "xhigh", "max"];
1472
1506
  var GEMINI_REASONING_EFFORTS = ["minimal", "low", "medium", "high"];
1473
1507
  var GLM_REASONING_EFFORTS = ["low", "high", "max"];
1474
1508
  var DEEPSEEK_REASONING_EFFORTS = ["low", "high", "max"];
1475
- var FUGU_REASONING_EFFORTS = ["high", "xhigh"];
1509
+ var FUGU_REASONING_EFFORTS = ["high", "xhigh", "max"];
1476
1510
  var META_REASONING_EFFORTS = ["minimal", "low", "medium", "high", "xhigh", "max"];
1477
1511
  var MISTRAL_REASONING_EFFORTS = ["minimal", "low", "medium", "high", "xhigh"];
1478
1512
  function clampReasoningEffort(effort, allowed) {
@@ -4240,13 +4274,15 @@ var FuguAgent = class extends AbstractAgent {
4240
4274
  client;
4241
4275
  // A getter, not a field: `maxOutputTokens` can be raised after construction, and a field
4242
4276
  // initializer would snapshot the default and silently ignore the override.
4277
+ // `reasoning_effort` is re-declared as string: the OpenAI SDK's union lacks Sakana's 'max'.
4243
4278
  get defaultParams() {
4244
4279
  return {
4245
4280
  model: this.model,
4246
4281
  stream: false,
4247
- // Caps visible output only. Server-side orchestration/reasoning tokens are
4248
- // separate and unaffected by this.
4249
- max_tokens: this.maxOutputTokens
4282
+ // Caps the final reply only. Per Sakana's docs the orchestrator "still uses maximum
4283
+ // token limit", so this bounds neither Ultra's latency nor its orchestration spend.
4284
+ max_tokens: this.maxOutputTokens,
4285
+ reasoning_effort: toFuguEffort(this.reasoningEffort ?? "high")
4250
4286
  };
4251
4287
  }
4252
4288
  logTemplates = {
@@ -4271,33 +4307,6 @@ var FuguAgent = class extends AbstractAgent {
4271
4307
  content: msg.content
4272
4308
  }));
4273
4309
  }
4274
- // ───────────────────────────────────────────────────────────────────────────────────────
4275
- // TEMPORARY (cost calibration). Base `fugu` is a dynamic router with no published per-token
4276
- // price and Sakana returns NO cost field in the response — only token counts. Crucially the
4277
- // response also reports "orchestration tokens" (billed at input/output rates per Sakana's
4278
- // pricing page) that our standard TokenUsage drops. This logs the full raw breakdown to
4279
- // BetterStack under a distinctive tag so we can sum real tokens per game and, combined with
4280
- // the Sakana billing dashboard total, derive the true per-token rate. REMOVE AFTER CALIBRATION.
4281
- logRawUsageForCalibration(completion) {
4282
- const usage = completion?.usage;
4283
- if (!usage) return;
4284
- logger.info("FUGU_COST_CALIBRATION", {
4285
- tag: "FUGU_COST_CALIBRATION",
4286
- model: this.model,
4287
- agentName: this.name,
4288
- gameId: this.gameId,
4289
- userId: this.userId,
4290
- promptTokens: usage.prompt_tokens ?? 0,
4291
- completionTokens: usage.completion_tokens ?? 0,
4292
- totalTokens: usage.total_tokens ?? 0,
4293
- cachedTokens: usage.prompt_tokens_details?.cached_tokens ?? 0,
4294
- orchestrationInputTokens: usage.prompt_tokens_details?.orchestration_input_tokens ?? 0,
4295
- orchestrationInputCachedTokens: usage.prompt_tokens_details?.orchestration_input_cached_tokens ?? 0,
4296
- reasoningTokens: usage.completion_tokens_details?.reasoning_tokens ?? 0,
4297
- orchestrationOutputTokens: usage.completion_tokens_details?.orchestration_output_tokens ?? 0,
4298
- rawUsage: usage
4299
- });
4300
- }
4301
4310
  extractThinkingAndUsage(completion) {
4302
4311
  let thinkingContent = "";
4303
4312
  const message = completion.choices[0]?.message;
@@ -4305,21 +4314,14 @@ var FuguAgent = class extends AbstractAgent {
4305
4314
  thinkingContent = message.reasoning_content;
4306
4315
  this.logger(`Captured reasoning_content (${thinkingContent.length} characters)`);
4307
4316
  }
4308
- let tokenUsage;
4309
- const usageResult = extractUsageAndCalculateCost(this.model, completion);
4310
- if (usageResult) {
4311
- tokenUsage = {
4312
- inputTokens: usageResult.usage.promptTokens,
4313
- outputTokens: usageResult.usage.completionTokens,
4314
- totalTokens: usageResult.usage.totalTokens,
4315
- costUSD: usageResult.cost,
4316
- ...usageResult.usage.cacheHitTokens !== void 0 ? { cachedInputTokens: usageResult.usage.cacheHitTokens } : {}
4317
- };
4318
- if (usageResult.usage.reasoningTokens) {
4319
- const reasoningTokens = usageResult.usage.reasoningTokens;
4320
- const finalAnswerTokens = Math.max(0, tokenUsage.outputTokens - reasoningTokens);
4317
+ const tokenUsage = extractFuguTokenUsage(this.model, completion);
4318
+ if (tokenUsage) {
4319
+ const details = completion.usage ?? {};
4320
+ const orchestrationIn = details.prompt_tokens_details?.orchestration_input_tokens ?? 0;
4321
+ const orchestrationOut = details.completion_tokens_details?.orchestration_output_tokens ?? 0;
4322
+ if (tokenUsage.reasoningTokens || orchestrationIn || orchestrationOut) {
4321
4323
  this.logger(
4322
- `Output breakdown: ${reasoningTokens} reasoning tokens, ${finalAnswerTokens} final answer tokens`
4324
+ `Usage breakdown: ${tokenUsage.reasoningTokens ?? 0} reasoning tokens; orchestration ${orchestrationIn} in / ${orchestrationOut} out (billed, folded into the totals)`
4323
4325
  );
4324
4326
  }
4325
4327
  }
@@ -4361,7 +4363,6 @@ ${schemaDescription}`;
4361
4363
  this.logger(this.logTemplates.error(this.name, apiError));
4362
4364
  throw new Error(this.errorMessages.apiError(apiError));
4363
4365
  }
4364
- this.logRawUsageForCalibration(completion);
4365
4366
  const rawReply = completion.choices[0]?.message?.content;
4366
4367
  if (!rawReply) {
4367
4368
  throw new Error(this.errorMessages.emptyResponse);
@@ -4405,7 +4406,6 @@ ${schemaDescription}`;
4405
4406
  this.logger(this.logTemplates.error(this.name, apiError));
4406
4407
  throw new Error(this.errorMessages.apiError(apiError));
4407
4408
  }
4408
- this.logRawUsageForCalibration(completion);
4409
4409
  const rawReply = completion.choices[0]?.message?.content;
4410
4410
  if (!rawReply) {
4411
4411
  throw new Error(this.errorMessages.emptyResponse);
@@ -4424,6 +4424,31 @@ ${schemaDescription}`;
4424
4424
  }
4425
4425
  }
4426
4426
  };
4427
+ function extractFuguTokenUsage(modelApiName, completion) {
4428
+ const usage = completion?.usage;
4429
+ if (!usage) {
4430
+ return void 0;
4431
+ }
4432
+ const promptDetails = usage.prompt_tokens_details ?? {};
4433
+ const completionDetails = usage.completion_tokens_details ?? {};
4434
+ const inputTokens = (usage.prompt_tokens ?? 0) + (promptDetails.orchestration_input_tokens ?? 0);
4435
+ const outputTokens = (usage.completion_tokens ?? 0) + (completionDetails.orchestration_output_tokens ?? 0);
4436
+ const cachedInputTokens = (promptDetails.cached_tokens ?? 0) + (promptDetails.orchestration_input_cached_tokens ?? 0);
4437
+ const reasoningTokens = completionDetails.reasoning_tokens ?? 0;
4438
+ return {
4439
+ inputTokens,
4440
+ outputTokens,
4441
+ totalTokens: inputTokens + outputTokens,
4442
+ cachedInputTokens,
4443
+ // Omitted rather than 0: consumers persist the object as-is (see TokenUsage).
4444
+ ...reasoningTokens > 0 ? { reasoningTokens } : {},
4445
+ costUSD: calculateModelCost(modelApiName, inputTokens, outputTokens, {
4446
+ cacheHitTokens: cachedInputTokens,
4447
+ // Context-tier selection: the request's own size, not the orchestrator's traffic.
4448
+ contextTokens: usage.total_tokens ?? inputTokens
4449
+ })
4450
+ };
4451
+ }
4427
4452
 
4428
4453
  // src/agents/qwen-agent.ts
4429
4454
  import { OpenAI as OpenAI9 } from "openai";
@@ -5084,6 +5109,7 @@ var AgentFactory = class {
5084
5109
  return new GlmAgent(name, instruction, model.modelApiName, key, model.temperature, shouldEnableThinking);
5085
5110
  // Sakana Fugu models — always-on reasoning, no temperature (ignored by the model)
5086
5111
  case LLM_CONSTANTS.FUGU_ULTRA:
5112
+ case LLM_CONSTANTS.FUGU_MAX:
5087
5113
  return new FuguAgent(name, instruction, model.modelApiName, key, shouldEnableThinking);
5088
5114
  // Qwen models — thinking-only (enable_thinking always sent)
5089
5115
  case LLM_CONSTANTS.QWEN_MAX: