@hiper2d/ai-agents 0.6.0 → 0.8.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",
@@ -826,9 +827,13 @@ var SupportedAiModels = {
826
827
  tags: ["expensive"]
827
828
  },
828
829
  // Claude models — thinking-only entries (non-thinking variants retired 2026-08-05)
830
+ // Opus moved 5 → 5.5 on 2026-09-22 (`claude-opus-5-5`, verified against GET /v1/models).
831
+ // Cheaper than Opus 5 ($4/$20 vs $5/$25) and the same 1M context / 128K output. Thinking is
832
+ // always on, as on Opus 5. Its API default effort is `medium`, but we keep the catalog-wide
833
+ // `high` pin so bot answers don't get shallower than every other model in the lobby.
829
834
  [LLM_CONSTANTS.CLAUDE_OPUS]: {
830
- displayName: "Claude 5 Opus",
831
- modelApiName: "claude-opus-5",
835
+ displayName: "Claude 5.5 Opus",
836
+ modelApiName: "claude-opus-5-5",
832
837
  apiKeyName: API_KEY_CONSTANTS.ANTHROPIC,
833
838
  hasThinking: true,
834
839
  reasoningEffort: "high",
@@ -1050,13 +1055,37 @@ var SupportedAiModels = {
1050
1055
  // so the rate is not even guaranteed stable, and its cache hit rate was 9.3% — effectively
1051
1056
  // zero, since every hit came from a duplicate call seconds apart rather than turn-to-turn
1052
1057
  // prefix reuse. Ultra costs the same and is predictable.
1058
+ //
1059
+ // reasoningEffort is PINNED on both entries because the server default differs per model:
1060
+ // fugu-ultra defaults to `xhigh` (console.sakana.ai/models), fugu-max to `high`. Until
1061
+ // 2026-09-20 the agent sent no effort at all, so every Ultra turn ran at xhigh. Measured that
1062
+ // day on a 2k-token three-day game prompt (two samples each, chat completions, json mode):
1063
+ // ultra xhigh 148s / 265s $0.34 / $0.50 a turn (orchestration tokens included)
1064
+ // ultra high 65s / 78s $0.16 / $0.17
1065
+ // max high 6s / 10s $0.006 / $0.008
1066
+ // max xhigh 41s / 47s $0.02
1067
+ // `max_tokens` is not a latency lever for Ultra: Sakana applies it to the final response only,
1068
+ // the orchestrator "still uses maximum token limit". Effort is the only knob.
1053
1069
  [LLM_CONSTANTS.FUGU_ULTRA]: {
1054
1070
  displayName: "Sakana Fugu Ultra",
1055
1071
  modelApiName: "fugu-ultra",
1056
1072
  apiKeyName: API_KEY_CONSTANTS.FUGU,
1057
1073
  hasThinking: false,
1074
+ reasoningEffort: "high",
1058
1075
  tags: ["extremely-slow", "expensive"]
1059
1076
  },
1077
+ // Fugu Max (2026-09-20, fugu-max → fugu-max-v1.0): Sakana's "largest pool of models on the
1078
+ // cost–performance Pareto frontier" — the everyday Fugu at 5x less than Ultra's output rate
1079
+ // and a fraction of its latency. It reasons (reasoning_tokens 80-500 a turn) but, like
1080
+ // Ultra, never returns reasoning_content, hence hasThinking: false. No orchestration
1081
+ // tokens observed on any call.
1082
+ [LLM_CONSTANTS.FUGU_MAX]: {
1083
+ displayName: "Sakana Fugu Max",
1084
+ modelApiName: "fugu-max",
1085
+ apiKeyName: API_KEY_CONSTANTS.FUGU,
1086
+ hasThinking: false,
1087
+ reasoningEffort: "high"
1088
+ },
1060
1089
  // Qwen models (QwenCloud, OpenAI-compatible endpoint). Added 2026-08-05 straight into the
1061
1090
  // thinking-only catalog: their API has an `enable_thinking` toggle, we always send true, and
1062
1091
  // thinking arrives in `reasoning_content` (verified live against all three, non-streaming).
@@ -1277,9 +1306,13 @@ var MODEL_PRICING = {
1277
1306
  cacheHitPrice: 0.25
1278
1307
  },
1279
1308
  [SupportedAiModels[LLM_CONSTANTS.CLAUDE_OPUS].modelApiName]: {
1280
- inputPrice: 5,
1281
- outputPrice: 25,
1282
- cacheHitPrice: 0.5
1309
+ // Opus 5.5 (2026-09-22): $4/$20, down from Opus 5's $5/$25. Cache reads are 5% of input
1310
+ // here, not the usual 10% — $0.20, not $0.40. Cache WRITES ($5 at 5m, $8 at 1h) are not
1311
+ // modelled: calculateCost bills every uncached prompt token at inputPrice, so a write is
1312
+ // under-billed by the 1.25x/2x premium. Pre-existing for every Anthropic model.
1313
+ inputPrice: 4,
1314
+ outputPrice: 20,
1315
+ cacheHitPrice: 0.2
1283
1316
  },
1284
1317
  [SupportedAiModels[LLM_CONSTANTS.CLAUDE_SONNET].modelApiName]: {
1285
1318
  inputPrice: 2,
@@ -1344,9 +1377,12 @@ var MODEL_PRICING = {
1344
1377
  extendedContextCacheHitPrice: 1,
1345
1378
  extendedContextThresholdTokens: 2e5
1346
1379
  },
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.
1380
+ // Sakana Fugu models (console.sakana.ai/pricing, read 2026-09-20). Base `fugu` was retired
1381
+ // 2026-08-04 — it had no published price and measured out at these same ultra rates, so it
1382
+ // has no pricing entry. fugu-ultra: above 272K context the rates roughly double. Ultra also
1383
+ // reports "orchestration" tokens (its internal expert calls) in prompt_tokens_details /
1384
+ // completion_tokens_details, billed at these same input/output rates — FuguAgent folds them
1385
+ // into the token counts before pricing.
1350
1386
  [SupportedAiModels[LLM_CONSTANTS.FUGU_ULTRA].modelApiName]: {
1351
1387
  inputPrice: 5,
1352
1388
  outputPrice: 30,
@@ -1356,6 +1392,12 @@ var MODEL_PRICING = {
1356
1392
  extendedContextCacheHitPrice: 1,
1357
1393
  extendedContextThresholdTokens: 272e3
1358
1394
  },
1395
+ // fugu-max: flat rates, no context tiers.
1396
+ [SupportedAiModels[LLM_CONSTANTS.FUGU_MAX].modelApiName]: {
1397
+ inputPrice: 2,
1398
+ outputPrice: 6,
1399
+ cacheHitPrice: 0.25
1400
+ },
1359
1401
  // Qwen models. Rates from the official pricing page (qwencloud.com/pricing/api, read
1360
1402
  // 2026-08-30 — the page is client-rendered, so it was read by eye, not WebFetch):
1361
1403
  // qwen3.8-max $2/$6 with implicit-cache hits at $0.25; qwen3.8-flash $0.15/$0.47, hits
@@ -1472,7 +1514,7 @@ var ANTHROPIC_REASONING_EFFORTS = ["low", "medium", "high", "xhigh", "max"];
1472
1514
  var GEMINI_REASONING_EFFORTS = ["minimal", "low", "medium", "high"];
1473
1515
  var GLM_REASONING_EFFORTS = ["low", "high", "max"];
1474
1516
  var DEEPSEEK_REASONING_EFFORTS = ["low", "high", "max"];
1475
- var FUGU_REASONING_EFFORTS = ["high", "xhigh"];
1517
+ var FUGU_REASONING_EFFORTS = ["high", "xhigh", "max"];
1476
1518
  var META_REASONING_EFFORTS = ["minimal", "low", "medium", "high", "xhigh", "max"];
1477
1519
  var MISTRAL_REASONING_EFFORTS = ["minimal", "low", "medium", "high", "xhigh"];
1478
1520
  function clampReasoningEffort(effort, allowed) {
@@ -2428,6 +2470,7 @@ ${instruction}`);
2428
2470
 
2429
2471
  // src/agents/anthropic-agent.ts
2430
2472
  import { Anthropic } from "@anthropic-ai/sdk";
2473
+ var rejectsDisabledThinking = (model) => model.includes("fable") || model.includes("opus-5-5");
2431
2474
  var ClaudeAgent = class extends AbstractAgent {
2432
2475
  client;
2433
2476
  /**
@@ -2644,7 +2687,9 @@ ${schemaDescription}`;
2644
2687
  params.temperature = 1;
2645
2688
  }
2646
2689
  } else if (usesAdaptiveThinking) {
2647
- params.thinking = { type: "disabled" };
2690
+ if (!rejectsDisabledThinking(this.model)) {
2691
+ params.thinking = { type: "disabled" };
2692
+ }
2648
2693
  } else {
2649
2694
  params.temperature = this.temperature;
2650
2695
  }
@@ -2738,7 +2783,9 @@ ${schemaDescription}`;
2738
2783
  params.temperature = 1;
2739
2784
  }
2740
2785
  } else if (usesAdaptiveThinking) {
2741
- params.thinking = { type: "disabled" };
2786
+ if (!rejectsDisabledThinking(this.model)) {
2787
+ params.thinking = { type: "disabled" };
2788
+ }
2742
2789
  } else {
2743
2790
  params.temperature = this.temperature;
2744
2791
  }
@@ -4240,13 +4287,15 @@ var FuguAgent = class extends AbstractAgent {
4240
4287
  client;
4241
4288
  // A getter, not a field: `maxOutputTokens` can be raised after construction, and a field
4242
4289
  // initializer would snapshot the default and silently ignore the override.
4290
+ // `reasoning_effort` is re-declared as string: the OpenAI SDK's union lacks Sakana's 'max'.
4243
4291
  get defaultParams() {
4244
4292
  return {
4245
4293
  model: this.model,
4246
4294
  stream: false,
4247
- // Caps visible output only. Server-side orchestration/reasoning tokens are
4248
- // separate and unaffected by this.
4249
- max_tokens: this.maxOutputTokens
4295
+ // Caps the final reply only. Per Sakana's docs the orchestrator "still uses maximum
4296
+ // token limit", so this bounds neither Ultra's latency nor its orchestration spend.
4297
+ max_tokens: this.maxOutputTokens,
4298
+ reasoning_effort: toFuguEffort(this.reasoningEffort ?? "high")
4250
4299
  };
4251
4300
  }
4252
4301
  logTemplates = {
@@ -4271,33 +4320,6 @@ var FuguAgent = class extends AbstractAgent {
4271
4320
  content: msg.content
4272
4321
  }));
4273
4322
  }
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
4323
  extractThinkingAndUsage(completion) {
4302
4324
  let thinkingContent = "";
4303
4325
  const message = completion.choices[0]?.message;
@@ -4305,21 +4327,14 @@ var FuguAgent = class extends AbstractAgent {
4305
4327
  thinkingContent = message.reasoning_content;
4306
4328
  this.logger(`Captured reasoning_content (${thinkingContent.length} characters)`);
4307
4329
  }
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);
4330
+ const tokenUsage = extractFuguTokenUsage(this.model, completion);
4331
+ if (tokenUsage) {
4332
+ const details = completion.usage ?? {};
4333
+ const orchestrationIn = details.prompt_tokens_details?.orchestration_input_tokens ?? 0;
4334
+ const orchestrationOut = details.completion_tokens_details?.orchestration_output_tokens ?? 0;
4335
+ if (tokenUsage.reasoningTokens || orchestrationIn || orchestrationOut) {
4321
4336
  this.logger(
4322
- `Output breakdown: ${reasoningTokens} reasoning tokens, ${finalAnswerTokens} final answer tokens`
4337
+ `Usage breakdown: ${tokenUsage.reasoningTokens ?? 0} reasoning tokens; orchestration ${orchestrationIn} in / ${orchestrationOut} out (billed, folded into the totals)`
4323
4338
  );
4324
4339
  }
4325
4340
  }
@@ -4361,7 +4376,6 @@ ${schemaDescription}`;
4361
4376
  this.logger(this.logTemplates.error(this.name, apiError));
4362
4377
  throw new Error(this.errorMessages.apiError(apiError));
4363
4378
  }
4364
- this.logRawUsageForCalibration(completion);
4365
4379
  const rawReply = completion.choices[0]?.message?.content;
4366
4380
  if (!rawReply) {
4367
4381
  throw new Error(this.errorMessages.emptyResponse);
@@ -4405,7 +4419,6 @@ ${schemaDescription}`;
4405
4419
  this.logger(this.logTemplates.error(this.name, apiError));
4406
4420
  throw new Error(this.errorMessages.apiError(apiError));
4407
4421
  }
4408
- this.logRawUsageForCalibration(completion);
4409
4422
  const rawReply = completion.choices[0]?.message?.content;
4410
4423
  if (!rawReply) {
4411
4424
  throw new Error(this.errorMessages.emptyResponse);
@@ -4424,6 +4437,31 @@ ${schemaDescription}`;
4424
4437
  }
4425
4438
  }
4426
4439
  };
4440
+ function extractFuguTokenUsage(modelApiName, completion) {
4441
+ const usage = completion?.usage;
4442
+ if (!usage) {
4443
+ return void 0;
4444
+ }
4445
+ const promptDetails = usage.prompt_tokens_details ?? {};
4446
+ const completionDetails = usage.completion_tokens_details ?? {};
4447
+ const inputTokens = (usage.prompt_tokens ?? 0) + (promptDetails.orchestration_input_tokens ?? 0);
4448
+ const outputTokens = (usage.completion_tokens ?? 0) + (completionDetails.orchestration_output_tokens ?? 0);
4449
+ const cachedInputTokens = (promptDetails.cached_tokens ?? 0) + (promptDetails.orchestration_input_cached_tokens ?? 0);
4450
+ const reasoningTokens = completionDetails.reasoning_tokens ?? 0;
4451
+ return {
4452
+ inputTokens,
4453
+ outputTokens,
4454
+ totalTokens: inputTokens + outputTokens,
4455
+ cachedInputTokens,
4456
+ // Omitted rather than 0: consumers persist the object as-is (see TokenUsage).
4457
+ ...reasoningTokens > 0 ? { reasoningTokens } : {},
4458
+ costUSD: calculateModelCost(modelApiName, inputTokens, outputTokens, {
4459
+ cacheHitTokens: cachedInputTokens,
4460
+ // Context-tier selection: the request's own size, not the orchestrator's traffic.
4461
+ contextTokens: usage.total_tokens ?? inputTokens
4462
+ })
4463
+ };
4464
+ }
4427
4465
 
4428
4466
  // src/agents/qwen-agent.ts
4429
4467
  import { OpenAI as OpenAI9 } from "openai";
@@ -5084,6 +5122,7 @@ var AgentFactory = class {
5084
5122
  return new GlmAgent(name, instruction, model.modelApiName, key, model.temperature, shouldEnableThinking);
5085
5123
  // Sakana Fugu models — always-on reasoning, no temperature (ignored by the model)
5086
5124
  case LLM_CONSTANTS.FUGU_ULTRA:
5125
+ case LLM_CONSTANTS.FUGU_MAX:
5087
5126
  return new FuguAgent(name, instruction, model.modelApiName, key, shouldEnableThinking);
5088
5127
  // Qwen models — thinking-only (enable_thinking always sent)
5089
5128
  case LLM_CONSTANTS.QWEN_MAX: