@migor/agentia 0.7.2 → 0.8.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.
Files changed (82) hide show
  1. package/CHANGELOG.md +106 -1
  2. package/README.md +10 -6
  3. package/dist/AGENTS.md +55 -9
  4. package/dist/core/trace.d.ts +25 -0
  5. package/dist/core/trace.js +31 -0
  6. package/dist/core/trace.js.map +1 -1
  7. package/dist/engine/loop-result.d.ts +72 -0
  8. package/dist/engine/loop-result.js +80 -0
  9. package/dist/engine/loop-result.js.map +1 -0
  10. package/dist/engine/loop.d.ts +3 -40
  11. package/dist/engine/loop.js +37 -127
  12. package/dist/engine/loop.js.map +1 -1
  13. package/dist/engine/resume-input.d.ts +26 -0
  14. package/dist/engine/resume-input.js +39 -0
  15. package/dist/engine/resume-input.js.map +1 -0
  16. package/dist/engine/retry.d.ts +8 -0
  17. package/dist/engine/retry.js +10 -0
  18. package/dist/engine/retry.js.map +1 -1
  19. package/dist/engine/run-config.d.ts +28 -0
  20. package/dist/engine/run-config.js +75 -0
  21. package/dist/engine/run-config.js.map +1 -0
  22. package/dist/engine/span-scope.d.ts +48 -0
  23. package/dist/engine/span-scope.js +31 -0
  24. package/dist/engine/span-scope.js.map +1 -0
  25. package/dist/engine/stop-reason.d.ts +27 -0
  26. package/dist/engine/stop-reason.js +76 -0
  27. package/dist/engine/stop-reason.js.map +1 -0
  28. package/dist/engine/text.d.ts +10 -0
  29. package/dist/engine/text.js +6 -0
  30. package/dist/engine/text.js.map +1 -0
  31. package/dist/engine/tool-context.d.ts +32 -0
  32. package/dist/engine/tool-context.js +18 -0
  33. package/dist/engine/tool-context.js.map +1 -0
  34. package/dist/engine/tool-events.d.ts +42 -0
  35. package/dist/engine/tool-events.js +64 -0
  36. package/dist/engine/tool-events.js.map +1 -0
  37. package/dist/engine/turn-request.d.ts +24 -0
  38. package/dist/engine/turn-request.js +12 -0
  39. package/dist/engine/turn-request.js.map +1 -0
  40. package/dist/engine/turn.d.ts +4 -27
  41. package/dist/engine/turn.js +48 -158
  42. package/dist/engine/turn.js.map +1 -1
  43. package/dist/index.d.ts +4 -2
  44. package/dist/index.js +6 -2
  45. package/dist/index.js.map +1 -1
  46. package/dist/integrations/otlp.d.ts +1 -1
  47. package/dist/integrations/otlp.js +13 -15
  48. package/dist/integrations/otlp.js.map +1 -1
  49. package/dist/toolkit/skill.js +4 -1
  50. package/dist/toolkit/skill.js.map +1 -1
  51. package/dist/toolkit/subagent.js +6 -3
  52. package/dist/toolkit/subagent.js.map +1 -1
  53. package/dist/toolkit/tool.js +1 -0
  54. package/dist/toolkit/tool.js.map +1 -1
  55. package/dist/transport/approval-policy.d.ts +30 -0
  56. package/dist/transport/approval-policy.js +37 -0
  57. package/dist/transport/approval-policy.js.map +1 -0
  58. package/dist/transport/async.d.ts +0 -4
  59. package/dist/transport/async.js +40 -144
  60. package/dist/transport/async.js.map +1 -1
  61. package/dist/transport/drain-gate.d.ts +28 -0
  62. package/dist/transport/drain-gate.js +62 -0
  63. package/dist/transport/drain-gate.js.map +1 -0
  64. package/dist/transport/http-route.d.ts +55 -0
  65. package/dist/transport/http-route.js +72 -0
  66. package/dist/transport/http-route.js.map +1 -0
  67. package/dist/transport/http-shapes.d.ts +57 -0
  68. package/dist/transport/http-shapes.js +56 -0
  69. package/dist/transport/http-shapes.js.map +1 -0
  70. package/dist/transport/http.d.ts +5 -21
  71. package/dist/transport/http.js +176 -232
  72. package/dist/transport/http.js.map +1 -1
  73. package/dist/transport/resume-policy.d.ts +40 -0
  74. package/dist/transport/resume-policy.js +25 -0
  75. package/dist/transport/resume-policy.js.map +1 -0
  76. package/dist/transport/slot-pool.d.ts +24 -0
  77. package/dist/transport/slot-pool.js +43 -0
  78. package/dist/transport/slot-pool.js.map +1 -0
  79. package/dist/transport/task-waiters.d.ts +25 -0
  80. package/dist/transport/task-waiters.js +63 -0
  81. package/dist/transport/task-waiters.js.map +1 -0
  82. package/package.json +4 -3
@@ -0,0 +1,76 @@
1
+ import { textOf } from './text.js';
2
+ /**
3
+ * —— 终止/边界分支(每个都给出终止结论,退出循环不再兜底改判)——
4
+ * 五种已知 stop_reason 各有归宿;剩余形态按「有没有可执行块」区分:
5
+ * 畸形 tool_use(块为空)与框架不认识的 stop_reason。
6
+ */
7
+ export function resolveStopReason(message, maxTokens) {
8
+ if (message.stop_reason === 'end_turn') {
9
+ return { kind: 'finish', stopReason: 'end_turn', finalText: textOf(message) };
10
+ }
11
+ if (message.stop_reason === 'refusal') {
12
+ return {
13
+ kind: 'finish',
14
+ stopReason: 'refusal',
15
+ finalText: textOf(message),
16
+ error: { type: 'refusal', message: 'model refused the request', retryable: false },
17
+ };
18
+ }
19
+ if (message.stop_reason === 'max_tokens') {
20
+ // 与 budget_exceeded / refusal 同口径:非正常收尾都带结构化 error(进 run 根 span),
21
+ // 否则 trace 里这类 run「失败却没有原因」
22
+ return {
23
+ kind: 'finish',
24
+ stopReason: 'max_tokens',
25
+ finalText: textOf(message),
26
+ error: {
27
+ type: 'max_tokens',
28
+ message: `模型输出触顶被截断(max_tokens=${maxTokens})`,
29
+ retryable: false,
30
+ },
31
+ };
32
+ }
33
+ if (message.stop_reason === 'pause_turn') {
34
+ // 无 server tools 时正常不会到;避免无限循环直接停
35
+ return {
36
+ kind: 'finish',
37
+ stopReason: 'pause_turn',
38
+ finalText: textOf(message),
39
+ error: {
40
+ type: 'pause_turn',
41
+ message: '模型返回 pause_turn(无 server tools 的场景不应出现),防死循环直接收尾',
42
+ retryable: false,
43
+ },
44
+ };
45
+ }
46
+ if (message.stop_reason === 'stop_sequence') {
47
+ // 命中 stop 序列 = 正常收尾(与 end_turn 同类),不是失败
48
+ return { kind: 'finish', stopReason: 'stop_sequence', finalText: textOf(message) };
49
+ }
50
+ const toolUses = message.content.filter((b) => b.type === 'tool_use');
51
+ if (toolUses.length === 0) {
52
+ // 到这里的剩余 stop_reason 不会产生可执行块,防死循环直接停:
53
+ // 'tool_use' 但块为空(畸形响应)与「本框架不认识的 stop_reason」区分开。
54
+ //
55
+ // **两种都属非正常收尾,都必须挂结构化 error** —— engine/loop.ts 的不变量是
56
+ // 「非正常收尾都带结构化 error」(与 budget_exceeded / refusal / max_iterations 同口径)。
57
+ // 此前只有 unknown_stop_reason 带 error,tool_use_no_blocks 不带:run 以
58
+ // status:'failed' 收尾、result.error 却是 undefined,HTTP body 与任务记录里
59
+ // 看不出「为什么失败」,只能看到一句 stopReason 字符串。
60
+ const stopReason = message.stop_reason === 'tool_use' ? 'tool_use_no_blocks' : 'unknown_stop_reason';
61
+ return {
62
+ kind: 'finish',
63
+ stopReason,
64
+ finalText: textOf(message),
65
+ error: {
66
+ type: 'agent_error',
67
+ message: stopReason === 'tool_use_no_blocks'
68
+ ? '模型以 stop_reason=tool_use 收尾,但响应里没有任何 tool_use 块(畸形响应)'
69
+ : `模型返回了未识别的 stop_reason: ${String(message.stop_reason)}`,
70
+ retryable: false,
71
+ },
72
+ };
73
+ }
74
+ return { kind: 'tools', toolUses };
75
+ }
76
+ //# sourceMappingURL=stop-reason.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stop-reason.js","sourceRoot":"","sources":["../../src/engine/stop-reason.ts"],"names":[],"mappings":"AAYA,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAMnC;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,OAAgB,EAAE,SAAiB;IACnE,IAAI,OAAO,CAAC,WAAW,KAAK,UAAU,EAAE,CAAC;QACvC,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;IAChF,CAAC;IACD,IAAI,OAAO,CAAC,WAAW,KAAK,SAAS,EAAE,CAAC;QACtC,OAAO;YACL,IAAI,EAAE,QAAQ;YACd,UAAU,EAAE,SAAS;YACrB,SAAS,EAAE,MAAM,CAAC,OAAO,CAAC;YAC1B,KAAK,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,2BAA2B,EAAE,SAAS,EAAE,KAAK,EAAE;SACnF,CAAC;IACJ,CAAC;IACD,IAAI,OAAO,CAAC,WAAW,KAAK,YAAY,EAAE,CAAC;QACzC,kEAAkE;QAClE,4BAA4B;QAC5B,OAAO;YACL,IAAI,EAAE,QAAQ;YACd,UAAU,EAAE,YAAY;YACxB,SAAS,EAAE,MAAM,CAAC,OAAO,CAAC;YAC1B,KAAK,EAAE;gBACL,IAAI,EAAE,YAAY;gBAClB,OAAO,EAAE,wBAAwB,SAAS,GAAG;gBAC7C,SAAS,EAAE,KAAK;aACjB;SACF,CAAC;IACJ,CAAC;IACD,IAAI,OAAO,CAAC,WAAW,KAAK,YAAY,EAAE,CAAC;QACzC,kCAAkC;QAClC,OAAO;YACL,IAAI,EAAE,QAAQ;YACd,UAAU,EAAE,YAAY;YACxB,SAAS,EAAE,MAAM,CAAC,OAAO,CAAC;YAC1B,KAAK,EAAE;gBACL,IAAI,EAAE,YAAY;gBAClB,OAAO,EAAE,kDAAkD;gBAC3D,SAAS,EAAE,KAAK;aACjB;SACF,CAAC;IACJ,CAAC;IACD,IAAI,OAAO,CAAC,WAAW,KAAK,eAAe,EAAE,CAAC;QAC5C,wCAAwC;QACxC,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;IACrF,CAAC;IAED,MAAM,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAqB,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,UAAU,CAAC,CAAC;IACzF,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1B,uCAAuC;QACvC,kDAAkD;QAClD,EAAE;QACF,sDAAsD;QACtD,wEAAwE;QACxE,+DAA+D;QAC/D,gEAAgE;QAChE,oCAAoC;QACpC,MAAM,UAAU,GACd,OAAO,CAAC,WAAW,KAAK,UAAU,CAAC,CAAC,CAAC,oBAAoB,CAAC,CAAC,CAAC,qBAAqB,CAAC;QACpF,OAAO;YACL,IAAI,EAAE,QAAQ;YACd,UAAU;YACV,SAAS,EAAE,MAAM,CAAC,OAAO,CAAC;YAC1B,KAAK,EAAE;gBACL,IAAI,EAAE,aAAa;gBACnB,OAAO,EACL,UAAU,KAAK,oBAAoB;oBACjC,CAAC,CAAC,uDAAuD;oBACzD,CAAC,CAAC,0BAA0B,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE;gBAC7D,SAAS,EAAE,KAAK;aACjB;SACF,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC;AACrC,CAAC"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * 引擎侧的文本读取口径(**单源**):多文本块用 `\n` 连接。
3
+ *
4
+ * 从 turn.ts 外移(2026-09-20,turn 拆分第一步):`stop-reason.ts` 也要用这个口径,而它若
5
+ * 继续从 turn.ts 取、turn.ts 又要 import stop-reason.ts ⇒ **成环**(分层守卫禁环)。这一层很薄,
6
+ * 但「引擎用 `\n`、适配器用 `''`」这个选择必须只有一处 —— 见 core/text.ts 头注。
7
+ */
8
+ import type { Message } from '../core/message.js';
9
+ /** 取消息里的全部文本块(引擎口径:多块按 `\n` 连接,保住模型的分段输出) */
10
+ export declare function textOf(message: Message): string;
@@ -0,0 +1,6 @@
1
+ import { textOf as coreTextOf } from '../core/text.js';
2
+ /** 取消息里的全部文本块(引擎口径:多块按 `\n` 连接,保住模型的分段输出) */
3
+ export function textOf(message) {
4
+ return coreTextOf(message, '\n');
5
+ }
6
+ //# sourceMappingURL=text.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"text.js","sourceRoot":"","sources":["../../src/engine/text.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAEvD,6CAA6C;AAC7C,MAAM,UAAU,MAAM,CAAC,OAAgB;IACrC,OAAO,UAAU,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;AACnC,CAAC"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * 单工具执行的**上下文装配**(纯):把引擎侧的取值摊成交给工具的 `ToolRunContext`。
3
+ *
4
+ * 从 turn.ts 的 executeOneTool 里外移(2026-09-20,turn 拆分第三步)。这段全部是搬运,但它
5
+ * **不是无害的样板**:八处条件展开里每一处都对应一个「漏了就静默降级」的守卫 ——
6
+ * 价格覆盖漏了 ⇒ 子 agent 用同一模型退化成「未定价」、`maxCostUsd` 静默失效;预算/超时上限
7
+ * 漏了 ⇒ 护栏在子循环里离线;`abandoned` 漏了 ⇒ 超时后没人通知工具「别等了」。
8
+ *
9
+ * 判定用哪一种是**语义**,不是风格,单测逐项钉住:
10
+ * - `signal` / `priceOverrides` / `onUnpricedModel` 用**真值**判定;
11
+ * - `maxEventChars` / `maxTotalTokens` / `maxCostUsd` / `toolTimeoutMs` 用**非空**判定 ——
12
+ * 因为 `false`(不截断)与 `0`(不限/不超时)都是**有意义的值**,真值判定会把它们吃掉;
13
+ * - `approval` 用 `!== undefined`:批/拒的决定要原样带给工具体(审计与分级授权用)。
14
+ *
15
+ * 纯的边界:只做取值搬运,不读 ctx、不碰 recorder、不执行工具。
16
+ */
17
+ import type { SpanId } from '../core/trace.js';
18
+ import type { RecorderBackend, ToolRunContext } from '../core/tool.js';
19
+ export declare function buildToolRunContext(opts: {
20
+ client: ToolRunContext['client'];
21
+ recorder: RecorderBackend;
22
+ parentSpanId: SpanId;
23
+ abandoned: AbortSignal;
24
+ signal?: ToolRunContext['signal'];
25
+ priceOverrides?: ToolRunContext['priceOverrides'];
26
+ onUnpricedModel?: ToolRunContext['onUnpricedModel'];
27
+ maxEventChars?: ToolRunContext['maxEventChars'];
28
+ maxTotalTokens?: ToolRunContext['maxTotalTokens'];
29
+ maxCostUsd?: ToolRunContext['maxCostUsd'];
30
+ toolTimeoutMs?: ToolRunContext['toolTimeoutMs'];
31
+ approval?: ToolRunContext['approval'];
32
+ }): ToolRunContext;
@@ -0,0 +1,18 @@
1
+ export function buildToolRunContext(opts) {
2
+ const { client, recorder, parentSpanId, abandoned, signal, priceOverrides, onUnpricedModel, maxEventChars, maxTotalTokens, maxCostUsd, toolTimeoutMs, approval, } = opts;
3
+ return {
4
+ client,
5
+ recorder,
6
+ parentSpanId,
7
+ abandoned,
8
+ ...(signal ? { signal } : {}),
9
+ ...(priceOverrides ? { priceOverrides } : {}),
10
+ ...(onUnpricedModel ? { onUnpricedModel } : {}),
11
+ ...(maxEventChars != null ? { maxEventChars } : {}),
12
+ ...(maxTotalTokens != null ? { maxTotalTokens } : {}),
13
+ ...(maxCostUsd != null ? { maxCostUsd } : {}),
14
+ ...(toolTimeoutMs != null ? { toolTimeoutMs } : {}),
15
+ ...(approval !== undefined ? { approval } : {}),
16
+ };
17
+ }
18
+ //# sourceMappingURL=tool-context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-context.js","sourceRoot":"","sources":["../../src/engine/tool-context.ts"],"names":[],"mappings":"AAmBA,MAAM,UAAU,mBAAmB,CAAC,IAanC;IACC,MAAM,EACJ,MAAM,EACN,QAAQ,EACR,YAAY,EACZ,SAAS,EACT,MAAM,EACN,cAAc,EACd,eAAe,EACf,aAAa,EACb,cAAc,EACd,UAAU,EACV,aAAa,EACb,QAAQ,GACT,GAAG,IAAI,CAAC;IACT,OAAO;QACL,MAAM;QACN,QAAQ;QACR,YAAY;QACZ,SAAS;QACT,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC7B,GAAG,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC7C,GAAG,CAAC,eAAe,CAAC,CAAC,CAAC,EAAE,eAAe,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC/C,GAAG,CAAC,aAAa,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACnD,GAAG,CAAC,cAAc,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACrD,GAAG,CAAC,UAAU,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC7C,GAAG,CAAC,aAAa,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACnD,GAAG,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAChD,CAAC;AACJ,CAAC"}
@@ -0,0 +1,42 @@
1
+ import type { ToolResultBlockParam, ToolUseBlock } from '../core/message.js';
2
+ /**
3
+ * 事件正文(tool.input / tool.output 的 body)缺省截断上限(字符)。
4
+ *
5
+ * 与「入参」/「成功出参」/「失败出参」三类一一对应:失败出参减半是为了让
6
+ * 「哪个工具老超时」这类判断在**一行**里看得完(正文本身多为一句错误摘要,
7
+ * 1000 已远超常见长度,只有工具把异常里的长上下文一起吐回来时才会触发)。
8
+ */
9
+ export declare const DEFAULT_EVENT_CHARS = 2000;
10
+ export declare const DEFAULT_ERROR_EVENT_CHARS = 1000;
11
+ /** 工具失败归类(进 `tool.output` 事件)。`denied` 是**人**的决定,不是工具故障,故单列一类账 */
12
+ export type ToolErrorKind = 'invalid_input' | 'timeout' | 'threw' | 'unknown_tool' | 'denied';
13
+ export interface ToolInputPayload {
14
+ tool: string;
15
+ tool_use_id: string;
16
+ input: string;
17
+ }
18
+ /** `tool.input` 事件体:入参按 `maxEventChars`(缺省 2000)截断 */
19
+ export declare function toolInputPayload(use: ToolUseBlock, maxEventChars?: number | false): ToolInputPayload;
20
+ export interface ToolOutputPayload {
21
+ tool: string;
22
+ tool_use_id: string;
23
+ ok: boolean;
24
+ durationMs: number;
25
+ errorKind?: ToolErrorKind;
26
+ content: string;
27
+ }
28
+ /**
29
+ * `tool.output` 事件体:耗时四路径都记、失败出参用**更小**的截断上限、`errorKind` 有值才在场。
30
+ * `now` 由调用点传入(便于单测固定时钟;时钟回拨时 durationMs 钳到 0)。
31
+ */
32
+ export declare function toolOutputPayload(opts: {
33
+ use: ToolUseBlock;
34
+ ok: boolean;
35
+ errorKind?: ToolErrorKind | undefined;
36
+ startedAt: number;
37
+ now: number;
38
+ content: unknown;
39
+ maxEventChars?: number | false | undefined;
40
+ }): ToolOutputPayload;
41
+ /** 回给模型的 tool_result 块:`is_error` 与 `ok` 互为反,content 一律走 stringifySafe */
42
+ export declare function toolResultBlock(use: ToolUseBlock, ok: boolean, content: unknown): ToolResultBlockParam;
@@ -0,0 +1,64 @@
1
+ /**
2
+ * 单工具执行的**记账面**(纯):`tool.input` / `tool.output` 两个事件体 + 回给模型的 tool_result 块。
3
+ *
4
+ * 从 turn.ts 的 executeOneTool 里外移(2026-09-20,turn 拆分第四步)。这里的每一条都是**口径**,
5
+ * 混在执行流程里只能透过 trace 的观感去猜:
6
+ * - **截断上限不对称**:失败出参用更小的上限(1000 vs 2000)—— 理由见常量注释;
7
+ * - **`maxEventChars: false` = 不截断**(调试期开全文),必须原样透传而不是回落默认;
8
+ * - **`durationMs` 覆盖四条路径**(成功 / 失败 / 超时 / 入参被拒)且**非负** —— 时钟回拨时不出现负数;
9
+ * - **`errorKind` 只在有值时在场**:成功事件不该带一个 `undefined` 的键。
10
+ *
11
+ * 纯的边界:只拼装数据,不读 ctx、不碰 recorder、不执行工具。
12
+ */
13
+ import { stringifySafe, truncateWithMark } from '../core/json.js';
14
+ /**
15
+ * 事件正文(tool.input / tool.output 的 body)缺省截断上限(字符)。
16
+ *
17
+ * 与「入参」/「成功出参」/「失败出参」三类一一对应:失败出参减半是为了让
18
+ * 「哪个工具老超时」这类判断在**一行**里看得完(正文本身多为一句错误摘要,
19
+ * 1000 已远超常见长度,只有工具把异常里的长上下文一起吐回来时才会触发)。
20
+ */
21
+ export const DEFAULT_EVENT_CHARS = 2_000;
22
+ export const DEFAULT_ERROR_EVENT_CHARS = 1_000;
23
+ /**
24
+ * 截断到上限字符,超长加省略标记(标记格式由 core/json.ts 的 truncateWithMark 单一提供)。
25
+ * `n === false` 表示**不截断**(`RunAgentOptions.maxEventChars: false`)——
26
+ * 传数字时三类事件共用同一个上限,缺省值由调用点给出。
27
+ */
28
+ function limit(x, n) {
29
+ const s = stringifySafe(x);
30
+ return n === false ? s : truncateWithMark(s, n);
31
+ }
32
+ /** `tool.input` 事件体:入参按 `maxEventChars`(缺省 2000)截断 */
33
+ export function toolInputPayload(use, maxEventChars) {
34
+ return {
35
+ tool: use.name,
36
+ tool_use_id: use.id,
37
+ input: limit(use.input, maxEventChars ?? DEFAULT_EVENT_CHARS),
38
+ };
39
+ }
40
+ /**
41
+ * `tool.output` 事件体:耗时四路径都记、失败出参用**更小**的截断上限、`errorKind` 有值才在场。
42
+ * `now` 由调用点传入(便于单测固定时钟;时钟回拨时 durationMs 钳到 0)。
43
+ */
44
+ export function toolOutputPayload(opts) {
45
+ const { use, ok, errorKind, startedAt, now, content, maxEventChars } = opts;
46
+ return {
47
+ tool: use.name,
48
+ tool_use_id: use.id,
49
+ ok,
50
+ durationMs: Math.max(0, now - startedAt),
51
+ ...(errorKind ? { errorKind } : {}),
52
+ content: limit(content, maxEventChars ?? (ok ? DEFAULT_EVENT_CHARS : DEFAULT_ERROR_EVENT_CHARS)),
53
+ };
54
+ }
55
+ /** 回给模型的 tool_result 块:`is_error` 与 `ok` 互为反,content 一律走 stringifySafe */
56
+ export function toolResultBlock(use, ok, content) {
57
+ return {
58
+ type: 'tool_result',
59
+ tool_use_id: use.id,
60
+ content: stringifySafe(content),
61
+ is_error: !ok,
62
+ };
63
+ }
64
+ //# sourceMappingURL=tool-events.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-events.js","sourceRoot":"","sources":["../../src/engine/tool-events.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAGlE;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,KAAK,CAAC;AACzC,MAAM,CAAC,MAAM,yBAAyB,GAAG,KAAK,CAAC;AAK/C;;;;GAIG;AACH,SAAS,KAAK,CAAC,CAAU,EAAE,CAAiB;IAC1C,MAAM,CAAC,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC;IAC3B,OAAO,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AAClD,CAAC;AAQD,sDAAsD;AACtD,MAAM,UAAU,gBAAgB,CAC9B,GAAiB,EACjB,aAA8B;IAE9B,OAAO;QACL,IAAI,EAAE,GAAG,CAAC,IAAI;QACd,WAAW,EAAE,GAAG,CAAC,EAAE;QACnB,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,aAAa,IAAI,mBAAmB,CAAC;KAC9D,CAAC;AACJ,CAAC;AAWD;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAQjC;IACC,MAAM,EAAE,GAAG,EAAE,EAAE,EAAE,SAAS,EAAE,SAAS,EAAE,GAAG,EAAE,OAAO,EAAE,aAAa,EAAE,GAAG,IAAI,CAAC;IAC5E,OAAO;QACL,IAAI,EAAE,GAAG,CAAC,IAAI;QACd,WAAW,EAAE,GAAG,CAAC,EAAE;QACnB,EAAE;QACF,UAAU,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,GAAG,SAAS,CAAC;QACxC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACnC,OAAO,EAAE,KAAK,CACZ,OAAO,EACP,aAAa,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC,yBAAyB,CAAC,CACxE;KACF,CAAC;AACJ,CAAC;AAED,0EAA0E;AAC1E,MAAM,UAAU,eAAe,CAC7B,GAAiB,EACjB,EAAW,EACX,OAAgB;IAEhB,OAAO;QACL,IAAI,EAAE,aAAa;QACnB,WAAW,EAAE,GAAG,CAAC,EAAE;QACnB,OAAO,EAAE,aAAa,CAAC,OAAO,CAAC;QAC/B,QAAQ,EAAE,CAAC,EAAE;KACd,CAAC;AACJ,CAAC"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * 回合请求的**装配**(纯):把引擎侧的取值摊成客户端 `messages.stream` 的入参。
3
+ *
4
+ * 从 turn.ts 的 streamTurn 里外移(2026-09-20,turn 拆分第二步)。这里没有一行逻辑算"难",
5
+ * 但三处**条件展开**是容易写错的地方 —— **键在不在场是语义**:
6
+ * - `system` / `tools` / `signal` 缺省时**不带该键**(不是传 `undefined`、更不是空数组):
7
+ * 适配器按「键是否在场」决定是否写进请求体,带一个空数组会被某些端点读成「声明了零个工具」;
8
+ * - `system: ''` 这种空值同样不带键(`!system` 判定)—— 与抽取前的行内写法逐字一致。
9
+ *
10
+ * 纯的边界:只做取值搬运,不读 ctx、不碰 recorder、不认识重试。
11
+ */
12
+ import type { MessageParam, ToolParam } from '../core/message.js';
13
+ import type { ModelClient } from '../core/tool.js';
14
+ import type { SystemParam } from './types.js';
15
+ type StreamParams = Parameters<ModelClient['messages']['stream']>[0];
16
+ export declare function buildTurnRequest(opts: {
17
+ model: string;
18
+ maxTokens: number;
19
+ system?: SystemParam | undefined;
20
+ apiTools: ToolParam[];
21
+ messages: MessageParam[];
22
+ signal?: AbortSignal | undefined;
23
+ }): StreamParams;
24
+ export {};
@@ -0,0 +1,12 @@
1
+ export function buildTurnRequest(opts) {
2
+ const { model, maxTokens, system, apiTools, messages, signal } = opts;
3
+ return {
4
+ model,
5
+ max_tokens: maxTokens,
6
+ ...(system ? { system } : {}),
7
+ ...(apiTools.length ? { tools: apiTools } : {}),
8
+ messages,
9
+ ...(signal ? { signal } : {}),
10
+ };
11
+ }
12
+ //# sourceMappingURL=turn-request.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"turn-request.js","sourceRoot":"","sources":["../../src/engine/turn-request.ts"],"names":[],"mappings":"AAiBA,MAAM,UAAU,gBAAgB,CAAC,IAOhC;IACC,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IACtE,OAAO;QACL,KAAK;QACL,UAAU,EAAE,SAAS;QACrB,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC7B,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC/C,QAAQ;QACR,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC9B,CAAC;AACJ,CAAC"}
@@ -4,11 +4,12 @@
4
4
  * 分工:loop.ts 留入口与编排骨架(runAgent / runAgentScoped / agentLoop 本体 +
5
5
  * 缺省旋钮解析);本文件装回合内部 —— 回合上下文装配(buildLoopContext)、
6
6
  * 回合入口检查(checkTurnEntry)、流式请求与重试(streamTurn)、回合记账
7
- * (recordTurnUsage)、stop_reason 分流(resolveStopReason)、工具执行
7
+ * (recordTurnUsage)、stop_reason 分流(已外移到 stop-reason.ts)、工具执行
8
8
  * (executeTurnTools / executeOneTool)。
9
9
  * 依赖方向单向:loop.ts → turn.ts(分层守卫禁环);两侧共用的类型与 helper
10
- * (AgentLoopArgs / textOf / replaceMessages)在本文件做 module 级 export,供 loop.ts
11
- * 与测试 import —— 按仓库约定不进 src/index.ts(纯内部实现细节)。
10
+ * (AgentLoopArgs / replaceMessages)在本文件做 module 级 export,供 loop.ts
11
+ * 与测试 import —— 按仓库约定不进 src/index.ts(纯内部实现细节)。textOf 的
12
+ * 规范位置是 text.ts(loop.ts 与 stop-reason.ts 都从那儿取;回本文件取会成环)。
12
13
  */
13
14
  import type { Message, MessageParam, ToolParam, ToolResultBlockParam, ToolUseBlock } from '../core/message.js';
14
15
  import type { AgentTool, ApprovalDecision, JsonSchema, ModelClient, ModelPricing, RecorderBackend, SchemaType } from '../core/tool.js';
@@ -127,21 +128,6 @@ export declare function streamTurn<S extends JsonSchema>(ctx: LoopContext<S>): P
127
128
  */
128
129
  export declare function recordTurnUsage<S extends JsonSchema>(ctx: LoopContext<S>, turnId: SpanId, message: Message): void;
129
130
  /** 回合收尾分流:finish = 终止/边界分支(带 stopReason 与收尾文本);tools = 还有工具要执行 */
130
- export type StopResolution = {
131
- kind: 'finish';
132
- stopReason: AgentStopReason;
133
- finalText: string;
134
- error?: SpanError;
135
- } | {
136
- kind: 'tools';
137
- toolUses: ToolUseBlock[];
138
- };
139
- /**
140
- * —— 终止/边界分支(每个都给出终止结论,退出循环不再兜底改判)——
141
- * 五种已知 stop_reason 各有归宿;剩余形态按「有没有可执行块」区分:
142
- * 畸形 tool_use(块为空)与框架不认识的 stop_reason。
143
- */
144
- export declare function resolveStopReason(message: Message, maxTokens: number): StopResolution;
145
131
  /**
146
132
  * 回合工具执行的结果:
147
133
  * - `executed`:全部执行完(results 与输入一一对应,顺序保持);
@@ -168,15 +154,6 @@ export type TurnToolsOutcome = {
168
154
  * 永不需审批)与未知工具(走既有 unknown_tool 路径)。
169
155
  */
170
156
  export declare function executeTurnTools<S extends JsonSchema>(ctx: LoopContext<S>, turnId: SpanId, toolUses: ToolUseBlock[], overBudget: 'tokens' | 'cost' | null): Promise<TurnToolsOutcome>;
171
- /**
172
- * 取消息里的全部文本块(引擎侧口径:**多块按 `\n` 连接** —— 多文本块是模型分段的
173
- * 输出,拼成 `finalText` 要保住分段)。
174
- *
175
- * 实现单源在 `core/text.ts`(2026-09-17 去重):两个适配器的「非流式回落路径」各有
176
- * 一份逐字相同的实现,只差连接符。这里保留同名包装,是为了让 loop.ts 与测试既有的
177
- * 大量调用点零改动,同时把「引擎用 `\n`」这个选择钉在一处。
178
- */
179
- export declare function textOf(message: Message): string;
180
157
  /**
181
158
  * 用 `next` 原地替换 `target` 的全部内容(保持数组引用不变 —— 循环各处持同一数组)。
182
159
  *