@qilin-agent/llm-retry 0.0.0-stage → 3.1.4

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 kkutysllb
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,70 @@
1
+ # Bilingual-pair consistency record for README.md (docs/i18n/README.md): per heading
2
+ # section, a hash of its English and Chinese blocks outside code blocks and generated regions.
3
+ # After editing either side, bring the other along and re-record with:
4
+ # pnpm run verify-translation-pairing --write packages/llm/llm-retry/README.md
5
+ /:
6
+ en: 48b7b8ee60e92601
7
+ zh: 28dfac5ed11f58ce
8
+ /qilin-agent-llm-retry:
9
+ en: ad59c00359289bbf
10
+ zh: bad791a25b1b5c2e
11
+ /qilin-agent-llm-retry/summary:
12
+ en: 8c6cf17ad639bcf9
13
+ zh: ca586c275d3f64db
14
+ /qilin-agent-llm-retry/table-of-contents:
15
+ en: d152484eb41ac6b4
16
+ zh: 09388d293f9be9cb
17
+ /qilin-agent-llm-retry/use-this-package:
18
+ en: ecee4f255eaa53bd
19
+ zh: 2e7983fd7668fa00
20
+ /qilin-agent-llm-retry/use-this-package/when-to-choose-it:
21
+ en: 7d3a9e96a556e1ff
22
+ zh: 7c9cce7e36ebc980
23
+ /qilin-agent-llm-retry/use-this-package/minimal-configuration:
24
+ en: 95b1cf09ee2c77a9
25
+ zh: 4d45e437db4988c7
26
+ /qilin-agent-llm-retry/use-this-package/what-you-can-observe:
27
+ en: 21267b22ad48e012
28
+ zh: 521bfad2c2d8e418
29
+ /qilin-agent-llm-retry/use-this-package/failures-and-recovery:
30
+ en: 730567739a21a4d2
31
+ zh: 3ea222c68d960948
32
+ /qilin-agent-llm-retry/understand-the-implementation:
33
+ en: cef3289094f94c25
34
+ zh: c7f40ff6e4e56de6
35
+ /qilin-agent-llm-retry/understand-the-implementation/design-philosophy:
36
+ en: fc4f15a14435698c
37
+ zh: 3c6d83f5ba04ec0e
38
+ /qilin-agent-llm-retry/understand-the-implementation/source-map:
39
+ en: f34b551859b68c88
40
+ zh: 813ffa32497b1d9a
41
+ /qilin-agent-llm-retry/understand-the-implementation/recovery-flow:
42
+ en: 85bace285b029101
43
+ zh: 6e0237450ca6ccc0
44
+ /qilin-agent-llm-retry/understand-the-implementation/waterfall-composition:
45
+ en: 388e8db333e03cb1
46
+ zh: 54b6446ac3584724
47
+ /qilin-agent-llm-retry/further-exploration:
48
+ en: 1ff198a0626eba5b
49
+ zh: 089b833f44f42c2f
50
+ /qilin-agent-llm-retry/model-experience:
51
+ en: 215e7ba838619b7b
52
+ zh: a311da3843709f90
53
+ /qilin-agent-llm-retry/model-experience/model-request-recovery:
54
+ en: 8496b7aefbe6fa20
55
+ zh: ed7b60ef09380ec4
56
+ /qilin-agent-llm-retry/model-experience/model-request-recovery/what-the-model-sees:
57
+ en: 9772b77e48e70dba
58
+ zh: df3d4105f7b8c256
59
+ /qilin-agent-llm-retry/model-experience/model-request-recovery/token-effect:
60
+ en: f86254d7c2ed2444
61
+ zh: 7e0f7b9e026e0bf2
62
+ /qilin-agent-llm-retry/model-experience/model-request-recovery/kv-cache-effect:
63
+ en: 82d6bdfc0ec747ea
64
+ zh: 14269c9dcd086611
65
+ /qilin-agent-llm-retry/known-limitations-and-deferred-work:
66
+ en: bef1baa545592b9c
67
+ zh: 61984a1e10de8294
68
+ /qilin-agent-llm-retry/known-limitations-and-deferred-work/dev-note:
69
+ en: add61ee91e4ae8e4
70
+ zh: 3877c147b541b2c9
package/README.md CHANGED
@@ -1,3 +1,148 @@
1
- # Temporary Holding Version
1
+ ---
2
+ description: "The retry executor for users and maintainers configuring provider-routed model-request recovery at durable agent-step boundaries."
3
+ kind: "package-reference"
4
+ ---
2
5
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
6
+ # @qilin-agent/llm-retry
7
+
8
+ English | [中文](README.zh.md)
9
+
10
+ ## Summary
11
+
12
+ Mount `@qilin-agent/llm-retry` to retry failed model requests at durable agent-step boundaries. Provider `retryPolicy` settings choose bounded normal-mode retries or unlimited always-mode retries; scheduled attempts reach the session log before backoff, and cancellation leaves consistent history. Retries re-run the failed step in the same open turn, while direct `ctx.llm.stream()` calls remain single-attempt. Each retry is another billed provider request, and always mode continues until success, cancellation, or disposal.
13
+
14
+ ## Table of Contents
15
+
16
+ - [Use this package](#use-this-package)
17
+ - [Understand the implementation](#understand-the-implementation)
18
+ - [Further Exploration](#further-exploration)
19
+ - [Model Experience](#model-experience)
20
+ - [Known Limitations and Deferred Work](#known-limitations-and-deferred-work)
21
+ - [Dev Note](#dev-note)
22
+
23
+ -----
24
+
25
+ <a id="use-this-package"></a>
26
+ ## Use this package
27
+
28
+ Mount this plugin when agent runs should recover from transient model-request failures — rate limits, server errors, timeouts, transport errors — instead of ending the turn. It is the executor: the retry policy itself lives on each provider adapter's configuration, and this package has no configuration of its own.
29
+
30
+ ### When to choose it
31
+
32
+ Choose it when a composition runs the agent loop and wants durable request recovery. The plugin is a function plugin with no config; provider adapters such as `qilin-llm-deepseek` and `qilin-llm-pi-ai` own the `retryPolicy` for their routes, and multi-provider adapters place it inside each provider profile. Skip it when calls go through `ctx.llm.stream()` directly without the agent loop: those consumers remain single-attempt because a raw stream cannot separate already-emitted chunks durably.
33
+
34
+ ### Minimal configuration
35
+
36
+ ```yaml
37
+ - name: '@qilin-agent/llm-deepseek-api-key'
38
+ config:
39
+ apiKeyEnv: DEEPSEEK_API_KEY
40
+ retryPolicy:
41
+ mode: always
42
+ backoff:
43
+ initialDelayMs: 1000
44
+ maxDelayMs: 30000
45
+ jitterRatio: 0.2
46
+
47
+ - name: '@qilin-agent/llm-retry'
48
+ ```
49
+
50
+ Omission of `retryPolicy` uses normal mode: five retries for `EMPTY_RESPONSE`, `RATE_LIMIT`, `SERVER`, `TIMEOUT`, and `TRANSPORT`, with bounded exponential backoff from 500 ms to 10 seconds and 10 percent jitter. Normal mode can change its finite budget, eligible codes, and backoff; always mode asks downstream recovery first, then retries every model-request failure without an attempt limit, stopping only on success, cancellation, or plugin disposal.
51
+
52
+ ### What you can observe
53
+
54
+ Each scheduled retry is durable before its wait: the plugin appends a non-surface `llm/retry` event carrying the retry id, provider, mode, policy key, failure, and scheduled delay, then a `llm/retry-started` event immediately before the retry begins. A valid `Retry-After` from the provider replaces local backoff when it fits the policy bounds. When the wait completes, the loop re-runs the failed step inside the same open turn over the same durable history, so the retried request is reconstructable from the session log exactly like the original. Cancellation or plugin disposal aborts active backoff, drains active delegated recovery, and makes a callback captured before disposal fail closed.
55
+
56
+ ### Failures and recovery
57
+
58
+ A failure before any final adapter is selected has no provider policy and delegates downstream unchanged. In normal mode, a failure code outside the eligible set, or an exhausted budget, delegates; in always mode, an over-cap provider delay uses the configured local backoff so the policy cannot terminate on that instruction. Nothing here is model-visible: no retry event, delay, provider error, or failed partial output reaches the model or derived messages.
59
+
60
+ -----
61
+
62
+ <a id="understand-the-implementation"></a>
63
+ ## Understand the implementation
64
+
65
+ <details>
66
+ <summary>Implementation internals — click to expand</summary>
67
+
68
+ This section explains the design behind the executor; the observable behavior is fully covered in [Use this package](#use-this-package).
69
+
70
+ ### Design philosophy
71
+
72
+ The executor is built on one rule: **durable before wait, open-step boundaries.** A retry is scheduled through the session log before any timer starts, so a crash or cancellation never leaves an invisible pending retry. Recovery runs on the agent loop's `agent/request-error` waterfall, the open-step extension point, rather than wrapping `ctx.llm.stream()` — a raw stream cannot separate already-emitted chunks durably, while the loop can re-run the failed step inside the same open turn.
73
+
74
+ ### Source map
75
+
76
+ | File | Role |
77
+ |---|---|
78
+ | [`src/index.ts`](src/index.ts) | The function plugin: waterfall listener, policy lookup, backoff, durable event appends |
79
+ | [`src/history.ts`](src/history.ts) | Durable retry-history lookup from the session log |
80
+ | [`src/types.ts`](src/types.ts) | Browser-safe `llm/retry` and `llm/retry-started` event payload types |
81
+ | [`src/brand.ts`](src/brand.ts) | The `RetryId` brand shared by the event payloads |
82
+
83
+ ### Recovery flow
84
+
85
+ A failed step arrives on the waterfall with its provider and resolved policy. Always mode settles downstream recovery first and honors a downstream `retry` decision; normal mode first checks that the failure code is eligible and the budget is not exhausted. The plugin computes the delay — provider `Retry-After` when valid and within bounds, otherwise local bounded exponential backoff with symmetric jitter — appends the `llm/retry` event, waits on a cancellable timer, appends `llm/retry-started`, and returns `{ kind: 'retry' }`. The loop then re-runs the failed step inside the same open turn over the same durable history.
86
+
87
+ ### Waterfall composition
88
+
89
+ The plugin is one listener in the `agent/request-error` waterfall. Always mode's "downstream first" posture means a later policy that ignores cancellation and never settles also prevents fallback, turn quiescence, and plugin disposal from completing; success, cancellation, or disposal stops always mode after active delegated recovery reaches quiescence.
90
+
91
+ </details>
92
+
93
+ -----
94
+
95
+ <a id="further-exploration"></a>
96
+ ## Further Exploration
97
+
98
+ Read these pages when the package-level contract is not enough. They move from the service contract to the adapters that own retry policies.
99
+
100
+ - [qilin-llm service](../llm/README.md) — the provider-neutral service whose adapters own `retryPolicy`.
101
+ - [llm-deepseek adapter](../llm-deepseek/README.md) — a provider adapter with a route-level `retryPolicy`.
102
+ - [llm-pi-ai adapter](../llm-pi-ai/README.md) — a multi-provider adapter with per-profile `retryPolicy`.
103
+ - [Terminal LLM stream failures](../../../.agents/notes/implemented/architecture/2026-07-29-terminal-llm-stream-failures.md) — how failures reach the service boundary as terminal chunks.
104
+ - [LLM streaming subsystem](../../../docs/subsystems/llm-streaming.md) — the `StreamChunk` protocol and adapter contract.
105
+
106
+ -----
107
+
108
+ <a id="model-experience"></a>
109
+ ## Model Experience
110
+
111
+ ### Model-request recovery
112
+
113
+ #### What the model sees
114
+
115
+ No retry event, delay, provider error, or failed partial output is model-visible. The retried step reconstructs the same explicit provider/model request from durable surface history unless a downstream recovery policy deliberately changes that surface; failed chunks never enter derived messages.
116
+
117
+ #### Token effect
118
+
119
+ Each retry is a new provider request and may repeat input-token billing. Normal mode has a finite budget; always mode can consume unbounded requests until success or cancellation. `llm/retry` itself contributes no tokens.
120
+
121
+ #### KV Cache effect
122
+
123
+ The reconstructed request preserves the prior prefix and is eligible for provider cache reuse under that provider's rules. The non-surface retry event does not change cache identity.
124
+
125
+ ## Known Limitations and Deferred Work
126
+
127
+ <a id="known-limitations-and-deferred-work"></a>
128
+
129
+
130
+ These limits define where the executor stops and future work begins. They are current package constraints, not a general retry comparison or a task backlog.
131
+
132
+ - **Agent turns are the only retry boundary** — direct `ctx.llm.stream()` consumers remain single-attempt because a raw stream cannot separate already-emitted chunks durably.
133
+ - **Always mode retries permanent failures** — authentication, quota, invalid-request, protocol, and unrecoverable context errors continue until success, cancellation, or disposal; deployments own provider-specific cost and latency controls.
134
+ - **Finite plugin budgets add** — normal mode counts only its configured codes and exact provider policy, while context-overflow compaction owns a separate budget. Any overlapping policy must define registration-order behavior.
135
+ - **Recovery policies compose by waterfall order** — always mode accepts a downstream retry before applying its fallback. A later policy that ignores cancellation and never settles also prevents fallback, turn quiescence, and plugin disposal from completing.
136
+ - **`llm/retry` records scheduling, not completion** — later step and turn events establish success, exhaustion, or cancellation.
137
+
138
+ <a id="dev-note"></a>
139
+ ### Dev Note
140
+
141
+ <details>
142
+ <summary>Working context for maintainers — click to expand</summary>
143
+
144
+ This Dev Note is non-authoritative working context: notes for maintainers and open questions. Shipped behavior and accepted rationale live in the sections above, the package code, and the linked Agent Notes.
145
+
146
+ - Retry numbers continue only across events with the same provider and complete policy key, so a route replacement with different limits, code membership, or backoff starts its own history; the key includes every behavior-affecting field and sorts normal-mode codes because eligibility uses set membership.
147
+
148
+ </details>
package/README.zh.md ADDED
@@ -0,0 +1,148 @@
1
+ ---
2
+ description: "面向用户与维护者的重试执行器说明:在持久 agent(智能体)步骤边界上配置按提供方路由的模型请求恢复。"
3
+ kind: "package-reference"
4
+ ---
5
+
6
+ # @qilin-agent/llm-retry
7
+
8
+ [English](README.md) | 中文
9
+
10
+ ## 概述
11
+
12
+ 挂载 `@qilin-agent/llm-retry`,可在持久 agent 步骤边界重试失败的模型请求。提供方的 `retryPolicy` 设置可选择有界的 normal mode 重试或无上限的 always mode 重试;计划的尝试会在退避前写入会话日志,取消后历史仍保持一致。重试会在同一个打开的轮次内重跑失败步骤,而直接 `ctx.llm.stream()` 调用仍只尝试一次。每次重试都会产生另一次提供方请求计费,always mode 会持续到成功、取消或释放。
13
+
14
+ ## 目录
15
+
16
+ - [使用本包](#use-this-package)
17
+ - [理解实现](#understand-the-implementation)
18
+ - [进一步探索](#further-exploration)
19
+ - [模型体验](#model-experience)
20
+ - [已知限制与延期工作](#known-limitations-and-deferred-work)
21
+ - [开发备注](#dev-note)
22
+
23
+ -----
24
+
25
+ <a id="use-this-package"></a>
26
+ ## 使用本包
27
+
28
+ 当 agent 运行应从暂时性模型请求失败——速率限制、服务端错误、超时、传输错误——中恢复而不是结束轮次时,挂载本插件。它是执行器:重试策略本身位于各提供方适配器的配置上,本包没有任何自己的配置。
29
+
30
+ ### 何时选择
31
+
32
+ 当组合运行 agent loop(智能体循环)并需要持久请求恢复时选择它。本插件是无配置的函数插件;`qilin-llm-deepseek` 与 `qilin-llm-pi-ai` 等提供方适配器拥有各自路由的 `retryPolicy`,多提供方适配器把它放进每个 provider profile。当调用不经 agent loop、直接走 `ctx.llm.stream()` 时跳过它:这些消费方仍是单次尝试,因为原始流无法持久地区分已发出的分片。
33
+
34
+ ### 最小配置
35
+
36
+ ```yaml
37
+ - name: '@qilin-agent/llm-deepseek-api-key'
38
+ config:
39
+ apiKeyEnv: DEEPSEEK_API_KEY
40
+ retryPolicy:
41
+ mode: always
42
+ backoff:
43
+ initialDelayMs: 1000
44
+ maxDelayMs: 30000
45
+ jitterRatio: 0.2
46
+
47
+ - name: '@qilin-agent/llm-retry'
48
+ ```
49
+
50
+ 省略 `retryPolicy` 时使用 normal mode:对 `EMPTY_RESPONSE`、`RATE_LIMIT`、`SERVER`、`TIMEOUT` 与 `TRANSPORT` 最多重试五次,退避从 500 毫秒到 10 秒、带 10% 抖动。normal mode 可以更改其有界预算、合格 code 与退避;always mode 先询问下游恢复,然后无尝试上限地重试每个模型请求失败,只在成功、取消或插件释放时停止。
51
+
52
+ ### 你可以观察到什么
53
+
54
+ 每次计划的重试在等待前就是持久的:插件会先追加携带重试 id、提供方、模式、策略键、失败与计划延迟的非表层 `llm/retry` 事件,然后在重试开始前立即追加 `llm/retry-started` 事件。提供方给出且符合策略边界的有效 `Retry-After` 会替换本地退避。等待完成后,loop 会在同一个打开的轮次内重跑失败步骤,仍基于同一份持久历史,因此重试请求与原始请求一样可以从会话日志重建。取消或插件释放会中止进行中的退避、等待活跃的委派恢复结算,并使释放前捕获的回调只能以失败结束。
55
+
56
+ ### 失败与恢复
57
+
58
+ 在任何最终适配器被选中之前发生的失败没有提供方策略,原样委派下游。normal mode 中,不在合格集合内的失败 code 或已耗尽的预算会委派;always mode 中,超上限的提供方延迟使用配置的本地退避,因此策略不会因该指令终止。这里没有任何模型可见内容:重试事件、延迟、提供方错误或失败的部分输出都不会到达模型或派生消息。
59
+
60
+ -----
61
+
62
+ <a id="understand-the-implementation"></a>
63
+ ## 理解实现
64
+
65
+ <details>
66
+ <summary>实现细节——点击展开</summary>
67
+
68
+ 本节解释执行器背后的设计;可观察行为已在[使用本包](#use-this-package)中完整说明。
69
+
70
+ ### 设计理念
71
+
72
+ 执行器建立在一条规定之上:**先持久、后等待,打开步骤边界。** 任何定时器启动之前,重试就已通过会话日志计划,因此崩溃或取消永远不会留下不可见的待处理重试。恢复运行在 agent loop 的 `agent/request-error` waterfall(瀑布式事件),即打开步骤扩展点上,而不是包装 `ctx.llm.stream()`——原始流无法持久地区分已发出的分片,而 loop 可以在同一个打开的轮次内重跑失败步骤。
73
+
74
+ ### 源码地图
75
+
76
+ | 文件 | 职责 |
77
+ |---|---|
78
+ | [`src/index.ts`](src/index.ts) | 函数插件:waterfall 监听器、策略查找、退避、持久事件追加 |
79
+ | [`src/history.ts`](src/history.ts) | 从会话日志查找持久重试历史 |
80
+ | [`src/types.ts`](src/types.ts) | 浏览器安全的 `llm/retry` 与 `llm/retry-started` 事件载荷类型 |
81
+ | [`src/brand.ts`](src/brand.ts) | 事件载荷共享的 `RetryId` 品牌 |
82
+
83
+ ### 恢复流程
84
+
85
+ 失败步骤连同其提供方与解析后的策略一起到达 waterfall。always mode 先结算下游恢复,并遵循下游的 `retry` 决定;normal mode 先检查失败 code 是否合格、预算是否未耗尽。插件计算延迟——有效且在边界内的提供方 `Retry-After`,否则带对称抖动的本地有界指数退避——追加 `llm/retry` 事件,在可取消定时器上等待,追加 `llm/retry-started`,然后返回 `{ kind: 'retry' }`。loop 随后在同一个打开的轮次内重跑失败步骤(仍基于同一份持久历史)。
86
+
87
+ ### Waterfall 组合
88
+
89
+ 本插件是 `agent/request-error` waterfall 中的一个监听器。always mode 的"下游优先"姿态意味着,之后忽略取消且永不结算的策略也会阻止回退、轮次完全停稳与插件释放完成;成功、取消或释放会在活动委派恢复达到完全停稳后停止 always mode。
90
+
91
+ </details>
92
+
93
+ -----
94
+
95
+ <a id="further-exploration"></a>
96
+ ## 进一步探索
97
+
98
+ 当包级约定不够用时阅读以下页面。它们从服务约定逐步进入拥有重试策略的适配器。
99
+
100
+ - [qilin-llm 服务](../llm/README.zh.md)——其适配器拥有 `retryPolicy` 的提供方无关服务。
101
+ - [llm-deepseek 适配器](../llm-deepseek/README.zh.md)——带路由级 `retryPolicy` 的提供方适配器。
102
+ - [llm-pi-ai 适配器](../llm-pi-ai/README.zh.md)——带逐 profile `retryPolicy` 的多提供方适配器。
103
+ - [LLM 流终止失败](../../../.agents/notes/implemented/architecture/2026-07-29-terminal-llm-stream-failures.zh.md)——失败如何以终止分片到达服务边界。
104
+ - [LLM 流式子系统](../../../docs/subsystems/llm-streaming.zh.md)——`StreamChunk` 协议与适配器约定(adapter contract)。
105
+
106
+ -----
107
+
108
+ <a id="model-experience"></a>
109
+ ## 模型体验
110
+
111
+ ### 模型请求恢复
112
+
113
+ #### 模型看到什么
114
+
115
+ 重试事件、延迟、提供方错误或失败的部分输出都不会对模型可见。除非下游恢复策略刻意改变该表层,重试步骤会从持久表层历史中重建相同的显式提供方/模型请求;失败分片绝不进入派生消息。
116
+
117
+ #### Token 影响
118
+
119
+ 每次重试都是一次新的提供方请求,可能重复输入 token 计费。normal mode 有有界预算;always mode 在成功或取消前可能消耗无上限请求。`llm/retry` 本身不贡献任何 token。
120
+
121
+ #### KV Cache 影响
122
+
123
+ 重建的请求保留此前前缀,有资格按该提供方规则复用提供方缓存。非表层重试事件不改变缓存标识。
124
+
125
+ ## 已知限制与延期工作
126
+
127
+ <a id="known-limitations-and-deferred-work"></a>
128
+
129
+
130
+ 这些限制说明执行器在哪里停止、由未来工作接续。它们是当前包约束,不是通用重试对比或任务积压。
131
+
132
+ - **agent 轮次是唯一重试边界**——直接 `ctx.llm.stream()` 消费方仍是单次尝试,因为原始流无法持久地区分已发出的分片。
133
+ - **always mode 会重试永久性失败**——认证、配额、无效请求、协议与不可恢复的上下文错误会持续到成功、取消或释放;部署方负责提供方专属的成本与延迟控制。
134
+ - **有界插件预算相加**——normal mode 只统计其配置的 code 与精确提供方策略,而上下文溢出压缩(compaction)拥有独立预算。任何重叠策略都必须定义注册顺序行为。
135
+ - **恢复策略按 waterfall 顺序组合**——always mode 先接受下游重试,再应用其回退。之后忽略取消且永不结算的策略也会阻止回退、轮次完全停稳与插件释放完成。
136
+ - **`llm/retry` 记录调度,而非完成**——后续步骤与轮次事件才确立成功、耗尽或取消。
137
+
138
+ <a id="dev-note"></a>
139
+ ### 开发备注
140
+
141
+ <details>
142
+ <summary>维护者的工作上下文——点击展开</summary>
143
+
144
+ 本开发备注是不具权威性的工作上下文:维护者备注与开放问题。已交付的行为与既定理由以上文、包代码和相关 Agent Note 为准。
145
+
146
+ - 重试编号只在同一提供方与完整策略键的事件间延续,因此限额、code 成员或退避不同的路由替换会开启自己的历史;该键包含每个影响行为的字段,并因资格判断使用集合成员而对 normal mode code 排序。
147
+
148
+ </details>
package/lib/index.js ADDED
@@ -0,0 +1,186 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import z from "@qilin-agent/schemastery";
3
+ import { z as z$1 } from "zod";
4
+ //#region lib/types/brand.js
5
+ /**
6
+ * Brand an implementation-minted retry-chain identity.
7
+ * @param id - opaque retry identity.
8
+ * @returns the same string, branded; no validation is performed.
9
+ */
10
+ function RetryId(id) {
11
+ return id;
12
+ }
13
+ //#endregion
14
+ //#region lib/types/index.js
15
+ /**
16
+ * Provider-routed model-request retry policy on the agent loop's request
17
+ * recovery extension point. Each scheduled retry is durable before its cancellable wait.
18
+ *
19
+ * @module @qilin-agent/llm-retry
20
+ */
21
+ const name = "llm-retry";
22
+ const inject = ["agents", "sessionProjections"];
23
+ /** Runtime schema for {@link Config}. */
24
+ const Config = z.object({});
25
+ function validateConfig(config) {
26
+ const [key] = Object.keys(config);
27
+ if (key === void 0) return;
28
+ if (key === "retryPolicy") throw new Error("llm-retry: retryPolicy belongs under each provider configuration");
29
+ throw new Error(`llm-retry: unknown key "${key}"`);
30
+ }
31
+ async function settleDownstream(next) {
32
+ try {
33
+ return {
34
+ type: "decision",
35
+ decision: await next()
36
+ };
37
+ } catch (error) {
38
+ return {
39
+ type: "error",
40
+ error
41
+ };
42
+ }
43
+ }
44
+ function localDelay(config, retry, random) {
45
+ const exponent = Math.min(retry - 1, 1024);
46
+ const exponential = Math.min(config.initialDelayMs * 2 ** exponent, config.maxDelayMs);
47
+ const jitter = 1 - config.jitterRatio + 2 * config.jitterRatio * random();
48
+ return Math.min(exponential * jitter, config.maxDelayMs);
49
+ }
50
+ function retryPolicyKey(policy) {
51
+ return policy.mode === "always" ? JSON.stringify([
52
+ policy.mode,
53
+ policy.initialDelayMs,
54
+ policy.maxDelayMs,
55
+ policy.jitterRatio
56
+ ]) : JSON.stringify([
57
+ policy.mode,
58
+ policy.maxRetries,
59
+ [...policy.retryableCodes].sort(),
60
+ policy.initialDelayMs,
61
+ policy.maxDelayMs,
62
+ policy.jitterRatio
63
+ ]);
64
+ }
65
+ function retryStateKey(provider, policyKey) {
66
+ return JSON.stringify([provider, policyKey]);
67
+ }
68
+ function cancellableDelay(delayMs, signal) {
69
+ if (signal.aborted) return Promise.resolve(false);
70
+ return new Promise((resolve) => {
71
+ const timer = setTimeout(() => {
72
+ signal.removeEventListener("abort", onAbort);
73
+ resolve(true);
74
+ }, delayMs);
75
+ function onAbort() {
76
+ clearTimeout(timer);
77
+ resolve(false);
78
+ }
79
+ signal.addEventListener("abort", onAbort, { once: true });
80
+ });
81
+ }
82
+ const llmRetryStateSchema = z$1.record(z$1.string(), z$1.object({
83
+ retry: z$1.number().int().nonnegative(),
84
+ retryId: z$1.string()
85
+ }));
86
+ function apply(ctx, config = {}, internals = {}) {
87
+ validateConfig(config);
88
+ ctx.sessionProjections.register({
89
+ key: "llmRetry",
90
+ stateVersion: 1,
91
+ stateSchema: llmRetryStateSchema,
92
+ init: () => ({}),
93
+ apply: (state, event) => {
94
+ if (event.type === "step/start" || event.type === "turn/end") return {};
95
+ if (event.type !== "llm/retry") return state;
96
+ const key = retryStateKey(event.data.provider, event.data.policyKey);
97
+ const entry = state[key];
98
+ if (entry?.retry === event.data.retry && entry.retryId === event.data.retryId) return state;
99
+ return {
100
+ ...state,
101
+ [key]: {
102
+ retry: event.data.retry,
103
+ retryId: event.data.retryId
104
+ }
105
+ };
106
+ }
107
+ });
108
+ const random = internals.random ?? Math.random;
109
+ const lifetime = new AbortController();
110
+ const active = /* @__PURE__ */ new Set();
111
+ function track(operation) {
112
+ const tracked = operation.finally(() => active.delete(tracked));
113
+ active.add(tracked);
114
+ return tracked;
115
+ }
116
+ async function backoff(agent, turn, step, failure, provider, policy, policyKey, retry, retryId, delayMs, signal) {
117
+ const fusedSignal = AbortSignal.any([signal, lifetime.signal]);
118
+ if (fusedSignal.aborted) return;
119
+ const eventData = policy.mode === "normal" ? {
120
+ retryId,
121
+ turn,
122
+ step,
123
+ provider,
124
+ mode: policy.mode,
125
+ policyKey,
126
+ retry,
127
+ maxRetries: policy.maxRetries,
128
+ delayMs,
129
+ failure
130
+ } : {
131
+ retryId,
132
+ turn,
133
+ step,
134
+ provider,
135
+ mode: policy.mode,
136
+ policyKey,
137
+ retry,
138
+ delayMs,
139
+ failure
140
+ };
141
+ agent.session.append("llm/retry", eventData);
142
+ if (!await cancellableDelay(delayMs, fusedSignal)) return;
143
+ agent.session.append("llm/retry-started", {
144
+ retryId,
145
+ turn,
146
+ step,
147
+ retry
148
+ });
149
+ return { kind: "retry" };
150
+ }
151
+ async function recover({ agent, turn, step, provider, failure, retryPolicy: policy, signal }, next) {
152
+ if (policy === void 0) return next();
153
+ if (policy.mode === "always") {
154
+ if (signal.aborted || lifetime.signal.aborted) return;
155
+ const fusedSignal = AbortSignal.any([signal, lifetime.signal]);
156
+ const downstream = await settleDownstream(next);
157
+ if (fusedSignal.aborted) return;
158
+ if (downstream.type === "error") ctx.logger.warn(`llm-retry: provider "${provider}" always policy ignored a downstream recovery failure: %o`, downstream.error);
159
+ if (downstream.type === "decision" && downstream.decision?.kind === "retry") return downstream.decision;
160
+ } else if (!policy.retryableCodes.includes(failure.code)) return next();
161
+ const policyKey = retryPolicyKey(policy);
162
+ const previous = ctx.sessionProjections.stateOf(agent.session, "llmRetry")[retryStateKey(provider, policyKey)];
163
+ const previousRetry = previous?.retry ?? 0;
164
+ if (policy.mode === "normal" && previousRetry >= policy.maxRetries) return next();
165
+ const retry = previousRetry + 1;
166
+ const retryId = previous?.retryId ?? RetryId(randomUUID());
167
+ let delayMs;
168
+ if (failure.providerRetryAfterMs !== void 0 && Number.isFinite(failure.providerRetryAfterMs) && failure.providerRetryAfterMs > 0) if (failure.providerRetryAfterMs > policy.maxDelayMs) {
169
+ if (policy.mode === "normal") return next();
170
+ delayMs = localDelay(policy, retry, random);
171
+ } else delayMs = failure.providerRetryAfterMs;
172
+ else delayMs = localDelay(policy, retry, random);
173
+ return backoff(agent, turn, step, failure, provider, policy, policyKey, retry, retryId, delayMs, signal);
174
+ }
175
+ const disposeListener = ctx.on("agent/request-error", (payload, next) => {
176
+ if (lifetime.signal.aborted) return Promise.resolve(void 0);
177
+ return track(recover(payload, next));
178
+ });
179
+ ctx.effect(() => async () => {
180
+ disposeListener();
181
+ lifetime.abort(/* @__PURE__ */ new Error("llm-retry plugin disposed"));
182
+ await Promise.allSettled([...active]);
183
+ }, "llm-retry: abort and drain active recovery");
184
+ }
185
+ //#endregion
186
+ export { Config, RetryId, apply, inject, name };
@@ -0,0 +1,10 @@
1
+ import type { Branded } from '@qilin-agent/brand';
2
+ /** Stable identity shared by every attempt in one request-step retry chain. */
3
+ export type RetryId = Branded<'RetryId'>;
4
+ /**
5
+ * Brand an implementation-minted retry-chain identity.
6
+ * @param id - opaque retry identity.
7
+ * @returns the same string, branded; no validation is performed.
8
+ */
9
+ export declare function RetryId(id: string): RetryId;
10
+ //# sourceMappingURL=brand.d.ts.map
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Brand an implementation-minted retry-chain identity.
3
+ * @param id - opaque retry identity.
4
+ * @returns the same string, branded; no validation is performed.
5
+ */
6
+ export function RetryId(id) {
7
+ return id;
8
+ }
9
+ //# sourceMappingURL=brand.js.map
@@ -0,0 +1,13 @@
1
+ /** Durable request-route lookup for one open model step. @module @qilin-agent/llm-retry/history */
2
+ import type { SessionEvent } from '@qilin-agent/session';
3
+ /**
4
+ * Find the provider in force for one currently open step.
5
+ * Request headers remain effective across turn boundaries until a newer full
6
+ * snapshot changes them; every provider change requires a newer full snapshot.
7
+ * @param events - session events ending inside the open step.
8
+ * @param turn - turn that owns the failed step.
9
+ * @param step - failed step whose provider is required.
10
+ * @returns the provider from the request header in force for the step.
11
+ */
12
+ export declare function providerForOpenStep(events: readonly SessionEvent[], turn: number, step: number): string | undefined;
13
+ //# sourceMappingURL=history.d.ts.map
@@ -0,0 +1,26 @@
1
+ /** Durable request-route lookup for one open model step. @module @qilin-agent/llm-retry/history */
2
+ /**
3
+ * Find the provider in force for one currently open step.
4
+ * Request headers remain effective across turn boundaries until a newer full
5
+ * snapshot changes them; every provider change requires a newer full snapshot.
6
+ * @param events - session events ending inside the open step.
7
+ * @param turn - turn that owns the failed step.
8
+ * @param step - failed step whose provider is required.
9
+ * @returns the provider from the request header in force for the step.
10
+ */
11
+ export function providerForOpenStep(events, turn, step) {
12
+ const stepStartIndex = events.findLastIndex(event => event.type === 'step/start'
13
+ && event.data.turn === turn
14
+ && event.data.step === step);
15
+ if (stepStartIndex < 0 || events.slice(stepStartIndex + 1).some(event => event.type === 'step/end' || event.type === 'turn/end'))
16
+ return undefined;
17
+ for (let index = events.length - 1; index >= 0; index -= 1) {
18
+ // The loop bounds prove this indexed read exists.
19
+ // oxlint-disable-next-line typescript/no-non-null-assertion
20
+ const event = events[index];
21
+ if (event.type === 'request/header')
22
+ return event.data.header.config.provider;
23
+ }
24
+ return undefined;
25
+ }
26
+ //# sourceMappingURL=history.js.map
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Provider-routed model-request retry policy on the agent loop's request
3
+ * recovery extension point. Each scheduled retry is durable before its cancellable wait.
4
+ *
5
+ * @module @qilin-agent/llm-retry
6
+ */
7
+ import type { Context } from '@qilin-agent/kylin';
8
+ import z from '@qilin-agent/schemastery';
9
+ import { RetryId } from './brand.ts';
10
+ export type { LlmRetryEventData, LlmRetryStartedEventData } from './types.ts';
11
+ export { RetryId } from './brand.ts';
12
+ export declare const name = "llm-retry";
13
+ export declare const inject: string[];
14
+ /** This policy executor has no config; providers own `retryPolicy`. */
15
+ export type Config = Readonly<Record<string, never>>;
16
+ /** Runtime schema for {@link Config}. */
17
+ export declare const Config: z<Config>;
18
+ /** Non-serializable hooks used to make timing policy deterministic in tests. */
19
+ export interface RetryInternals {
20
+ /** Random sample in the inclusive zero-to-one range used for jitter. */
21
+ random?: () => number;
22
+ }
23
+ /**
24
+ * Install provider-routed normal or unbounded request recovery.
25
+ * @param ctx - plugin context that owns the listener and active waits.
26
+ * @param config - empty executor config; provider registrations own policy.
27
+ * @param internals - non-serializable deterministic hooks for tests.
28
+ */
29
+ interface RetryStateEntry {
30
+ retry: number;
31
+ retryId: RetryId;
32
+ }
33
+ type LlmRetryState = Record<string, RetryStateEntry>;
34
+ declare module '@qilin-agent/session-projection/types' {
35
+ interface SessionProjectionStateMap {
36
+ /** Retry state for the current step by provider and policy. */
37
+ llmRetry: LlmRetryState;
38
+ }
39
+ }
40
+ export declare function apply(ctx: Context, config?: Config, internals?: RetryInternals): void;
41
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,197 @@
1
+ /**
2
+ * Provider-routed model-request retry policy on the agent loop's request
3
+ * recovery extension point. Each scheduled retry is durable before its cancellable wait.
4
+ *
5
+ * @module @qilin-agent/llm-retry
6
+ */
7
+ import { randomUUID } from 'node:crypto';
8
+ import z from '@qilin-agent/schemastery';
9
+ import { z as zod } from 'zod';
10
+ import { RetryId } from "./brand.js";
11
+ export { RetryId } from "./brand.js";
12
+ export const name = 'llm-retry';
13
+ export const inject = ['agents', 'sessionProjections'];
14
+ /** Runtime schema for {@link Config}. */
15
+ export const Config = z.object({});
16
+ function validateConfig(config) {
17
+ const [key] = Object.keys(config);
18
+ if (key === undefined)
19
+ return;
20
+ if (key === 'retryPolicy') {
21
+ throw new Error('llm-retry: retryPolicy belongs under each provider configuration');
22
+ }
23
+ throw new Error(`llm-retry: unknown key "${key}"`);
24
+ }
25
+ async function settleDownstream(next) {
26
+ try {
27
+ return { type: 'decision', decision: await next() };
28
+ }
29
+ catch (error) {
30
+ return { type: 'error', error };
31
+ }
32
+ }
33
+ function localDelay(config, retry, random) {
34
+ const exponent = Math.min(retry - 1, 1024);
35
+ const exponential = Math.min(config.initialDelayMs * 2 ** exponent, config.maxDelayMs);
36
+ const jitter = 1 - config.jitterRatio + 2 * config.jitterRatio * random();
37
+ return Math.min(exponential * jitter, config.maxDelayMs);
38
+ }
39
+ function retryPolicyKey(policy) {
40
+ return policy.mode === 'always'
41
+ ? JSON.stringify([policy.mode, policy.initialDelayMs, policy.maxDelayMs, policy.jitterRatio])
42
+ : JSON.stringify([
43
+ policy.mode,
44
+ policy.maxRetries,
45
+ [...policy.retryableCodes].sort(),
46
+ policy.initialDelayMs,
47
+ policy.maxDelayMs,
48
+ policy.jitterRatio,
49
+ ]);
50
+ }
51
+ function retryStateKey(provider, policyKey) {
52
+ return JSON.stringify([provider, policyKey]);
53
+ }
54
+ function cancellableDelay(delayMs, signal) {
55
+ if (signal.aborted)
56
+ return Promise.resolve(false);
57
+ return new Promise((resolve) => {
58
+ const timer = setTimeout(() => {
59
+ signal.removeEventListener('abort', onAbort);
60
+ resolve(true);
61
+ }, delayMs);
62
+ function onAbort() {
63
+ clearTimeout(timer);
64
+ resolve(false);
65
+ }
66
+ signal.addEventListener('abort', onAbort, { once: true });
67
+ });
68
+ }
69
+ // The cast bridges the branded retry id, which Zod cannot express directly.
70
+ const llmRetryStateSchema = zod.record(zod.string(), zod.object({
71
+ retry: zod.number().int().nonnegative(),
72
+ retryId: zod.string(),
73
+ }));
74
+ export function apply(ctx, config = {}, internals = {}) {
75
+ validateConfig(config);
76
+ ctx.sessionProjections.register({
77
+ key: 'llmRetry',
78
+ stateVersion: 1,
79
+ stateSchema: llmRetryStateSchema,
80
+ init: () => ({}),
81
+ apply: (state, event) => {
82
+ if (event.type === 'step/start' || event.type === 'turn/end')
83
+ return {};
84
+ if (event.type !== 'llm/retry')
85
+ return state;
86
+ const key = retryStateKey(event.data.provider, event.data.policyKey);
87
+ const entry = state[key];
88
+ if (entry?.retry === event.data.retry && entry.retryId === event.data.retryId)
89
+ return state;
90
+ return { ...state, [key]: { retry: event.data.retry, retryId: event.data.retryId } };
91
+ },
92
+ });
93
+ const random = internals.random ?? Math.random;
94
+ const lifetime = new AbortController();
95
+ const active = new Set();
96
+ function track(operation) {
97
+ const tracked = operation.finally(() => active.delete(tracked));
98
+ active.add(tracked);
99
+ return tracked;
100
+ }
101
+ async function backoff(agent, turn, step, failure, provider, policy, policyKey, retry, retryId, delayMs, signal) {
102
+ const fusedSignal = AbortSignal.any([signal, lifetime.signal]);
103
+ if (fusedSignal.aborted)
104
+ return;
105
+ const eventData = policy.mode === 'normal'
106
+ ? {
107
+ retryId,
108
+ turn,
109
+ step,
110
+ provider,
111
+ mode: policy.mode,
112
+ policyKey,
113
+ retry,
114
+ maxRetries: policy.maxRetries,
115
+ delayMs,
116
+ failure,
117
+ }
118
+ : {
119
+ retryId,
120
+ turn,
121
+ step,
122
+ provider,
123
+ mode: policy.mode,
124
+ policyKey,
125
+ retry,
126
+ delayMs,
127
+ failure,
128
+ };
129
+ agent.session.append('llm/retry', eventData);
130
+ if (!await cancellableDelay(delayMs, fusedSignal))
131
+ return;
132
+ agent.session.append('llm/retry-started', { retryId, turn, step, retry });
133
+ return { kind: 'retry' };
134
+ }
135
+ async function recover({ agent, turn, step, provider, failure, retryPolicy: policy, signal }, next) {
136
+ if (policy === undefined)
137
+ return next();
138
+ if (policy.mode === 'always') {
139
+ if (signal.aborted || lifetime.signal.aborted)
140
+ return;
141
+ const fusedSignal = AbortSignal.any([signal, lifetime.signal]);
142
+ // The loop and plugin lifetime stay open until delegated recovery settles.
143
+ // An abort then wins before the decision or fallback can mutate later state.
144
+ const downstream = await settleDownstream(next);
145
+ if (fusedSignal.aborted)
146
+ return;
147
+ if (downstream.type === 'error') {
148
+ ctx.logger.warn(`llm-retry: provider "${provider}" always policy ignored a downstream recovery failure: %o`, downstream.error);
149
+ }
150
+ if (downstream.type === 'decision' && downstream.decision?.kind === 'retry') {
151
+ return downstream.decision;
152
+ }
153
+ }
154
+ else if (!policy.retryableCodes.includes(failure.code)) {
155
+ return next();
156
+ }
157
+ const policyKey = retryPolicyKey(policy);
158
+ const retryState = ctx.sessionProjections.stateOf(agent.session, 'llmRetry');
159
+ const previous = retryState[retryStateKey(provider, policyKey)];
160
+ const previousRetry = previous?.retry ?? 0;
161
+ if (policy.mode === 'normal' && previousRetry >= policy.maxRetries)
162
+ return next();
163
+ const retry = previousRetry + 1;
164
+ const retryId = previous?.retryId ?? RetryId(randomUUID());
165
+ let delayMs;
166
+ if (failure.providerRetryAfterMs !== undefined
167
+ && Number.isFinite(failure.providerRetryAfterMs)
168
+ && failure.providerRetryAfterMs > 0) {
169
+ if (failure.providerRetryAfterMs > policy.maxDelayMs) {
170
+ if (policy.mode === 'normal')
171
+ return next();
172
+ delayMs = localDelay(policy, retry, random);
173
+ }
174
+ else {
175
+ delayMs = failure.providerRetryAfterMs;
176
+ }
177
+ }
178
+ else {
179
+ delayMs = localDelay(policy, retry, random);
180
+ }
181
+ return backoff(agent, turn, step, failure, provider, policy, policyKey, retry, retryId, delayMs, signal);
182
+ }
183
+ const disposeListener = ctx.on('agent/request-error', (payload, next) => {
184
+ // A waterfall may have captured this callback before its registration was
185
+ // removed. Lifetime cancellation must prevent that stale callback from
186
+ // entering a downstream policy after disposal.
187
+ if (lifetime.signal.aborted)
188
+ return Promise.resolve(undefined);
189
+ return track(recover(payload, next));
190
+ });
191
+ ctx.effect(() => async () => {
192
+ disposeListener();
193
+ lifetime.abort(new Error('llm-retry plugin disposed'));
194
+ await Promise.allSettled([...active]);
195
+ }, 'llm-retry: abort and drain active recovery');
196
+ }
197
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,42 @@
1
+ import type { LlmFailure } from '@qilin-agent/llm/types';
2
+ import type { RetryId } from './brand.ts';
3
+ export type { RetryId };
4
+ declare module '@qilin-agent/session/types' {
5
+ interface SessionEventMap {
6
+ /** Durable, non-surface record of one provider-routed retry scheduled after a failed request attempt. */
7
+ 'llm/retry': LlmRetryEventData;
8
+ /** Durable transition written after a retry wait succeeds and before the next request attempt starts. */
9
+ 'llm/retry-started': LlmRetryStartedEventData;
10
+ }
11
+ }
12
+ /** Durable payload recorded before one provider-routed model-request retry wait. */
13
+ export type LlmRetryEventData = {
14
+ retryId: RetryId;
15
+ turn: number;
16
+ step: number;
17
+ provider: string;
18
+ mode: 'normal';
19
+ policyKey: string;
20
+ retry: number;
21
+ maxRetries: number;
22
+ delayMs: number;
23
+ failure: LlmFailure;
24
+ } | {
25
+ retryId: RetryId;
26
+ turn: number;
27
+ step: number;
28
+ provider: string;
29
+ mode: 'always';
30
+ policyKey: string;
31
+ retry: number;
32
+ delayMs: number;
33
+ failure: LlmFailure;
34
+ };
35
+ /** Durable transition recorded after one retry delay completes. */
36
+ export interface LlmRetryStartedEventData {
37
+ retryId: RetryId;
38
+ turn: number;
39
+ step: number;
40
+ retry: number;
41
+ }
42
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
package/package.json CHANGED
@@ -1,6 +1,68 @@
1
1
  {
2
2
  "name": "@qilin-agent/llm-retry",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "description": "Provider-routed LLM request retry policy for the QiLin",
4
+ "version": "3.1.4",
5
+ "publishConfig": {
6
+ "access": "public"
7
+ },
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/kkutysllb/QiLin.git",
11
+ "directory": "packages/llm/llm-retry"
12
+ },
13
+ "engines": {
14
+ "node": "^22.19.0 || >=24.0.0"
15
+ },
16
+ "type": "module",
17
+ "main": "lib/index.js",
18
+ "types": "lib/types/index.d.ts",
19
+ "exports": {
20
+ ".": {
21
+ "types": "./lib/types/index.d.ts",
22
+ "default": "./lib/index.js"
23
+ },
24
+ "./types": {
25
+ "types": "./lib/types/types.d.ts",
26
+ "default": "./lib/types/types.js"
27
+ },
28
+ "./package.json": "./package.json"
29
+ },
30
+ "files": [
31
+ "lib/index.js",
32
+ "lib/types/**/*.js",
33
+ "lib/types/**/*.d.ts"
34
+ ],
35
+ "license": "MIT",
36
+ "peerDependencies": {
37
+ "@qilin-agent/llm": "3.1.4",
38
+ "@qilin-agent/session": "3.1.4",
39
+ "@qilin-agent/agent": "3.1.4",
40
+ "@qilin-agent/brand": "3.1.4",
41
+ "@qilin-agent/timeout": "3.1.4",
42
+ "@qilin-agent/session-projection": "3.1.4",
43
+ "@qilin-agent/kylin": "~4.0.3"
44
+ },
45
+ "dependencies": {
46
+ "zod": "^4.4.3",
47
+ "@qilin-agent/schemastery": "~3.18.3"
48
+ },
49
+ "devDependencies": {
50
+ "@qilin-agent/brand": "3.1.4",
51
+ "@qilin-agent/kylin-plugin-include": "~1.0.8",
52
+ "@qilin-agent/kylin-plugin-loader": "~1.0.4",
53
+ "@qilin-agent/agent": "3.1.4",
54
+ "@qilin-agent/agent-loop": "3.1.4",
55
+ "@qilin-agent/agent-loop-testkit": "3.1.4",
56
+ "@qilin-agent/llm": "3.1.4",
57
+ "@qilin-agent/llm-deepseek": "3.1.4",
58
+ "@qilin-agent/llm-mock-server": "3.1.4",
59
+ "@qilin-agent/llm-deepseek-api-key": "3.1.4",
60
+ "@qilin-agent/system-prompt": "3.1.4",
61
+ "@qilin-agent/session-persistence-jsonl": "3.1.4",
62
+ "@qilin-agent/timeout": "3.1.4",
63
+ "@qilin-agent/session-projection": "3.1.4",
64
+ "@qilin-agent/session": "3.1.4",
65
+ "@qilin-agent/tools": "3.1.4",
66
+ "@qilin-agent/kylin": "~4.0.3"
67
+ }
6
68
  }