@hiper2d/ai-agents 0.3.0 → 0.4.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
@@ -758,7 +758,8 @@ var API_KEY_CONSTANTS = {
758
758
  Z_AI: "Z_AI_API_KEY",
759
759
  FUGU: "FUGU_API_KEY",
760
760
  QWEN: "QWEN_API_KEY",
761
- MINIMAX: "MINIMAX_API_KEY"
761
+ MINIMAX: "MINIMAX_API_KEY",
762
+ META: "META_API_KEY"
762
763
  };
763
764
  var SupportedAiKeyNames = {
764
765
  [API_KEY_CONSTANTS.OPENAI]: "OpenAI",
@@ -771,7 +772,8 @@ var SupportedAiKeyNames = {
771
772
  [API_KEY_CONSTANTS.Z_AI]: "Z.AI",
772
773
  [API_KEY_CONSTANTS.FUGU]: "Sakana Fugu",
773
774
  [API_KEY_CONSTANTS.QWEN]: "Qwen",
774
- [API_KEY_CONSTANTS.MINIMAX]: "MiniMax"
775
+ [API_KEY_CONSTANTS.MINIMAX]: "MiniMax",
776
+ [API_KEY_CONSTANTS.META]: "Meta"
775
777
  };
776
778
  var LLM_CONSTANTS = {
777
779
  // Thinking-only catalog since 2026-08-05: models whose API offers a thinking toggle used to
@@ -808,7 +810,9 @@ var LLM_CONSTANTS = {
808
810
  QWEN_MAX: "qwen-max",
809
811
  QWEN_FLASH: "qwen-flash",
810
812
  // MiniMax. Single M3 entry; stable id without the version for the same repoint reason.
811
- MINIMAX: "minimax"
813
+ MINIMAX: "minimax",
814
+ // Meta Model API (api.meta.ai). Muse Spark; version-free id so a 1.3 → 1.4 repoint is entry-only.
815
+ MUSE_SPARK: "muse-spark"
812
816
  };
813
817
  var DEFAULT_MAX_OUTPUT_TOKENS = 8192;
814
818
  var SupportedAiModels = {
@@ -1093,6 +1097,22 @@ var SupportedAiModels = {
1093
1097
  hasThinking: true,
1094
1098
  temperature: 1,
1095
1099
  tags: ["very-slow", "cheap"]
1100
+ },
1101
+ // Meta Muse Spark 1.3 (added 2026-09-12) on Meta's own Model API — Standard tier, i.e.
1102
+ // the private model id (the `-contributor` id is a quarter of the price but Meta trains
1103
+ // on the prompts). Always-on reasoning with an effort dial (minimal … max, "none" is
1104
+ // rejected); the chain of thought is never returned, only an optional summary, plus
1105
+ // encrypted reasoning items replayed across turns like Grok. 'medium' is pinned as the
1106
+ // game default: turns are short and every reasoning token bills as output.
1107
+ // Temperature: Meta documents the model as tuned to its 1.0 default.
1108
+ // Speed/tags: unmeasured until the first live run — no tag rather than a guess.
1109
+ [LLM_CONSTANTS.MUSE_SPARK]: {
1110
+ displayName: "Muse Spark 1.3",
1111
+ modelApiName: "muse-spark-1.3",
1112
+ apiKeyName: API_KEY_CONSTANTS.META,
1113
+ hasThinking: true,
1114
+ temperature: 1,
1115
+ reasoningEffort: "medium"
1096
1116
  }
1097
1117
  };
1098
1118
  function createCatalog(overrides = {}) {
@@ -1360,6 +1380,14 @@ var MODEL_PRICING = {
1360
1380
  extendedContextOutputPrice: 2.4,
1361
1381
  extendedContextCacheHitPrice: 0.12,
1362
1382
  extendedContextThresholdTokens: 512e3
1383
+ },
1384
+ // Meta Muse Spark 1.3, Standard tier. Rates from ai.developer.meta.com/docs/pricing-rate-limits
1385
+ // (2026-09-12): no long-context premium at any point of the 1M window; reasoning tokens bill
1386
+ // as output; caching is automatic, hits reported in input_tokens_details.cached_tokens.
1387
+ [SupportedAiModels[LLM_CONSTANTS.MUSE_SPARK].modelApiName]: {
1388
+ inputPrice: 1.25,
1389
+ outputPrice: 4.25,
1390
+ cacheHitPrice: 0.15
1363
1391
  }
1364
1392
  };
1365
1393
  var HYBRID_THINKING_API_NAMES = /* @__PURE__ */ new Set([
@@ -1423,6 +1451,9 @@ function getProviderSignatureFields(aiType, signature) {
1423
1451
  if (aiType.startsWith("grok")) {
1424
1452
  return { grokEncryptedReasoning: signature };
1425
1453
  }
1454
+ if (aiType.startsWith("muse-")) {
1455
+ return { metaEncryptedReasoning: signature };
1456
+ }
1426
1457
  return {};
1427
1458
  }
1428
1459
 
@@ -1434,6 +1465,7 @@ var GEMINI_REASONING_EFFORTS = ["minimal", "low", "medium", "high"];
1434
1465
  var GLM_REASONING_EFFORTS = ["low", "high", "max"];
1435
1466
  var DEEPSEEK_REASONING_EFFORTS = ["low", "high", "max"];
1436
1467
  var FUGU_REASONING_EFFORTS = ["high", "xhigh"];
1468
+ var META_REASONING_EFFORTS = ["minimal", "low", "medium", "high", "xhigh", "max"];
1437
1469
  function clampReasoningEffort(effort, allowed) {
1438
1470
  const rank = REASONING_EFFORT_SCALE.indexOf(effort);
1439
1471
  let best = allowed[0];
@@ -1452,6 +1484,7 @@ var toAnthropicEffort = (effort) => clampReasoningEffort(effort, ANTHROPIC_REASO
1452
1484
  var toGeminiEffort = (effort) => clampReasoningEffort(effort, GEMINI_REASONING_EFFORTS);
1453
1485
  var toGlmEffort = (effort) => clampReasoningEffort(effort, GLM_REASONING_EFFORTS);
1454
1486
  var toDeepSeekEffort = (effort) => clampReasoningEffort(effort, DEEPSEEK_REASONING_EFFORTS);
1487
+ var toMetaEffort = (effort) => clampReasoningEffort(effort, META_REASONING_EFFORTS);
1455
1488
  var toFuguEffort = (effort) => clampReasoningEffort(effort, FUGU_REASONING_EFFORTS);
1456
1489
 
1457
1490
  // src/pricing/token-usage-utils.ts
@@ -1506,6 +1539,9 @@ function extractKimiTokenUsage(response) {
1506
1539
  function extractGrokTokenUsage(response) {
1507
1540
  return extractTokenUsage(response);
1508
1541
  }
1542
+ function extractMetaTokenUsage(response) {
1543
+ return extractTokenUsage(response);
1544
+ }
1509
1545
  function extractAnthropicTokenUsage(response) {
1510
1546
  if (!response?.usage) {
1511
1547
  return null;
@@ -1594,11 +1630,19 @@ function extractTokenUsageFromResponse4(response) {
1594
1630
  return extractGrokTokenUsage(response);
1595
1631
  }
1596
1632
 
1633
+ // src/pricing/meta-pricing.ts
1634
+ function calculateMetaCost(model, inputTokens, outputTokens, cacheHitTokens = 0) {
1635
+ return calculateCost(model, inputTokens, outputTokens, { cacheHitTokens });
1636
+ }
1637
+ function extractTokenUsageFromResponse5(response) {
1638
+ return extractMetaTokenUsage(response);
1639
+ }
1640
+
1597
1641
  // src/pricing/anthropic-pricing.ts
1598
1642
  function calculateAnthropicCost(model, inputTokens, outputTokens, cacheHitTokens = 0) {
1599
1643
  return calculateCost(model, inputTokens, outputTokens, { cacheHitTokens });
1600
1644
  }
1601
- function extractTokenUsageFromResponse5(response) {
1645
+ function extractTokenUsageFromResponse6(response) {
1602
1646
  return extractAnthropicTokenUsage(response);
1603
1647
  }
1604
1648
 
@@ -1606,7 +1650,7 @@ function extractTokenUsageFromResponse5(response) {
1606
1650
  function calculateGoogleCost(model, inputTokens, outputTokens, options = {}) {
1607
1651
  return calculateCost(model, inputTokens, outputTokens, options);
1608
1652
  }
1609
- function extractTokenUsageFromResponse6(response) {
1653
+ function extractTokenUsageFromResponse7(response) {
1610
1654
  return extractGoogleTokenUsage(response);
1611
1655
  }
1612
1656
 
@@ -1614,7 +1658,7 @@ function extractTokenUsageFromResponse6(response) {
1614
1658
  function calculateMistralCost(model, inputTokens, outputTokens, cacheHitTokens = 0) {
1615
1659
  return calculateCost(model, inputTokens, outputTokens, { cacheHitTokens });
1616
1660
  }
1617
- function extractTokenUsageFromResponse7(response) {
1661
+ function extractTokenUsageFromResponse8(response) {
1618
1662
  return extractMistralTokenUsage(response);
1619
1663
  }
1620
1664
 
@@ -2022,6 +2066,10 @@ var BudgetController = class {
2022
2066
  };
2023
2067
 
2024
2068
  // src/agents/abstract-agent.ts
2069
+ var beforeAskHook;
2070
+ function setBeforeAskHook(hook) {
2071
+ beforeAskHook = hook;
2072
+ }
2025
2073
  var AbstractAgent = class {
2026
2074
  name;
2027
2075
  gameId;
@@ -2073,6 +2121,7 @@ var AbstractAgent = class {
2073
2121
  * must NOT override these.
2074
2122
  */
2075
2123
  async askWithZodSchema(zodSchema, messages) {
2124
+ if (beforeAskHook) await beforeAskHook(this);
2076
2125
  const startedAt = Date.now();
2077
2126
  try {
2078
2127
  const [result, thinking, usage, signature] = await this.doAskWithZodSchema(zodSchema, messages);
@@ -2083,6 +2132,7 @@ var AbstractAgent = class {
2083
2132
  }
2084
2133
  }
2085
2134
  async askText(messages) {
2135
+ if (beforeAskHook) await beforeAskHook(this);
2086
2136
  const startedAt = Date.now();
2087
2137
  try {
2088
2138
  const [content, thinking, usage, signature] = await this.doAskText(messages);
@@ -4678,6 +4728,212 @@ ${openAIMessages[0].content}`;
4678
4728
  }
4679
4729
  };
4680
4730
 
4731
+ // src/agents/meta-agent.ts
4732
+ import { OpenAI as OpenAI11 } from "openai";
4733
+ var MetaAgent = class extends AbstractAgent {
4734
+ client;
4735
+ promptCacheKey;
4736
+ // Log message templates
4737
+ logTemplates = {
4738
+ error: (name, error) => `Error in ${name} agent: ${error}`
4739
+ };
4740
+ // Error message templates
4741
+ errorMessages = {
4742
+ emptyResponse: "Empty or undefined response from Meta API",
4743
+ invalidFormat: "Invalid response format from Meta API",
4744
+ apiError: (error) => `Failed to get response from Meta API: ${error instanceof Error ? error.message : String(error)}`
4745
+ };
4746
+ constructor(name, instruction, model, apiKey, temperature, enableThinking = false, agentLoggingConfig = DEFAULT_LOGGING_CONFIG.agents) {
4747
+ super(name, instruction, model, temperature, enableThinking, agentLoggingConfig);
4748
+ this.promptCacheKey = stableHashHex(`${name}
4749
+ ${instruction}`);
4750
+ this.client = new OpenAI11({
4751
+ apiKey,
4752
+ baseURL: "https://api.meta.ai/v1",
4753
+ timeout: 12e5
4754
+ });
4755
+ }
4756
+ /**
4757
+ * Structured output: json_schema format on the Responses API plus the schema described
4758
+ * in the prompt, parsed leniently and validated with Zod.
4759
+ */
4760
+ async doAskWithZodSchema(zodSchema, messages) {
4761
+ try {
4762
+ const schemaDescription = ZodSchemaConverter.toPromptDescription(zodSchema);
4763
+ const input = this.buildResponsesInput(this.prepareMessages(messages));
4764
+ const lastMessage = input[input.length - 1];
4765
+ if (lastMessage && typeof lastMessage.content === "string") {
4766
+ lastMessage.content += `
4767
+
4768
+ Your response must be a valid JSON object matching this schema:
4769
+ ${schemaDescription}`;
4770
+ }
4771
+ this.logAsking(messages);
4772
+ this.logMessages(messages);
4773
+ const response = await this.createResponse(input, zodSchema);
4774
+ const { text, reasoningSummary, encryptedReasoning } = this.extractResponseParts(response);
4775
+ if (!text) {
4776
+ throw new Error(this.errorMessages.emptyResponse);
4777
+ }
4778
+ this.logger(`Meta Agent - Found reasoning summary: ${!!reasoningSummary}, encrypted reasoning: ${!!encryptedReasoning}`);
4779
+ const parsedData = parseAndValidateLlmJson(text, zodSchema, (m) => this.logger(m));
4780
+ this.logger(`\u2705 Response validated successfully with Zod schema`);
4781
+ const tokenUsage = this.extractTokenUsage(response);
4782
+ if (parsedData) {
4783
+ this.logReply(parsedData, reasoningSummary, tokenUsage);
4784
+ }
4785
+ return [parsedData, reasoningSummary, tokenUsage, encryptedReasoning];
4786
+ } catch (error) {
4787
+ this.logger(this.logTemplates.error(this.name, error));
4788
+ throw new Error(this.errorMessages.apiError(error));
4789
+ }
4790
+ }
4791
+ /**
4792
+ * Plain-text ask: no JSON mode and no schema appended to the prompt.
4793
+ * Reasoning extraction and token accounting are identical to askWithZodSchema.
4794
+ */
4795
+ async doAskText(messages) {
4796
+ try {
4797
+ const input = this.buildResponsesInput(this.prepareMessages(messages));
4798
+ this.logAsking(messages);
4799
+ this.logMessages(messages);
4800
+ const response = await this.createResponse(input);
4801
+ const { text, reasoningSummary, encryptedReasoning } = this.extractResponseParts(response);
4802
+ if (!text) {
4803
+ throw new Error(this.errorMessages.emptyResponse);
4804
+ }
4805
+ const tokenUsage = this.extractTokenUsage(response);
4806
+ this.logReply(text, reasoningSummary, tokenUsage);
4807
+ return [text, reasoningSummary, tokenUsage, encryptedReasoning];
4808
+ } catch (error) {
4809
+ this.logger(this.logTemplates.error(this.name, error));
4810
+ throw new Error(this.errorMessages.apiError(error));
4811
+ }
4812
+ }
4813
+ createResponse(input, zodSchema) {
4814
+ return this.client.responses.create({
4815
+ model: this.model,
4816
+ temperature: this.temperature,
4817
+ input,
4818
+ // Reasoning bills against the output budget on top of the visible answer, so this
4819
+ // has to cover both. Raise it with a catalog `maxOutputTokens` override if Muse
4820
+ // ever starts truncating.
4821
+ max_output_tokens: this.maxOutputTokens,
4822
+ // Effort read at request time so a per-instance override (story generation runs
4823
+ // deeper) is honored; omitted entirely when nothing is pinned, leaving Meta's default.
4824
+ reasoning: {
4825
+ ...this.reasoningEffort ? { effort: toMetaEffort(this.reasoningEffort) } : {},
4826
+ summary: "auto"
4827
+ },
4828
+ // We manage conversation state ourselves; encrypted reasoning is only
4829
+ // returned for unstored responses.
4830
+ store: false,
4831
+ include: ["reasoning.encrypted_content"],
4832
+ prompt_cache_key: this.promptCacheKey,
4833
+ ...zodSchema ? {
4834
+ text: {
4835
+ format: {
4836
+ type: "json_schema",
4837
+ name: "response_schema",
4838
+ schema: ZodSchemaConverter.toJsonSchema(zodSchema),
4839
+ strict: false
4840
+ }
4841
+ }
4842
+ } : {}
4843
+ });
4844
+ }
4845
+ /**
4846
+ * Converts history to Responses API input items. The system instruction is merged into
4847
+ * the leading system message; assistant messages carrying stored encrypted reasoning get
4848
+ * their reasoning items replayed right before them.
4849
+ */
4850
+ buildResponsesInput(messages) {
4851
+ const input = [];
4852
+ for (const msg of messages) {
4853
+ if (msg.role === "assistant" && msg.metaEncryptedReasoning) {
4854
+ try {
4855
+ const reasoningItems = JSON.parse(msg.metaEncryptedReasoning);
4856
+ if (Array.isArray(reasoningItems)) {
4857
+ input.push(...reasoningItems);
4858
+ }
4859
+ } catch {
4860
+ this.logger(`Failed to parse stored encrypted reasoning, replaying message without it`);
4861
+ }
4862
+ }
4863
+ input.push({ role: msg.role, content: msg.content });
4864
+ }
4865
+ if (input.length > 0 && input[0].role !== "system") {
4866
+ input.unshift({ role: "system", content: this.instruction });
4867
+ } else if (input.length > 0 && input[0].role === "system") {
4868
+ input[0].content = `${this.instruction}
4869
+
4870
+ ${input[0].content}`;
4871
+ }
4872
+ return input;
4873
+ }
4874
+ /**
4875
+ * Walks the response output items: reasoning items yield the human-readable summary
4876
+ * plus the encrypted items (serialized for storage/replay); message items yield text.
4877
+ */
4878
+ extractResponseParts(response) {
4879
+ const textParts = [];
4880
+ const summaryParts = [];
4881
+ const encryptedItems = [];
4882
+ for (const item of response?.output ?? []) {
4883
+ if (!item) {
4884
+ continue;
4885
+ }
4886
+ if (item.type === "reasoning") {
4887
+ for (const summary of item.summary ?? []) {
4888
+ if (typeof summary?.text === "string" && summary.text) {
4889
+ summaryParts.push(summary.text);
4890
+ }
4891
+ }
4892
+ if (item.encrypted_content) {
4893
+ encryptedItems.push(item);
4894
+ }
4895
+ } else if (item.type === "message") {
4896
+ for (const part of item.content ?? []) {
4897
+ if (part?.type === "output_text" && typeof part.text === "string") {
4898
+ textParts.push(part.text);
4899
+ }
4900
+ }
4901
+ }
4902
+ }
4903
+ return {
4904
+ text: textParts.join("\n").trim(),
4905
+ reasoningSummary: summaryParts.join("\n").trim(),
4906
+ encryptedReasoning: encryptedItems.length > 0 ? JSON.stringify(encryptedItems) : void 0
4907
+ };
4908
+ }
4909
+ extractTokenUsage(response) {
4910
+ const usage = response?.usage;
4911
+ if (!usage) {
4912
+ return void 0;
4913
+ }
4914
+ const inputTokens = usage.input_tokens || 0;
4915
+ const outputTokens = usage.output_tokens || 0;
4916
+ const reasoningTokens = usage.output_tokens_details?.reasoning_tokens || 0;
4917
+ const cachedTokens = usage.input_tokens_details?.cached_tokens || 0;
4918
+ const cost = calculateMetaCost(this.model, inputTokens, outputTokens, cachedTokens);
4919
+ if (reasoningTokens > 0) {
4920
+ this.logger(`Output breakdown: ${reasoningTokens} reasoning tokens, ${outputTokens - reasoningTokens} final answer tokens, ${outputTokens} total output tokens`);
4921
+ }
4922
+ if (cachedTokens > 0) {
4923
+ this.logger(`Input breakdown: ${cachedTokens} cached tokens of ${inputTokens} input tokens`);
4924
+ }
4925
+ return {
4926
+ inputTokens,
4927
+ outputTokens,
4928
+ totalTokens: inputTokens + outputTokens,
4929
+ costUSD: cost,
4930
+ // Omitted when zero so we never hand Firestore an undefined value.
4931
+ ...reasoningTokens > 0 ? { reasoningTokens } : {},
4932
+ ...cachedTokens > 0 ? { cachedInputTokens: cachedTokens } : {}
4933
+ };
4934
+ }
4935
+ };
4936
+
4681
4937
  // src/agents/agent-factory.ts
4682
4938
  var AgentFactory = class {
4683
4939
  static createAgent(name, instruction, llmType, apiKeys, enableThinking = false) {
@@ -4731,6 +4987,9 @@ var AgentFactory = class {
4731
4987
  // MiniMax M3 — adaptive thinking (the model decides per-request)
4732
4988
  case LLM_CONSTANTS.MINIMAX:
4733
4989
  return new MiniMaxAgent(name, instruction, model.modelApiName, key, model.temperature, shouldEnableThinking);
4990
+ // Meta Muse Spark — Responses API with encrypted reasoning replay, effort from the catalog
4991
+ case LLM_CONSTANTS.MUSE_SPARK:
4992
+ return new MetaAgent(name, instruction, model.modelApiName, key, model.temperature, shouldEnableThinking);
4734
4993
  default:
4735
4994
  throw new Error(`Unknown Key: ${modelName}`);
4736
4995
  }
@@ -4772,7 +5031,9 @@ export {
4772
5031
  KimiAgent,
4773
5032
  LLM_CONSTANTS,
4774
5033
  MESSAGE_ROLE,
5034
+ META_REASONING_EFFORTS,
4775
5035
  MODEL_PRICING,
5036
+ MetaAgent,
4776
5037
  MiniMaxAgent,
4777
5038
  MistralAgent,
4778
5039
  ModelAuthenticationError,
@@ -4805,6 +5066,7 @@ export {
4805
5066
  calculateGoogleCost,
4806
5067
  calculateGrokCost,
4807
5068
  calculateKimiCost,
5069
+ calculateMetaCost,
4808
5070
  calculateMistralCost,
4809
5071
  calculateModelCost,
4810
5072
  calculateOpenAICost,
@@ -4816,18 +5078,20 @@ export {
4816
5078
  createVoiceAgent,
4817
5079
  evaluateBudget,
4818
5080
  extractAnthropicTokenUsage,
4819
- extractTokenUsageFromResponse5 as extractAnthropicTokenUsageFromResponse,
5081
+ extractTokenUsageFromResponse6 as extractAnthropicTokenUsageFromResponse,
4820
5082
  extractDeepSeekTokenUsage,
4821
5083
  extractTokenUsageFromResponse2 as extractDeepSeekTokenUsageFromResponse,
4822
5084
  extractFirstJsonObject,
4823
5085
  extractGoogleTokenUsage,
4824
- extractTokenUsageFromResponse6 as extractGoogleTokenUsageFromResponse,
5086
+ extractTokenUsageFromResponse7 as extractGoogleTokenUsageFromResponse,
4825
5087
  extractGrokTokenUsage,
4826
5088
  extractTokenUsageFromResponse4 as extractGrokTokenUsageFromResponse,
4827
5089
  extractKimiTokenUsage,
4828
5090
  extractTokenUsageFromResponse3 as extractKimiTokenUsageFromResponse,
5091
+ extractMetaTokenUsage,
5092
+ extractTokenUsageFromResponse5 as extractMetaTokenUsageFromResponse,
4829
5093
  extractMistralTokenUsage,
4830
- extractTokenUsageFromResponse7 as extractMistralTokenUsageFromResponse,
5094
+ extractTokenUsageFromResponse8 as extractMistralTokenUsageFromResponse,
4831
5095
  extractOpenAITokenUsage,
4832
5096
  extractTokenUsageFromResponse as extractOpenAITokenUsageFromResponse,
4833
5097
  extractTokenUsage,
@@ -4857,6 +5121,7 @@ export {
4857
5121
  periodEnd,
4858
5122
  periodKey,
4859
5123
  safeValidateResponse,
5124
+ setBeforeAskHook,
4860
5125
  setLlmLogger,
4861
5126
  stableHashHex,
4862
5127
  stripInlineThinking,
@@ -4866,6 +5131,7 @@ export {
4866
5131
  toFuguEffort,
4867
5132
  toGeminiEffort,
4868
5133
  toGlmEffort,
5134
+ toMetaEffort,
4869
5135
  toOpenAIEffort,
4870
5136
  transcribeWithGemini,
4871
5137
  transcribeWithOpenAi,