@ai-setting/roy-agent-core 1.7.53 → 1.7.55

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.
Files changed (62) hide show
  1. package/dist/env/agent/index.js +3 -3
  2. package/dist/env/error-reply.d.ts +184 -0
  3. package/dist/env/event-source/event-source-handlers.d.ts +9 -23
  4. package/dist/env/event-source/index.js +6 -6
  5. package/dist/env/index.js +18 -18
  6. package/dist/env/llm/adapter-pi-ai.d.ts +147 -2
  7. package/dist/env/llm/adapter.d.ts +21 -1
  8. package/dist/env/llm/index.js +2 -2
  9. package/dist/env/llm/invoke.d.ts +41 -1
  10. package/dist/env/llm/llm.d.ts +25 -0
  11. package/dist/env/llm/reasoning-defense.d.ts +104 -0
  12. package/dist/env/llm/types.d.ts +26 -0
  13. package/dist/env/prompt/index.js +2 -2
  14. package/dist/env/prompt/prompts-index.d.ts +1 -1
  15. package/dist/env/session/index.js +4 -4
  16. package/dist/env/session/storage/index.js +3 -3
  17. package/dist/env/task/backend/index.js +0 -2
  18. package/dist/env/task/backend/types.d.ts +0 -11
  19. package/dist/env/task/delegate/index.js +2 -2
  20. package/dist/env/task/index.js +3 -3
  21. package/dist/env/task/plugins/card-chat-routing.d.ts +42 -89
  22. package/dist/env/task/plugins/index.js +4 -4
  23. package/dist/env/task/plugins/lark-card-patch-notifier.d.ts +0 -51
  24. package/dist/env/task/plugins/lark-cli-task-notify-plugin.d.ts +14 -31
  25. package/dist/env/task/plugins/per-task-group-chat-plugin.d.ts +4 -4
  26. package/dist/env/workflow/engine/engine.d.ts +65 -0
  27. package/dist/env/workflow/engine/index.js +2 -2
  28. package/dist/env/workflow/index.js +9 -9
  29. package/dist/env/workflow/nodes/index.js +1 -1
  30. package/dist/env/workflow/nodes/tool-node.d.ts +6 -0
  31. package/dist/env/workflow/plugins/index.js +2 -2
  32. package/dist/env/workflow/tools/index.js +6 -6
  33. package/dist/env/workflow/tools/workflow-validate-tool.d.ts +11 -2
  34. package/dist/env/workflow/types/run.d.ts +19 -0
  35. package/dist/env/workflow/utils/agent-session-metadata.d.ts +13 -0
  36. package/dist/env/workflow/utils/index.js +1 -1
  37. package/dist/env/workflow/utils/redact-command-secrets.d.ts +70 -0
  38. package/dist/env/workflow/utils/shell-dialect-lint.d.ts +75 -0
  39. package/dist/env/workflow/utils/validate-workflow-definition.d.ts +22 -0
  40. package/dist/index.js +20 -20
  41. package/dist/shared/@ai-setting/{roy-agent-core-stnddvx9.js → roy-agent-core-12d2xap8.js} +1 -1
  42. package/dist/shared/@ai-setting/{roy-agent-core-pk1b3kvf.js → roy-agent-core-2b8jtv7m.js} +1 -1
  43. package/dist/shared/@ai-setting/{roy-agent-core-x8n1r7h4.js → roy-agent-core-2bscfrr1.js} +188 -7
  44. package/dist/shared/@ai-setting/{roy-agent-core-gdsqp6j8.js → roy-agent-core-6masakz4.js} +45 -0
  45. package/dist/shared/@ai-setting/{roy-agent-core-vvxb9522.js → roy-agent-core-6qxh4xrm.js} +67 -6
  46. package/dist/shared/@ai-setting/{roy-agent-core-h93v6p6y.js → roy-agent-core-7qy3nffr.js} +2 -2
  47. package/dist/shared/@ai-setting/{roy-agent-core-4vpk40ss.js → roy-agent-core-8eqngasy.js} +2 -2
  48. package/dist/shared/@ai-setting/{roy-agent-core-brwzs8wf.js → roy-agent-core-9h72sk1n.js} +1 -1
  49. package/dist/shared/@ai-setting/{roy-agent-core-na73mym1.js → roy-agent-core-a4vzsn0g.js} +212 -1
  50. package/dist/shared/@ai-setting/{roy-agent-core-35s1w7tc.js → roy-agent-core-aer5kw1j.js} +142 -70
  51. package/dist/shared/@ai-setting/{roy-agent-core-j8e92gj8.js → roy-agent-core-b5ew8t0k.js} +0 -59
  52. package/dist/shared/@ai-setting/{roy-agent-core-4sx6sdb2.js → roy-agent-core-cd62e9p1.js} +1 -1
  53. package/dist/shared/@ai-setting/{roy-agent-core-39xejr39.js → roy-agent-core-e2z1etk4.js} +1 -1
  54. package/dist/shared/@ai-setting/{roy-agent-core-hch2geta.js → roy-agent-core-g36jevrk.js} +14 -183
  55. package/dist/shared/@ai-setting/{roy-agent-core-ph5qbqcx.js → roy-agent-core-j2nvedt6.js} +465 -128
  56. package/dist/shared/@ai-setting/{roy-agent-core-p2r27era.js → roy-agent-core-mxdc756a.js} +1 -1
  57. package/dist/shared/@ai-setting/{roy-agent-core-p8ak03s0.js → roy-agent-core-w527pcpe.js} +293 -42
  58. package/dist/shared/@ai-setting/{roy-agent-core-3tv1q6f2.js → roy-agent-core-ytr2w0vb.js} +2 -2
  59. package/dist/shared/@ai-setting/{roy-agent-core-yd21n6c7.js → roy-agent-core-zvemdfx5.js} +1 -1
  60. package/dist/test-utils/child-process-mock.d.ts +73 -0
  61. package/package.json +2 -2
  62. /package/dist/shared/@ai-setting/{roy-agent-core-zy3hqfwg.js → roy-agent-core-0hq21px2.js} +0 -0
@@ -2,14 +2,14 @@ import {
2
2
  AgentComponent,
3
3
  AgentComponentConfigSchema,
4
4
  SummaryAgent
5
- } from "../../shared/@ai-setting/roy-agent-core-3tv1q6f2.js";
5
+ } from "../../shared/@ai-setting/roy-agent-core-ytr2w0vb.js";
6
6
  import"../../shared/@ai-setting/roy-agent-core-fvs48d38.js";
7
7
  import"../../shared/@ai-setting/roy-agent-core-kzg1jwt8.js";
8
8
  import"../../shared/@ai-setting/roy-agent-core-rx16h2m1.js";
9
9
  import"../../shared/@ai-setting/roy-agent-core-qxnbvgwe.js";
10
10
  import"../../shared/@ai-setting/roy-agent-core-dv714bqh.js";
11
- import"../../shared/@ai-setting/roy-agent-core-pk1b3kvf.js";
12
- import"../../shared/@ai-setting/roy-agent-core-zy3hqfwg.js";
11
+ import"../../shared/@ai-setting/roy-agent-core-2b8jtv7m.js";
12
+ import"../../shared/@ai-setting/roy-agent-core-0hq21px2.js";
13
13
  import"../../shared/@ai-setting/roy-agent-core-c1v263jn.js";
14
14
  import"../../shared/@ai-setting/roy-agent-core-e25xkv53.js";
15
15
  import"../../shared/@ai-setting/roy-agent-core-ctdhjv68.js";
@@ -0,0 +1,184 @@
1
+ /**
2
+ * @fileoverview Task #4418: React 循环异常中断 → 给飞书发 reply
3
+ *
4
+ * ## 问题(用户飞书 p2p 2026-10-21)
5
+ *
6
+ * ```
7
+ * RetryError [AI_RetryError]: Failed after 4 attempts.
8
+ * Last error: Upstream request failed: Endpoint is unavailable.
9
+ * statusCode: 429 / isRetryable: true
10
+ * ```
11
+ *
12
+ * 上游 429 不是 roy-agent 的 bug;但**异常中断后飞书侧完全静默**是。
13
+ * 用户原话:「这个失败导致的 react 中断,也需要找到对应的 triggerEvent
14
+ * 构建 response 让飞书方知道(送一个 reply 哈)」。
15
+ *
16
+ * ## 为什么必须由框架直接发(不能靠 prompt 引导 agent)
17
+ *
18
+ * 正常路径的 reply 是 **LLM 自己调 lark-cli**(prompt 由
19
+ * `buildNotifyHandlePrompt` / event-source-handlers 生成,告诉 agent
20
+ * 用 `recommendedAction.replyTo` 回消息)。
21
+ *
22
+ * 但异常路径的根本问题是:**LLM 调用本身失败了**,React 循环从未拿到
23
+ * 一个可用的 assistant turn,自然也不可能再调工具发消息。所以框架必须
24
+ * 自己 spawn `lark-cli`,这是与正常路径的本质区别。
25
+ *
26
+ * ## 断点(为什么曾经没 reply)
27
+ *
28
+ * 1. `agent-component.ts:2096` 捕获异常 → `result.error` + `result.originalError`
29
+ * 2. `environment.ts:430` 非 ContextError → `throw new Error(result.error)`
30
+ * ⚠️ 类型在这里丢掉,下游只能拿到字符串
31
+ * 3. `context-handler.service.ts:224` 直接 rethrow
32
+ * 4. `query-executor.ts:287` 只处理 ContextError,其余 rethrow
33
+ * 5. `interactive.ts:1301` / `event-handler.ts:620` 的 catch **只 console.error**
34
+ * → 静默丢失
35
+ *
36
+ * ## 现在的接线(四处,去重保证不重复)
37
+ *
38
+ * 1. `QueryExecutor.execute` 的 catch —— 唯一执行入口,覆盖
39
+ * interactive / run / run --es 三种模式,带完整 `agentContext.envEvent`
40
+ * 2. `EventHandler.processQueue` 的 catch —— 兜住「还没走到 QueryExecutor
41
+ * 就失败」的路径(session 解析 / 事件分发),并作为 TUI 层吞异常时的
42
+ * 最后一道防线
43
+ * 3. REPL `executeInternal` 的 catch —— 兜住 agentContext 组装 / plugin
44
+ * 加载 / REPL 包装层(这些异常会被 REPL 自己吞掉,到不了 EventHandler)
45
+ * 4. `LarkCliInstance.handleEvent` 的 catch —— event-source 自身的解析 /
46
+ * 落库 / 分发失败
47
+ *
48
+ * 四处都调用本模块,意味着**任何 React 中断都至少有一个出口到飞书**。
49
+ *
50
+ * ## 设计原则
51
+ *
52
+ * - **规则表驱动(rule base)**:错误分类 + 是否通知 + 用户文案收敛成
53
+ * `FAILURE_RULES` 有序表;表尾 `UNKNOWN_RULE` 是 `notify: true` 的
54
+ * catch-all —— 结构上保证**任何未分类错误也不会沉默**。只有
55
+ * `ask_user`(正常交互)显式 `notify: false`
56
+ * - **best-effort**:任何异常(spawn 失败 / lark-cli 非 0 / 库挂了)
57
+ * 都不得向上抛,绝不污染原始错误传播链
58
+ * - **有硬超时**:兜底 reply 在 5s 内一定 settle(超时 kill 子进程)。
59
+ * 否则 lark-cli 卡住会把 `QueryExecutor` 的 rethrow 拖死 → 事件队列堵死
60
+ * - **复用现有机制**:spawn 参数形态与 `LarkCliNotificationChannel` /
61
+ * `event-source-handlers.sendLarkMessage` 保持一致(`--profile` 前置、
62
+ * messageId → `+messages-reply`、chatId → `+messages-send`)
63
+ * - **去重**:同一 triggerEvent(messageId)只回一次;chat-only 通道走
64
+ * 5 分钟窗口,**不会**把整个群永久封锁;发送失败回滚去重标记
65
+ * - **无 channel 不崩但也不静默**:本地 REPL / `act` 没有飞书上下文 → debug;
66
+ * 有 triggerEvent 却拿不到 replyChannel = 衔接 bug → warn
67
+ * - **用户可读**:给用户看中文原因,技术细节单独放 `technical` 字段(截断)
68
+ */
69
+ /**
70
+ * 失败分类。
71
+ *
72
+ * 顺序即优先级:越具体的越先匹配。
73
+ */
74
+ export type ReactFailureKind = "ask_user" | "upstream_retry" | "timeout" | "upstream_aborted" | "aborted" | "context_overflow" | "unknown";
75
+ export interface ReactFailureInfo {
76
+ kind: ReactFailureKind;
77
+ /** 命中的规则 id(可观测性 / 测试锚点) */
78
+ ruleId: string;
79
+ /** 一行标题(中文,给用户看) */
80
+ title: string;
81
+ /** 可读原因(中文,不含 stack trace) */
82
+ reason: string;
83
+ /** 是否值得用户稍后重试 */
84
+ retryable: boolean;
85
+ /**
86
+ * 是否应该把这条失败反馈到飞书。
87
+ *
88
+ * 只有 `ask_user`(正常交互)为 false —— **所有真正的失败/中断都必须通知**,
89
+ * 这是 #4418 的硬契约:不允许静默。
90
+ */
91
+ notify: boolean;
92
+ /** 技术细节(截断,供定位;不直接展示给用户) */
93
+ technical?: string;
94
+ }
95
+ /** replyChannel 的最小形态(同时兼容 EnvReplyChannel / event-source ReplyChannel) */
96
+ export interface FailureReplyChannelLike {
97
+ type?: string;
98
+ chatId?: string;
99
+ messageId?: string;
100
+ profile?: string;
101
+ appId?: string;
102
+ userId?: string;
103
+ }
104
+ export interface NotifyReactFailureOptions {
105
+ /** 触发 React 中断的错误(任意类型,内部会做类型安全处理) */
106
+ error: unknown;
107
+ /**
108
+ * 触发本次执行的 EnvEvent。
109
+ *
110
+ * 从中提取 replyChannel(优先 `metadata.replyChannel`,
111
+ * 兼容 `payload.replyChannel`),这就是用户说的「找到对应的 triggerEvent」。
112
+ */
113
+ envEvent?: unknown;
114
+ /** 显式 replyChannel(优先级最高,便于调用方直接指定) */
115
+ replyChannel?: FailureReplyChannelLike;
116
+ /** 原始 query 摘要(会被截断后放进 reply,便于用户对上号) */
117
+ query?: string;
118
+ /** 会话 ID(仅用于日志 / 去重兜底) */
119
+ sessionId?: string;
120
+ /** 去重 key 覆盖(默认从 messageId/chatId 推导) */
121
+ dedupKey?: string;
122
+ /**
123
+ * 兜底 reply 的硬超时(ms)。
124
+ *
125
+ * 生产默认 5s;仅测试注入短值用。超时后 kill 子进程并按失败返回,
126
+ * 保证 `QueryExecutor` 的 catch 不会被 lark-cli 挂住。
127
+ */
128
+ replyTimeoutMs?: number;
129
+ }
130
+ export interface NotifyReactFailureResult {
131
+ /** 是否成功投递到飞书 */
132
+ sent: boolean;
133
+ /** 失败分类(即使没发送也会返回,调用方可记日志) */
134
+ kind: ReactFailureKind;
135
+ /** 结构化失败信息 */
136
+ info: ReactFailureInfo;
137
+ /** 跳过原因(sent=false 时有值) */
138
+ skipped?: "no_channel" | "duplicate" | "not_a_failure";
139
+ /** 投递错误(sent=false 且确实尝试过时有值) */
140
+ error?: string;
141
+ }
142
+ /** 当前生效的规则 id 列表(供可观测性 / 文档 / 测试锚定顺序)。 */
143
+ export declare function getFailureRuleIds(): string[];
144
+ /**
145
+ * 把任意错误分类成结构化、用户可读的失败信息。
146
+ *
147
+ * ⚠️ 关键:必须能处理**类型已丢失**的场景 —— 真实调用链上
148
+ * `environment.ts` 会把 RetryError 字符串化成普通 `Error`,所以只能靠
149
+ * message 文本特征识别,不能依赖 `instanceof RetryError`。
150
+ */
151
+ export declare function classifyReactFailure(error: unknown): ReactFailureInfo;
152
+ /**
153
+ * 生成发给用户的 reply 正文(Markdown)。
154
+ *
155
+ * 契约:
156
+ * - 不含裸 stack trace(` at `)
157
+ * - 含「怎么办」的可操作建议
158
+ * - 总长度远小于飞书 4096 单条上限
159
+ */
160
+ export declare function buildFailureReplyMarkdown(info: ReactFailureInfo, opts?: {
161
+ query?: string;
162
+ sessionId?: string;
163
+ }): string;
164
+ /** 测试用:清空去重状态 */
165
+ export declare function __resetFailureReplyDedupForTest(): void;
166
+ /**
167
+ * React 循环异常中断时,尽最大努力给飞书发一条失败说明。
168
+ *
169
+ * **绝不抛异常** —— 所有错误都被吞掉并记日志,保证不污染原始错误传播。
170
+ *
171
+ * @example
172
+ * ```ts
173
+ * // interactive.ts executeInternal 的 catch 分支
174
+ * } catch (error) {
175
+ * await notifyReactFailure({
176
+ * error,
177
+ * envEvent: this.currentAgentContext?.envEvent,
178
+ * sessionId: agentContext?.eventSessionId,
179
+ * });
180
+ * // …原有错误处理继续
181
+ * }
182
+ * ```
183
+ */
184
+ export declare function notifyReactFailure(options: NotifyReactFailureOptions): Promise<NotifyReactFailureResult>;
@@ -106,11 +106,9 @@ export declare class LarkCliInstance implements EventSourceInstance {
106
106
  /**
107
107
  * ★ Task #4230 follow-up:卡片路由端口(lookup/getTask 侧)
108
108
  *
109
- * 构建一次、多处复用(含 loadPlugins 收尾时补 ensureTaskGroupChat)。
109
+ * ★ Task #4394:只剩 `getTask`(无触发群时 lineage 上溯兜底用)。
110
110
  */
111
111
  private cardRoutingPorts?;
112
- /** ★ Task #4230 follow-up:群名缓存(判定"触发群是不是别 task 的专属群") */
113
- private chatNameCache;
114
112
  /** ★ Task #4230 follow-up:已加载插件实例(收尾 cross-wire 用) */
115
113
  private pluginInstances;
116
114
  /**
@@ -269,13 +267,6 @@ export declare class LarkCliInstance implements EventSourceInstance {
269
267
  * 任意一环缺失(如没配 task component)→ 不注入,行为与之前一致(warn + skip)。
270
268
  */
271
269
  private wireCardActionPorts;
272
- /**
273
- * Task #4230 follow-up:把「按需建专属群」能力接到卡片路由端口上
274
- *
275
- * 顺序问题:两个插件是分别 import + registerPluginHooks 的,NotifyHook 在
276
- * PerTaskGroupChatPlugin 之前加载,所以只能在 loadPlugins 收尾时补一次。
277
- */
278
- private wireCardRoutingGroupProvider;
279
270
  /**
280
271
  * 获取插件的 hook 点列表
281
272
  */
@@ -330,22 +321,17 @@ export declare class LarkCliInstance implements EventSourceInstance {
330
321
  */
331
322
  private injectTaskGetter;
332
323
  /**
333
- * ★ Task #4230 follow-up:给支持 setCardRoutingPorts 的 plugin 注入「卡片路由端口」
324
+ * ★ Task #4230 follow-up / ★ Task #4394 精简:注入「卡片路由端口」
334
325
  *
335
- * 端口负责三件"需要 IO"的事(让 plugin 保持纯逻辑 + 可单测):
336
- * 1. lookupChatOwnership(chatId):这个群是不是某个 task 的专属群(多重信号)
337
- * 2. getTask(id):lineage 上溯
338
- * 3. ensureTaskGroupChat(task):按需建专属群(由 PerTaskGroupChatPlugin 提供,
339
- * 在 loadPlugins 收尾时 cross-wire)
340
- */
341
- private injectCardRoutingPorts;
342
- /**
343
- * 取群名(带进程内缓存)
326
+ * ★ Task #4394 删除:`lookupChatOwnership` + `lookupChatName` + `chatNameCache`
327
+ * —— 卡片不再判定"触发群是不是别 task 的专属群",因此整套群归属反查
328
+ * (session chat↔task 绑定 + `findTasksByContextValue` DB LIKE + lark-cli
329
+ * 查群名)都不再需要。这同时省掉了每张卡一次 DB 查询 + 一次 lark-cli 调用。
344
330
  *
345
- * 只在需要判定"触发群是不是别 task 的群"时调用(DB 信号为空才会走到)。
346
- * lark-cli 失败 / 未安装 → 返回 undefined(判定退化为"不是专属群")。
331
+ * 保留 `getTask(id)`:无触发群时,仍需要沿 lineage 上溯找本 task / 祖先
332
+ * 已有的专属群兜底(卡片不能丢)。
347
333
  */
348
- private lookupChatName;
334
+ private injectCardRoutingPorts;
349
335
  /**
350
336
  * ★ Task #4023:如果 plugin 支持 setSessionComponent,自动注入 SessionComponent 引用
351
337
  *
@@ -11,7 +11,7 @@ import {
11
11
  larkCliHandler,
12
12
  resolveBountyIMSystemPrompt,
13
13
  timerHandler
14
- } from "../../shared/@ai-setting/roy-agent-core-ph5qbqcx.js";
14
+ } from "../../shared/@ai-setting/roy-agent-core-j2nvedt6.js";
15
15
  import {
16
16
  BUILT_IN_EVENT_SOURCE_TYPES,
17
17
  BUILT_IN_EVENT_SOURCE_TYPE_LIST,
@@ -22,7 +22,7 @@ import {
22
22
  validateEventSourceConfig
23
23
  } from "../../shared/@ai-setting/roy-agent-core-ck60j1q9.js";
24
24
  import"../../shared/@ai-setting/roy-agent-core-rnwr39b0.js";
25
- import"../../shared/@ai-setting/roy-agent-core-hch2geta.js";
25
+ import"../../shared/@ai-setting/roy-agent-core-g36jevrk.js";
26
26
  import {
27
27
  EventStoreImpl,
28
28
  getSharedEventStore,
@@ -30,11 +30,11 @@ import {
30
30
  setSharedEventStore
31
31
  } from "../../shared/@ai-setting/roy-agent-core-yhsssn86.js";
32
32
  import"../../shared/@ai-setting/roy-agent-core-1pg0fepg.js";
33
- import"../../shared/@ai-setting/roy-agent-core-gdsqp6j8.js";
34
- import"../../shared/@ai-setting/roy-agent-core-brwzs8wf.js";
35
- import"../../shared/@ai-setting/roy-agent-core-j8e92gj8.js";
33
+ import"../../shared/@ai-setting/roy-agent-core-6masakz4.js";
34
+ import"../../shared/@ai-setting/roy-agent-core-9h72sk1n.js";
35
+ import"../../shared/@ai-setting/roy-agent-core-b5ew8t0k.js";
36
36
  import"../../shared/@ai-setting/roy-agent-core-9csa89jx.js";
37
- import"../../shared/@ai-setting/roy-agent-core-na73mym1.js";
37
+ import"../../shared/@ai-setting/roy-agent-core-a4vzsn0g.js";
38
38
  import"../../shared/@ai-setting/roy-agent-core-8s9jd2qy.js";
39
39
  import"../../shared/@ai-setting/roy-agent-core-jrj30vq3.js";
40
40
  import"../../shared/@ai-setting/roy-agent-core-dv714bqh.js";
package/dist/env/index.js CHANGED
@@ -1,21 +1,21 @@
1
1
  import {
2
2
  BaseEnvironment
3
- } from "../shared/@ai-setting/roy-agent-core-4sx6sdb2.js";
3
+ } from "../shared/@ai-setting/roy-agent-core-cd62e9p1.js";
4
4
  import {
5
5
  TaskComponent
6
- } from "../shared/@ai-setting/roy-agent-core-39xejr39.js";
6
+ } from "../shared/@ai-setting/roy-agent-core-e2z1etk4.js";
7
7
  import"../shared/@ai-setting/roy-agent-core-amkqnyzy.js";
8
8
  import"../shared/@ai-setting/roy-agent-core-qm9jda8p.js";
9
9
  import"../shared/@ai-setting/roy-agent-core-7g03zkkb.js";
10
10
  import"../shared/@ai-setting/roy-agent-core-rn57psp0.js";
11
11
  import"../shared/@ai-setting/roy-agent-core-8gxth0eh.js";
12
- import"../shared/@ai-setting/roy-agent-core-p2r27era.js";
12
+ import"../shared/@ai-setting/roy-agent-core-mxdc756a.js";
13
13
  import"../shared/@ai-setting/roy-agent-core-tbrn9wdm.js";
14
14
  import {
15
15
  EventSourceComponent,
16
16
  builtInHandlers,
17
17
  larkCliHandler
18
- } from "../shared/@ai-setting/roy-agent-core-ph5qbqcx.js";
18
+ } from "../shared/@ai-setting/roy-agent-core-j2nvedt6.js";
19
19
  import {
20
20
  EventSourceInitHooks,
21
21
  getDefaultConfigForType,
@@ -24,33 +24,33 @@ import {
24
24
  validateEventSourceConfig
25
25
  } from "../shared/@ai-setting/roy-agent-core-ck60j1q9.js";
26
26
  import"../shared/@ai-setting/roy-agent-core-rnwr39b0.js";
27
- import"../shared/@ai-setting/roy-agent-core-hch2geta.js";
27
+ import"../shared/@ai-setting/roy-agent-core-g36jevrk.js";
28
28
  import"../shared/@ai-setting/roy-agent-core-yhsssn86.js";
29
29
  import"../shared/@ai-setting/roy-agent-core-1pg0fepg.js";
30
- import"../shared/@ai-setting/roy-agent-core-gdsqp6j8.js";
31
- import"../shared/@ai-setting/roy-agent-core-brwzs8wf.js";
32
- import"../shared/@ai-setting/roy-agent-core-j8e92gj8.js";
30
+ import"../shared/@ai-setting/roy-agent-core-6masakz4.js";
31
+ import"../shared/@ai-setting/roy-agent-core-9h72sk1n.js";
32
+ import"../shared/@ai-setting/roy-agent-core-b5ew8t0k.js";
33
33
  import {
34
34
  WorkflowComponent
35
- } from "../shared/@ai-setting/roy-agent-core-4vpk40ss.js";
35
+ } from "../shared/@ai-setting/roy-agent-core-8eqngasy.js";
36
36
  import"../shared/@ai-setting/roy-agent-core-vrsxhw9e.js";
37
37
  import"../shared/@ai-setting/roy-agent-core-h0x19xgn.js";
38
38
  import"../shared/@ai-setting/roy-agent-core-1ce3fqrk.js";
39
- import"../shared/@ai-setting/roy-agent-core-vvxb9522.js";
40
- import"../shared/@ai-setting/roy-agent-core-yd21n6c7.js";
41
- import"../shared/@ai-setting/roy-agent-core-h93v6p6y.js";
39
+ import"../shared/@ai-setting/roy-agent-core-6qxh4xrm.js";
40
+ import"../shared/@ai-setting/roy-agent-core-zvemdfx5.js";
41
+ import"../shared/@ai-setting/roy-agent-core-7qy3nffr.js";
42
42
  import"../shared/@ai-setting/roy-agent-core-hhrg314p.js";
43
43
  import"../shared/@ai-setting/roy-agent-core-7evvpfwc.js";
44
- import"../shared/@ai-setting/roy-agent-core-35s1w7tc.js";
44
+ import"../shared/@ai-setting/roy-agent-core-aer5kw1j.js";
45
45
  import"../shared/@ai-setting/roy-agent-core-6atd905e.js";
46
46
  import"../shared/@ai-setting/roy-agent-core-6167phd4.js";
47
- import"../shared/@ai-setting/roy-agent-core-x8n1r7h4.js";
47
+ import"../shared/@ai-setting/roy-agent-core-2bscfrr1.js";
48
48
  import"../shared/@ai-setting/roy-agent-core-1db4vpc6.js";
49
49
  import"../shared/@ai-setting/roy-agent-core-ze068vc9.js";
50
50
  import"../shared/@ai-setting/roy-agent-core-fs36xgcs.js";
51
51
  import"../shared/@ai-setting/roy-agent-core-9csa89jx.js";
52
52
  import"../shared/@ai-setting/roy-agent-core-7exqwax1.js";
53
- import"../shared/@ai-setting/roy-agent-core-na73mym1.js";
53
+ import"../shared/@ai-setting/roy-agent-core-a4vzsn0g.js";
54
54
  import"../shared/@ai-setting/roy-agent-core-tjg71zqp.js";
55
55
  import"../shared/@ai-setting/roy-agent-core-ddwa0n6h.js";
56
56
  import"../shared/@ai-setting/roy-agent-core-8s9jd2qy.js";
@@ -59,7 +59,7 @@ import"../shared/@ai-setting/roy-agent-core-rvxg1wps.js";
59
59
  import {
60
60
  AgentComponent,
61
61
  AgentComponentConfigSchema
62
- } from "../shared/@ai-setting/roy-agent-core-3tv1q6f2.js";
62
+ } from "../shared/@ai-setting/roy-agent-core-ytr2w0vb.js";
63
63
  import"../shared/@ai-setting/roy-agent-core-fvs48d38.js";
64
64
  import"../shared/@ai-setting/roy-agent-core-kzg1jwt8.js";
65
65
  import"../shared/@ai-setting/roy-agent-core-rx16h2m1.js";
@@ -69,8 +69,8 @@ import {
69
69
  getXDGPaths
70
70
  } from "../shared/@ai-setting/roy-agent-core-qxnbvgwe.js";
71
71
  import"../shared/@ai-setting/roy-agent-core-dv714bqh.js";
72
- import"../shared/@ai-setting/roy-agent-core-pk1b3kvf.js";
73
- import"../shared/@ai-setting/roy-agent-core-zy3hqfwg.js";
72
+ import"../shared/@ai-setting/roy-agent-core-2b8jtv7m.js";
73
+ import"../shared/@ai-setting/roy-agent-core-0hq21px2.js";
74
74
  import"../shared/@ai-setting/roy-agent-core-c1v263jn.js";
75
75
  import"../shared/@ai-setting/roy-agent-core-e25xkv53.js";
76
76
  import {
@@ -11,16 +11,161 @@
11
11
  * - AssistantMessage.stopReason → ChatResponse.finishReason
12
12
  * - usage {input, output, totalTokens} → UsageInfo
13
13
  */
14
- import { type Model, type Context } from "@earendil-works/pi-ai";
14
+ import { type Model, type Context, type CacheRetention } from "@earendil-works/pi-ai";
15
15
  import type { LLMAdapter, ChatRequest, ChatResponse, StreamHandle, ResolvedProvider, EmbedRequest, EmbedResponse } from "./adapter";
16
16
  import type { LLMMessage, ToolInfo } from "./types";
17
+ /**
18
+ * Known provider list — 驱动 resolveProvider 的硬编码能力映射
19
+ *
20
+ * `apiProtocol`(Task #4383 P1)是**显式**协议声明,优先于任何 baseURL 猜测。
21
+ * 历史背景:`api` 字段曾被 `resolvePiModel()` 里的字面量特判
22
+ * (`baseURL?.includes("anthropic")`)绕过 —— minimax 因为真实 baseURL 恰好是
23
+ * `https://api.minimaxi.com/anthropic` 才「误打误撞」走对 anthropic-messages,
24
+ * 从而成为三家 provider 里唯一挂上显式 `cache_control` 的那个(#4381 实测 99.86%)。
25
+ * 现在把它变成声明式的,避免「换个 baseURL 就悄悄改了协议」。
26
+ *
27
+ * `api` 保留为**向后兼容**的遗留字段(只在既无 `apiProtocol` 也无 baseURL 线索时兜底)。
28
+ */
29
+ export declare const KNOWN_PROVIDER_APIS: Record<string, {
30
+ api: string;
31
+ apiProtocol?: string;
32
+ contextLimit: number;
33
+ maxTokensLimit: number;
34
+ }>;
35
+ /**
36
+ * Resolve the pi-ai API protocol for a provider (Task #4383 P1).
37
+ *
38
+ * Resolution order (first match wins):
39
+ * 1. `explicitProtocol` — caller/config pins it, highest priority
40
+ * 2. `PROVIDER_API_OVERRIDES` — deliberate id-variant pins
41
+ * 3. `PROVIDER_ENDPOINT_APIS[provider]` — declared per-endpoint mapping
42
+ * (only for vendors that genuinely ship two protocols)
43
+ * 4. `KNOWN_PROVIDER_APIS[provider].apiProtocol` — declarative vendor default
44
+ * 5. generic `baseURL` heuristics — backward compat for user-defined proxies
45
+ * (`/responses` → openai-responses, `anthropic` → anthropic-messages).
46
+ * This path logs a warning so we notice configs that should be declared.
47
+ * 6. `KNOWN_PROVIDER_APIS[provider].api` — legacy field
48
+ * 7. `openai-completions` — final fallback
49
+ *
50
+ * @param provider roy-agent provider id from config (e.g. "minimax")
51
+ * @param baseURL resolved provider base URL (may be undefined)
52
+ * @param explicitProtocol optional explicit protocol from caller/config
53
+ */
54
+ export declare function resolveApiProtocol(provider: string, baseURL?: string, explicitProtocol?: string): string;
55
+ /**
56
+ * pi-ai `Model.compat` overrides that make `reasoning: true` a **byte-level
57
+ * no-op** for the openai-completions request body (Task #4403).
58
+ *
59
+ * Both entries are load-bearing and were derived empirically by capturing the
60
+ * real payload through pi-ai's public `onPayload` hook and diffing it against
61
+ * the `reasoning: false` baseline:
62
+ *
63
+ * - `supportsDeveloperRole: false` — without it, `openai-completions.js:591`
64
+ * computes `useDeveloperRole = model.reasoning && compat.supportsDeveloperRole`
65
+ * and rewrites the first message's role from `system` to `developer`.
66
+ * That difference lands at **JSON offset ~40** (the very start of the body),
67
+ * so prompt caching — which is a prefix match — would miss on every turn.
68
+ * - `thinkingFormat: "openai"` — without it, `openai-completions.js:433-475`
69
+ * picks the vendor's native thinking branch and, because roy-agent never
70
+ * passes `options.reasoningEffort`, emits an explicit
71
+ * `thinking: {type: "disabled"}` (deepseek) / `enable_thinking: false`
72
+ * (zai/qwen). That is a new top-level field the provider never saw before.
73
+ *
74
+ * With all three pins the payload is byte-identical to the `reasoning: false`
75
+ * baseline for every CUSTOM provider in the real config (verified for
76
+ * opencodego / glm / minimax / ali / volcengine / tong / opencode, plus
77
+ * deepseek once the Task #4398 `reasoning_content` hook is attached), so
78
+ * turning reasoning on can never regress the cache.
79
+ *
80
+ * Scope caveat: this guarantee covers the **fallback** (custom-provider)
81
+ * branch of `resolvePiModel()`. pi-ai built-ins keep their own hand-tuned
82
+ * `compat`/`reasoning` in the descriptor and are deliberately left alone.
83
+ */
84
+ export declare const REASONING_COMPAT_PIN: {
85
+ readonly supportsDeveloperRole: false;
86
+ readonly thinkingFormat: "openai";
87
+ readonly supportsReasoningEffort: false;
88
+ };
89
+ /**
90
+ * Protocols where {@link REASONING_COMPAT_PIN} can actually hold the payload
91
+ * byte-stable.
92
+ *
93
+ * `openai-responses` is deliberately excluded: pi-ai flips the system prompt
94
+ * role there **unconditionally** —
95
+ * `openai-responses-shared.js:66: const role = model.reasoning ? "developer" : "system"`
96
+ * — with no `compat` escape hatch (its `getCompat()` only exposes
97
+ * `sendSessionIdHeader` / `supportsLongCacheRetention`). So on that path
98
+ * `reasoning: true` is a guaranteed prefix break, and we keep it off.
99
+ */
100
+ export declare const REASONING_SAFE_PROTOCOLS: ReadonlySet<string>;
101
+ /**
102
+ * Decide the `reasoning` flag for a pi-ai model descriptor (Task #4403).
103
+ *
104
+ * This replaces the hardcoded `reasoning: false` that `resolvePiModel()` used
105
+ * to emit for every non-built-in (i.e. every custom) provider — the root
106
+ * problem flagged by Task #4398, because that literal silently disabled every
107
+ * `model.reasoning`-gated pi-ai capability.
108
+ *
109
+ * Resolution order:
110
+ * 1. protocol safety guard — see {@link REASONING_SAFE_PROTOCOLS}. Even an
111
+ * explicit `capabilities.reasoning: true` cannot win here, because on
112
+ * `openai-responses` the request prefix is guaranteed to change.
113
+ * 2. explicit config `capabilities.reasoning` (either `true` or `false`).
114
+ * 3. conservative default `false`.
115
+ *
116
+ * Deliberately NO model-name pattern matching. Guessing from names (e.g.
117
+ * "looks like a reasoner") risks emitting provider-specific thinking fields
118
+ * that the backend rejects (400), makes calls slower, and bills thinking
119
+ * tokens as output. Config is the only source of truth; absence means off.
120
+ *
121
+ * @param provider roy-agent provider id (may be a gateway)
122
+ * @param modelId model id requested from the provider
123
+ * @param capabilities `llm.providers.<id>.capabilities` from config
124
+ * @param apiProtocol resolved pi-ai API protocol for this target
125
+ */
126
+ export declare function resolveModelReasoning(provider: string, modelId: string, capabilities?: {
127
+ reasoning?: boolean;
128
+ }, apiProtocol?: string): boolean;
129
+ /**
130
+ * Build the pi-ai cache-related request options (Task #4383 P0).
131
+ *
132
+ * Why this exists (#4381 root cause): pi-ai only emits an explicit cache
133
+ * marker when it is handed the right options. Two independent code paths:
134
+ *
135
+ * - **openai-completions** (`openai-completions.js:394-398`): `prompt_cache_key`
136
+ * is only set when `baseUrl` contains `api.openai.com` **or**
137
+ * `cacheRetention === "long" && compat.supportsLongCacheRetention`.
138
+ * deepseek / glm are neither `api.openai.com` nor passed any retention, so
139
+ * `prompt_cache_key` was permanently `undefined` → they fell back to implicit
140
+ * caching. Passing `cacheRetention: "long"` also sets
141
+ * `prompt_cache_retention: "24h"`.
142
+ * - **anthropic-messages** (`anthropic.js:25-36, 667-707`): `cache_control`
143
+ * markers are attached whenever `cacheRetention !== "none"`, and pi-ai
144
+ * already defaults to `"short"` (ephemeral, no ttl). That default is exactly
145
+ * what makes minimax hit 99.86%, so we must NOT override it — passing
146
+ * `"long"` would add `ttl: "1h"` and silently change current behaviour.
147
+ *
148
+ * Hence: openai-completions defaults to `"long"`, everything else keeps pi-ai's
149
+ * own default unless the caller explicitly asks for a retention.
150
+ *
151
+ * @returns options fragment to spread into `complete()` / `stream()` options.
152
+ * Only keys that should actually be sent are present (no `undefined`
153
+ * values), so `"sessionId" in opts` stays a meaningful assertion.
154
+ */
155
+ export declare function buildPiCacheOptions(args: {
156
+ apiProtocol: string;
157
+ sessionId?: string;
158
+ cacheRetention?: CacheRetention;
159
+ }): Record<string, unknown>;
17
160
  /**
18
161
  * Resolve built-in models and models declared by roy-agent configuration.
19
162
  * pi-ai's generated registry only contains its bundled provider/model pairs;
20
163
  * custom provider ids (for example minimax-openai) need a complete Model
21
164
  * descriptor before complete()/stream() can dispatch by API.
22
165
  */
23
- export declare function resolvePiModel(provider: string, modelId: string, baseURL?: string): Model<any>;
166
+ export declare function resolvePiModel(provider: string, modelId: string, baseURL?: string, capabilities?: {
167
+ reasoning?: boolean;
168
+ }): Model<any>;
24
169
  /**
25
170
  * 将 LLMMessage 转换为 pi-ai 的消息结构
26
171
  */
@@ -8,7 +8,7 @@
8
8
  *
9
9
  * 通过 {@link createLLMAdapter} 选择,由环境变量 `ROY_LLM_PROVIDER` 控制。
10
10
  */
11
- import type { LLMMessage, ToolInfo, UsageInfo } from "./types";
11
+ import type { LLMMessage, ToolInfo, UsageInfo, ProviderCapabilities } from "./types";
12
12
  /** Adapter 名称(联合类型 + 运行时数组) */
13
13
  export type AdapterName = "pi-ai" | "ai-sdk";
14
14
  /** 运行时可用的 adapter 列表 */
@@ -40,6 +40,26 @@ export interface ChatRequest {
40
40
  * `x-opencode-session` header (see opencode-session.ts).
41
41
  */
42
42
  sessionId?: string;
43
+ /**
44
+ * Prompt-cache retention preference (Task #4383 P0).
45
+ *
46
+ * Forwarded to pi-ai's `ProviderStreamOptions.cacheRetention`. When omitted,
47
+ * `buildPiCacheOptions()` applies a protocol-aware default:
48
+ * - `openai-completions` → `"long"` (needed for `prompt_cache_key`)
49
+ * - anything else → pi-ai's own default (`anthropic` uses `"short"` =
50
+ * ephemeral `cache_control`, which is the behaviour minimax already relies on)
51
+ */
52
+ cacheRetention?: "none" | "short" | "long";
53
+ /**
54
+ * Provider capabilities from config (`llm.providers.<id>.capabilities`).
55
+ *
56
+ * Task #4403: this is the data path that was missing. `resolvePiModel()` used
57
+ * to hardcode `reasoning: false` for every custom provider, so
58
+ * `capabilities.reasoning` in config never influenced the pi-ai model
59
+ * descriptor at all. Threading it through here lets the descriptor reflect
60
+ * what the user actually configured.
61
+ */
62
+ capabilities?: ProviderCapabilities;
43
63
  }
44
64
  /** Chat 响应(统一形状) */
45
65
  export interface ChatResponse {
@@ -14,7 +14,7 @@ import {
14
14
  createLLMAdapter,
15
15
  getLLMProviderOverride,
16
16
  setLLMProvider
17
- } from "../../shared/@ai-setting/roy-agent-core-p8ak03s0.js";
17
+ } from "../../shared/@ai-setting/roy-agent-core-w527pcpe.js";
18
18
  import"../../shared/@ai-setting/roy-agent-core-adg9ghbt.js";
19
19
  import"../../shared/@ai-setting/roy-agent-core-zjdzk1gg.js";
20
20
  import {
@@ -28,7 +28,7 @@ import {
28
28
  isOpencodeEndpoint,
29
29
  isOpencodeProvider,
30
30
  parseModelString
31
- } from "../../shared/@ai-setting/roy-agent-core-zy3hqfwg.js";
31
+ } from "../../shared/@ai-setting/roy-agent-core-0hq21px2.js";
32
32
  import"../../shared/@ai-setting/roy-agent-core-ctdhjv68.js";
33
33
  import"../../shared/@ai-setting/roy-agent-core-7hh0brvs.js";
34
34
  import"../../shared/@ai-setting/roy-agent-core-tdbv7ff4.js";