@hiper2d/ai-agents 0.13.1 → 0.14.1

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
@@ -1527,6 +1527,9 @@ function getProviderSignatureFields(aiType, signature) {
1527
1527
  if (aiType.startsWith("grok")) {
1528
1528
  return { grokEncryptedReasoning: signature };
1529
1529
  }
1530
+ if (aiType.startsWith("gpt")) {
1531
+ return { openaiEncryptedReasoning: signature };
1532
+ }
1530
1533
  if (aiType.startsWith("muse-")) {
1531
1534
  return { metaEncryptedReasoning: signature };
1532
1535
  }
@@ -2351,7 +2354,54 @@ ${instruction}`);
2351
2354
  * inherited from a default OpenAI can change under us.
2352
2355
  */
2353
2356
  reasoningParams() {
2354
- return this.reasoningEffort ? { reasoning: { effort: toOpenAIEffort(this.reasoningEffort) } } : {};
2357
+ return {
2358
+ reasoning: {
2359
+ ...this.reasoningEffort ? { effort: toOpenAIEffort(this.reasoningEffort) } : {},
2360
+ // Render reasoning from earlier turns into this sample. It is already the
2361
+ // default on all four models we run, but stated explicitly for the same reason
2362
+ // effort is. It only does anything because we replay the items ourselves.
2363
+ context: "all_turns"
2364
+ }
2365
+ };
2366
+ }
2367
+ /**
2368
+ * Responses API input items, with each assistant turn's stored reasoning items replayed
2369
+ * immediately before it. OpenAI attaches `encrypted_content` to reasoning items whenever
2370
+ * `store` is false, which is how we call it: the documented stateless route is to preserve
2371
+ * the output items and replay the history yourself, rather than lean on
2372
+ * `previous_response_id` and OpenAI-side session state.
2373
+ *
2374
+ * This was a single flattened string before ("User: ... / Assistant: ..."), which left
2375
+ * nowhere to put reasoning items. The system prompt still travels ONLY as `instructions`.
2376
+ *
2377
+ * Reasoning is readable only within one model family and the API silently omits items it
2378
+ * cannot read, so a bot moved between GPT-6 and GPT-5.6 Terra loses its reasoning and keeps
2379
+ * working. A bot moved to another provider never reaches this code at all: every agent
2380
+ * reads only its own field, and a missing one falls back to a plain message.
2381
+ */
2382
+ buildResponsesInput(messages) {
2383
+ const input = [];
2384
+ for (const msg of this.prepareMessages(messages)) {
2385
+ if (msg.role === "assistant" && msg.openaiEncryptedReasoning) {
2386
+ try {
2387
+ const items = JSON.parse(msg.openaiEncryptedReasoning);
2388
+ if (Array.isArray(items)) {
2389
+ input.push(...items);
2390
+ }
2391
+ } catch {
2392
+ this.logger("Failed to parse stored encrypted reasoning, replaying message without it");
2393
+ }
2394
+ }
2395
+ input.push({ role: msg.role, content: msg.content });
2396
+ }
2397
+ return input;
2398
+ }
2399
+ /** The response's reasoning items, JSON-serialized for the caller to store on the message. */
2400
+ extractReasoningItems(response) {
2401
+ const items = (response?.output ?? []).filter(
2402
+ (item) => item?.type === "reasoning" && item.encrypted_content
2403
+ );
2404
+ return items.length > 0 ? JSON.stringify(items) : void 0;
2355
2405
  }
2356
2406
  /**
2357
2407
  * Structured output method using Zod with OpenAI's Responses API
@@ -2363,7 +2413,7 @@ ${instruction}`);
2363
2413
  try {
2364
2414
  this.logAsking(messages);
2365
2415
  this.logMessages(messages);
2366
- const input = this.prepareMessages(messages).map((msg) => `${msg.role === "user" ? "User" : "Assistant"}: ${msg.content}`).join("\n\n");
2416
+ const input = this.buildResponsesInput(messages);
2367
2417
  const schemaToSend = zodSchema;
2368
2418
  let response;
2369
2419
  try {
@@ -2373,6 +2423,8 @@ ${instruction}`);
2373
2423
  input,
2374
2424
  max_output_tokens: this.maxOutputTokens,
2375
2425
  prompt_cache_key: this.promptCacheKey,
2426
+ // Stateless: encrypted reasoning is only returned for unstored responses.
2427
+ store: false,
2376
2428
  ...this.reasoningParams(),
2377
2429
  text: {
2378
2430
  format: zodTextFormat(schemaToSend, "response_schema")
@@ -2433,7 +2485,7 @@ ${instruction}`);
2433
2485
  this.logReply(response.output_parsed, reasoningContent, tokenUsage);
2434
2486
  }
2435
2487
  this.logger(`\u2705 Response validated successfully with Zod schema`);
2436
- return [response.output_parsed, reasoningContent, tokenUsage];
2488
+ return [response.output_parsed, reasoningContent, tokenUsage, this.extractReasoningItems(response)];
2437
2489
  } catch (error) {
2438
2490
  this.logger(this.logTemplates.error(this.name, error));
2439
2491
  if (error instanceof ModelError) {
@@ -2452,13 +2504,14 @@ ${instruction}`);
2452
2504
  try {
2453
2505
  this.logAsking(messages);
2454
2506
  this.logMessages(messages);
2455
- const input = this.prepareMessages(messages).map((msg) => `${msg.role === "user" ? "User" : "Assistant"}: ${msg.content}`).join("\n\n");
2507
+ const input = this.buildResponsesInput(messages);
2456
2508
  const response = await this.client.responses.create({
2457
2509
  model: this.model,
2458
2510
  instructions: this.instruction,
2459
2511
  input,
2460
2512
  max_output_tokens: this.maxOutputTokens,
2461
2513
  prompt_cache_key: this.promptCacheKey,
2514
+ store: false,
2462
2515
  ...this.reasoningParams()
2463
2516
  });
2464
2517
  const content = response.output_text;
@@ -2500,7 +2553,7 @@ ${instruction}`);
2500
2553
  }
2501
2554
  }
2502
2555
  this.logReply(content, "", tokenUsage);
2503
- return [content, "", tokenUsage];
2556
+ return [content, "", tokenUsage, this.extractReasoningItems(response)];
2504
2557
  } catch (error) {
2505
2558
  this.logger(this.logTemplates.error(this.name, error));
2506
2559
  if (error instanceof ModelError) {
@@ -4592,13 +4645,29 @@ var QwenAgent = class extends AbstractAgent {
4592
4645
  const budget = this.thinkingBudgetTokens;
4593
4646
  return {
4594
4647
  enable_thinking: this.enableThinking,
4595
- ...this.enableThinking && budget !== void 0 ? { thinking_budget: budget } : {}
4648
+ ...this.enableThinking && budget !== void 0 ? { thinking_budget: budget } : {},
4649
+ // Lets the model read the `reasoning_content` we replay on assistant messages, so a
4650
+ // bot keeps its own train of thought across turns. Qwen documents models as NOT
4651
+ // reading replayed reasoning unless this is set (qwen3.8-max and qwen3.8-flash are
4652
+ // both on the supported list). Measured 2026-09-22: qwen3.8-max actually used the
4653
+ // replayed reasoning with the flag off too, but we follow the documented contract
4654
+ // rather than rely on that. Stateless — the reasoning comes from our payload, not
4655
+ // from any server-side session.
4656
+ ...this.enableThinking ? { preserve_thinking: true } : {}
4596
4657
  };
4597
4658
  }
4659
+ /**
4660
+ * Assistant turns carry their own `reasoning_content` back so the model can reference what
4661
+ * it was thinking on earlier turns (paired with `preserve_thinking` in thinkingParams).
4662
+ * Qwen's reasoning is PLAIN TEXT, not a signed or encrypted blob, so there is nothing for
4663
+ * another provider to reject: a bot whose model changes mid-game simply stops sending it
4664
+ * and keeps working. The text is the same one the caller already stores as `thinking`.
4665
+ */
4598
4666
  convertToOpenAIMessages(messages) {
4599
4667
  return messages.map((msg) => ({
4600
4668
  role: msg.role,
4601
- content: msg.content
4669
+ content: msg.content,
4670
+ ...msg.role === "assistant" && msg.thinking ? { reasoning_content: msg.thinking } : {}
4602
4671
  }));
4603
4672
  }
4604
4673
  extractThinkingAndUsage(completion) {