|
1 | 1 | // Verifies OpenAI-compatible streaming payloads, failures, and transport wrapping. |
2 | 2 | import { createServer } from "node:http"; |
| 3 | +import OpenAI from "openai"; |
3 | 4 | import type { Api, Model } from "openclaw/plugin-sdk/llm"; |
4 | 5 | import { describe, expect, it, vi } from "vitest"; |
5 | 6 | import { |
@@ -4502,6 +4503,86 @@ describe("openai transport stream", () => {
|
4502 | 4503 | expect(stripped.input[1]).toEqual(params.input[1]); |
4503 | 4504 | }); |
4504 | 4505 | |
| 4506 | +it("retries thinking_signature_invalid once without encrypted reasoning content", async () => { |
| 4507 | +const request = { |
| 4508 | +model: "gpt-5.5", |
| 4509 | +stream: true, |
| 4510 | +input: [ |
| 4511 | +{ |
| 4512 | +type: "reasoning", |
| 4513 | +id: "rs_prior", |
| 4514 | +encrypted_content: "ciphertext", |
| 4515 | +summary: [], |
| 4516 | +}, |
| 4517 | +{ |
| 4518 | +type: "message", |
| 4519 | +id: "msg_prior", |
| 4520 | +role: "assistant", |
| 4521 | +content: [{ type: "output_text", text: "visible answer" }], |
| 4522 | +}, |
| 4523 | +{ |
| 4524 | +type: "function_call", |
| 4525 | +id: "fc_prior", |
| 4526 | +call_id: "call_abc", |
| 4527 | +name: "price_lookup", |
| 4528 | +arguments: "{}", |
| 4529 | +}, |
| 4530 | +], |
| 4531 | +}; |
| 4532 | +const recoveredStream = streamChunks([]); |
| 4533 | +const create = vi |
| 4534 | +.fn() |
| 4535 | +.mockRejectedValueOnce( |
| 4536 | +new OpenAI.BadRequestError( |
| 4537 | +400, |
| 4538 | +{ |
| 4539 | +code: "thinking_signature_invalid", |
| 4540 | +message: |
| 4541 | +"The encrypted content for item rs_prior could not be verified. Reason: Encrypted content could not be decrypted or parsed.", |
| 4542 | +type: "invalid_request_error", |
| 4543 | +}, |
| 4544 | +undefined, |
| 4545 | +new Headers(), |
| 4546 | +), |
| 4547 | +) |
| 4548 | +.mockResolvedValueOnce(recoveredStream); |
| 4549 | + |
| 4550 | +await expect( |
| 4551 | +testing.createResponsesStreamWithEncryptedContentRetry({ |
| 4552 | +client: { responses: { create } } as never, |
| 4553 | +request: request as never, |
| 4554 | +requestOptions: undefined, |
| 4555 | +model: { |
| 4556 | +id: "gpt-5.5", |
| 4557 | +name: "GPT-5.5", |
| 4558 | +api: "openai-responses", |
| 4559 | +provider: "openai", |
| 4560 | +baseUrl: "https://api.openai.com/v1", |
| 4561 | +reasoning: true, |
| 4562 | +input: ["text"], |
| 4563 | +cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, |
| 4564 | +contextWindow: 200_000, |
| 4565 | +maxTokens: 8192, |
| 4566 | +}, |
| 4567 | +}), |
| 4568 | +).resolves.toBe(recoveredStream); |
| 4569 | + |
| 4570 | +expect(create).toHaveBeenCalledTimes(2); |
| 4571 | +expect(create.mock.calls[0]?.[0]).toBe(request); |
| 4572 | +expect(create.mock.calls[1]?.[0]).toEqual({ |
| 4573 | + ...request, |
| 4574 | +input: [ |
| 4575 | +{ |
| 4576 | +type: "reasoning", |
| 4577 | +id: "rs_prior", |
| 4578 | +summary: [], |
| 4579 | +}, |
| 4580 | +request.input[1], |
| 4581 | +request.input[2], |
| 4582 | +], |
| 4583 | +}); |
| 4584 | +}); |
| 4585 | + |
4505 | 4586 | it("normalizes overlong Copilot Responses replay tool ids before dispatch", () => { |
4506 | 4587 | const longToolItemId = "iVec" + "A".repeat(360); |
4507 | 4588 | const longToolCallId = `call_ug6lFGKwZDjHfzW8H0PDQRwN|${longToolItemId}`; |
|