mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-26 04:15:48 -06:00
12138d2cee
A LINE push made exactly one attempt, so a transient provider or transport failure dropped the reply even though retrying was safe to do. Retrying alone would have duplicated a send LINE already accepted, so every push now carries an X-Line-Retry-Key and reuses it across attempts: LINE answers a replayed key with 409 and the accepted request's sent messages, which resolves to the original delivery instead of a second message. Retries follow LINE's documented policy - server errors and transport failures only, never 2xx, 409 or any 4xx - and run through the shared channel API retry runner in strict mode. Replies stay single-attempt because LINE offers no retry key for them.
38 lines
1.4 KiB
TypeScript
38 lines
1.4 KiB
TypeScript
// Line plugin module implements push retry policy behavior.
|
|
import { HTTPFetchError } from "@line/bot-sdk";
|
|
import { collectErrorGraphCandidates, extractErrorCode } from "openclaw/plugin-sdk/error-runtime";
|
|
import {
|
|
classifyTransientNetworkErrorCode,
|
|
createChannelApiRetryRunner,
|
|
} from "openclaw/plugin-sdk/retry-runtime";
|
|
|
|
function isRetryableLinePushError(error: unknown): boolean {
|
|
const candidates = collectErrorGraphCandidates(error, (candidate) => [
|
|
candidate.cause,
|
|
candidate.error,
|
|
]);
|
|
const httpError = candidates.find(
|
|
(candidate): candidate is HTTPFetchError => candidate instanceof HTTPFetchError,
|
|
);
|
|
if (httpError) {
|
|
// LINE documents server errors and transport failures as the retriable
|
|
// outcomes; every 4xx (429 included) answers "retries don't change the result".
|
|
return httpError.status >= 500;
|
|
}
|
|
// A transport failure never reached a LINE response, so the retry key decides
|
|
// whether the earlier attempt already landed.
|
|
return candidates.some(
|
|
(candidate) => classifyTransientNetworkErrorCode(extractErrorCode(candidate)) !== undefined,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Pushes are non-idempotent without a retry key, so the generic message-matching
|
|
* fallback stays off and only the classification above may replay a request.
|
|
*/
|
|
export const runLinePushWithRetries = createChannelApiRetryRunner({
|
|
shouldRetry: isRetryableLinePushError,
|
|
strictShouldRetry: true,
|
|
verbose: true,
|
|
});
|