@hybridaione/hybridclaw 0.2.3 → 0.2.7

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 (285) hide show
  1. package/.github/workflows/ci.yml +70 -0
  2. package/.husky/pre-commit +1 -0
  3. package/CHANGELOG.md +95 -0
  4. package/CONTRIBUTING.md +33 -0
  5. package/README.md +57 -19
  6. package/SECURITY.md +17 -0
  7. package/biome.json +35 -0
  8. package/config.example.json +72 -7
  9. package/container/package-lock.json +2 -2
  10. package/container/package.json +1 -1
  11. package/container/src/approval-policy.ts +1405 -0
  12. package/container/src/browser-tools.ts +672 -136
  13. package/container/src/extensions.ts +36 -12
  14. package/container/src/hybridai-client.ts +65 -16
  15. package/container/src/index.ts +463 -110
  16. package/container/src/ipc.ts +5 -3
  17. package/container/src/token-usage.ts +107 -18
  18. package/container/src/tools.ts +600 -225
  19. package/container/src/types.ts +49 -2
  20. package/container/src/web-fetch.ts +89 -32
  21. package/dist/agent.d.ts.map +1 -1
  22. package/dist/agent.js +15 -4
  23. package/dist/agent.js.map +1 -1
  24. package/dist/audit-cli.d.ts.map +1 -1
  25. package/dist/audit-cli.js +4 -2
  26. package/dist/audit-cli.js.map +1 -1
  27. package/dist/audit-events.d.ts.map +1 -1
  28. package/dist/audit-events.js +53 -3
  29. package/dist/audit-events.js.map +1 -1
  30. package/dist/audit-trail.d.ts.map +1 -1
  31. package/dist/audit-trail.js +17 -8
  32. package/dist/audit-trail.js.map +1 -1
  33. package/dist/channels/discord/attachments.d.ts.map +1 -1
  34. package/dist/channels/discord/attachments.js +14 -7
  35. package/dist/channels/discord/attachments.js.map +1 -1
  36. package/dist/channels/discord/debounce.d.ts +9 -0
  37. package/dist/channels/discord/debounce.d.ts.map +1 -0
  38. package/dist/channels/discord/debounce.js +20 -0
  39. package/dist/channels/discord/debounce.js.map +1 -0
  40. package/dist/channels/discord/delivery.d.ts +4 -1
  41. package/dist/channels/discord/delivery.d.ts.map +1 -1
  42. package/dist/channels/discord/delivery.js +24 -4
  43. package/dist/channels/discord/delivery.js.map +1 -1
  44. package/dist/channels/discord/human-delay.d.ts +16 -0
  45. package/dist/channels/discord/human-delay.d.ts.map +1 -0
  46. package/dist/channels/discord/human-delay.js +29 -0
  47. package/dist/channels/discord/human-delay.js.map +1 -0
  48. package/dist/channels/discord/inbound.d.ts +31 -0
  49. package/dist/channels/discord/inbound.d.ts.map +1 -1
  50. package/dist/channels/discord/inbound.js +156 -6
  51. package/dist/channels/discord/inbound.js.map +1 -1
  52. package/dist/channels/discord/mentions.d.ts.map +1 -1
  53. package/dist/channels/discord/mentions.js +16 -4
  54. package/dist/channels/discord/mentions.js.map +1 -1
  55. package/dist/channels/discord/presence.d.ts +33 -0
  56. package/dist/channels/discord/presence.d.ts.map +1 -0
  57. package/dist/channels/discord/presence.js +111 -0
  58. package/dist/channels/discord/presence.js.map +1 -0
  59. package/dist/channels/discord/rate-limiter.d.ts +14 -0
  60. package/dist/channels/discord/rate-limiter.d.ts.map +1 -0
  61. package/dist/channels/discord/rate-limiter.js +49 -0
  62. package/dist/channels/discord/rate-limiter.js.map +1 -0
  63. package/dist/channels/discord/reactions.d.ts +38 -0
  64. package/dist/channels/discord/reactions.d.ts.map +1 -0
  65. package/dist/channels/discord/reactions.js +151 -0
  66. package/dist/channels/discord/reactions.js.map +1 -0
  67. package/dist/channels/discord/runtime.d.ts +7 -4
  68. package/dist/channels/discord/runtime.d.ts.map +1 -1
  69. package/dist/channels/discord/runtime.js +755 -150
  70. package/dist/channels/discord/runtime.js.map +1 -1
  71. package/dist/channels/discord/stream.d.ts +4 -1
  72. package/dist/channels/discord/stream.d.ts.map +1 -1
  73. package/dist/channels/discord/stream.js +19 -12
  74. package/dist/channels/discord/stream.js.map +1 -1
  75. package/dist/channels/discord/tool-actions.d.ts.map +1 -1
  76. package/dist/channels/discord/tool-actions.js +24 -12
  77. package/dist/channels/discord/tool-actions.js.map +1 -1
  78. package/dist/channels/discord/typing.d.ts +15 -0
  79. package/dist/channels/discord/typing.d.ts.map +1 -0
  80. package/dist/channels/discord/typing.js +106 -0
  81. package/dist/channels/discord/typing.js.map +1 -0
  82. package/dist/chunk.d.ts.map +1 -1
  83. package/dist/chunk.js +4 -2
  84. package/dist/chunk.js.map +1 -1
  85. package/dist/cli.js +77 -23
  86. package/dist/cli.js.map +1 -1
  87. package/dist/config.d.ts +24 -0
  88. package/dist/config.d.ts.map +1 -1
  89. package/dist/config.js +113 -18
  90. package/dist/config.js.map +1 -1
  91. package/dist/container-runner.d.ts.map +1 -1
  92. package/dist/container-runner.js +59 -26
  93. package/dist/container-runner.js.map +1 -1
  94. package/dist/container-setup.d.ts.map +1 -1
  95. package/dist/container-setup.js +10 -9
  96. package/dist/container-setup.js.map +1 -1
  97. package/dist/conversation.d.ts +2 -2
  98. package/dist/conversation.d.ts.map +1 -1
  99. package/dist/conversation.js +1 -1
  100. package/dist/conversation.js.map +1 -1
  101. package/dist/db.d.ts +118 -2
  102. package/dist/db.d.ts.map +1 -1
  103. package/dist/db.js +1568 -50
  104. package/dist/db.js.map +1 -1
  105. package/dist/delegation-manager.d.ts.map +1 -1
  106. package/dist/delegation-manager.js +3 -2
  107. package/dist/delegation-manager.js.map +1 -1
  108. package/dist/gateway-client.d.ts +2 -2
  109. package/dist/gateway-client.d.ts.map +1 -1
  110. package/dist/gateway-client.js +10 -4
  111. package/dist/gateway-client.js.map +1 -1
  112. package/dist/gateway-service.d.ts +3 -3
  113. package/dist/gateway-service.d.ts.map +1 -1
  114. package/dist/gateway-service.js +624 -87
  115. package/dist/gateway-service.js.map +1 -1
  116. package/dist/gateway-types.d.ts +24 -0
  117. package/dist/gateway-types.d.ts.map +1 -1
  118. package/dist/gateway-types.js.map +1 -1
  119. package/dist/gateway.js +303 -26
  120. package/dist/gateway.js.map +1 -1
  121. package/dist/health.d.ts.map +1 -1
  122. package/dist/health.js +20 -10
  123. package/dist/health.js.map +1 -1
  124. package/dist/heartbeat.d.ts +4 -0
  125. package/dist/heartbeat.d.ts.map +1 -1
  126. package/dist/heartbeat.js +62 -20
  127. package/dist/heartbeat.js.map +1 -1
  128. package/dist/hybridai-bots.d.ts.map +1 -1
  129. package/dist/hybridai-bots.js +4 -2
  130. package/dist/hybridai-bots.js.map +1 -1
  131. package/dist/hybridai-models.d.ts +8 -0
  132. package/dist/hybridai-models.d.ts.map +1 -0
  133. package/dist/hybridai-models.js +94 -0
  134. package/dist/hybridai-models.js.map +1 -0
  135. package/dist/instruction-approval-audit.d.ts.map +1 -1
  136. package/dist/instruction-approval-audit.js.map +1 -1
  137. package/dist/instruction-integrity.d.ts.map +1 -1
  138. package/dist/instruction-integrity.js +8 -2
  139. package/dist/instruction-integrity.js.map +1 -1
  140. package/dist/ipc.d.ts.map +1 -1
  141. package/dist/ipc.js +6 -1
  142. package/dist/ipc.js.map +1 -1
  143. package/dist/logger.js.map +1 -1
  144. package/dist/memory-consolidation.d.ts +17 -0
  145. package/dist/memory-consolidation.d.ts.map +1 -0
  146. package/dist/memory-consolidation.js +25 -0
  147. package/dist/memory-consolidation.js.map +1 -0
  148. package/dist/memory-service.d.ts +200 -0
  149. package/dist/memory-service.d.ts.map +1 -0
  150. package/dist/memory-service.js +294 -0
  151. package/dist/memory-service.js.map +1 -0
  152. package/dist/mount-security.d.ts.map +1 -1
  153. package/dist/mount-security.js +31 -7
  154. package/dist/mount-security.js.map +1 -1
  155. package/dist/observability-ingest.d.ts.map +1 -1
  156. package/dist/observability-ingest.js +32 -11
  157. package/dist/observability-ingest.js.map +1 -1
  158. package/dist/onboarding.d.ts.map +1 -1
  159. package/dist/onboarding.js +32 -9
  160. package/dist/onboarding.js.map +1 -1
  161. package/dist/proactive-policy.d.ts.map +1 -1
  162. package/dist/proactive-policy.js +2 -1
  163. package/dist/proactive-policy.js.map +1 -1
  164. package/dist/prompt-hooks.d.ts.map +1 -1
  165. package/dist/prompt-hooks.js +11 -7
  166. package/dist/prompt-hooks.js.map +1 -1
  167. package/dist/runtime-config.d.ts +104 -1
  168. package/dist/runtime-config.d.ts.map +1 -1
  169. package/dist/runtime-config.js +503 -24
  170. package/dist/runtime-config.js.map +1 -1
  171. package/dist/scheduled-task-runner.d.ts +1 -0
  172. package/dist/scheduled-task-runner.d.ts.map +1 -1
  173. package/dist/scheduled-task-runner.js +43 -10
  174. package/dist/scheduled-task-runner.js.map +1 -1
  175. package/dist/scheduler.d.ts +43 -4
  176. package/dist/scheduler.d.ts.map +1 -1
  177. package/dist/scheduler.js +530 -56
  178. package/dist/scheduler.js.map +1 -1
  179. package/dist/session-export.d.ts +26 -0
  180. package/dist/session-export.d.ts.map +1 -0
  181. package/dist/session-export.js +149 -0
  182. package/dist/session-export.js.map +1 -0
  183. package/dist/session-maintenance.d.ts.map +1 -1
  184. package/dist/session-maintenance.js +75 -13
  185. package/dist/session-maintenance.js.map +1 -1
  186. package/dist/session-transcripts.d.ts.map +1 -1
  187. package/dist/session-transcripts.js.map +1 -1
  188. package/dist/side-effects.d.ts.map +1 -1
  189. package/dist/side-effects.js +14 -2
  190. package/dist/side-effects.js.map +1 -1
  191. package/dist/skills-guard.d.ts.map +1 -1
  192. package/dist/skills-guard.js +893 -130
  193. package/dist/skills-guard.js.map +1 -1
  194. package/dist/skills.d.ts +5 -0
  195. package/dist/skills.d.ts.map +1 -1
  196. package/dist/skills.js +29 -15
  197. package/dist/skills.js.map +1 -1
  198. package/dist/token-efficiency.d.ts.map +1 -1
  199. package/dist/token-efficiency.js.map +1 -1
  200. package/dist/tui.js +92 -11
  201. package/dist/tui.js.map +1 -1
  202. package/dist/types.d.ts +150 -0
  203. package/dist/types.d.ts.map +1 -1
  204. package/dist/types.js +24 -1
  205. package/dist/types.js.map +1 -1
  206. package/dist/update.d.ts.map +1 -1
  207. package/dist/update.js +42 -14
  208. package/dist/update.js.map +1 -1
  209. package/dist/workspace.d.ts.map +1 -1
  210. package/dist/workspace.js +49 -9
  211. package/dist/workspace.js.map +1 -1
  212. package/docs/chat.html +9 -3
  213. package/docs/index.html +72 -13
  214. package/package.json +8 -2
  215. package/src/agent.ts +31 -4
  216. package/src/audit-cli.ts +44 -16
  217. package/src/audit-events.ts +69 -5
  218. package/src/audit-trail.ts +41 -15
  219. package/src/channels/discord/attachments.ts +81 -27
  220. package/src/channels/discord/debounce.ts +25 -0
  221. package/src/channels/discord/delivery.ts +65 -14
  222. package/src/channels/discord/human-delay.ts +48 -0
  223. package/src/channels/discord/inbound.ts +190 -8
  224. package/src/channels/discord/mentions.ts +42 -18
  225. package/src/channels/discord/presence.ts +148 -0
  226. package/src/channels/discord/rate-limiter.ts +58 -0
  227. package/src/channels/discord/reactions.ts +211 -0
  228. package/src/channels/discord/runtime.ts +1161 -205
  229. package/src/channels/discord/stream.ts +85 -31
  230. package/src/channels/discord/tool-actions.ts +78 -37
  231. package/src/channels/discord/typing.ts +140 -0
  232. package/src/chunk.ts +12 -4
  233. package/src/cli.ts +174 -57
  234. package/src/config.ts +208 -36
  235. package/src/container-runner.ts +134 -42
  236. package/src/container-setup.ts +57 -22
  237. package/src/conversation.ts +9 -7
  238. package/src/db.ts +2217 -84
  239. package/src/delegation-manager.ts +6 -2
  240. package/src/gateway-client.ts +41 -17
  241. package/src/gateway-service.ts +996 -220
  242. package/src/gateway-types.ts +33 -0
  243. package/src/gateway.ts +487 -52
  244. package/src/health.ts +66 -26
  245. package/src/heartbeat.ts +99 -22
  246. package/src/hybridai-bots.ts +14 -5
  247. package/src/hybridai-models.ts +158 -0
  248. package/src/instruction-approval-audit.ts +4 -1
  249. package/src/instruction-integrity.ts +30 -9
  250. package/src/ipc.ts +23 -5
  251. package/src/logger.ts +4 -1
  252. package/src/memory-consolidation.ts +41 -0
  253. package/src/memory-service.ts +606 -0
  254. package/src/mount-security.ts +58 -13
  255. package/src/observability-ingest.ts +134 -35
  256. package/src/onboarding.ts +126 -35
  257. package/src/proactive-policy.ts +3 -1
  258. package/src/prompt-hooks.ts +42 -17
  259. package/src/runtime-config.ts +1112 -122
  260. package/src/scheduled-task-runner.ts +77 -11
  261. package/src/scheduler.ts +683 -60
  262. package/src/session-export.ts +196 -0
  263. package/src/session-maintenance.ts +125 -22
  264. package/src/session-transcripts.ts +12 -3
  265. package/src/side-effects.ts +28 -5
  266. package/src/skills-guard.ts +1067 -219
  267. package/src/skills.ts +163 -65
  268. package/src/token-efficiency.ts +31 -9
  269. package/src/tui.ts +166 -25
  270. package/src/types.ts +199 -2
  271. package/src/update.ts +79 -23
  272. package/src/workspace.ts +63 -11
  273. package/tests/approval-policy.test.ts +335 -0
  274. package/tests/discord.basic.test.ts +213 -4
  275. package/tests/discord.human-presence.test.ts +85 -0
  276. package/tests/gateway-service.media-routing.test.ts +8 -2
  277. package/tests/hybridai-client.test.ts +112 -0
  278. package/tests/hybridai-models.test.ts +46 -0
  279. package/tests/memory-service.test.ts +1114 -0
  280. package/tests/token-efficiency.basic.test.ts +8 -2
  281. package/tests/token-usage.cache.test.ts +128 -0
  282. package/vitest.e2e.config.ts +3 -1
  283. package/vitest.integration.config.ts +3 -1
  284. package/vitest.live.config.ts +3 -1
  285. package/vitest.unit.config.ts +9 -0
@@ -16,14 +16,22 @@ interface RuntimeEventPayload {
16
16
  interface RuntimeExtension {
17
17
  name: string;
18
18
  onEvent?: (payload: RuntimeEventPayload) => void | Promise<void>;
19
- onBeforeToolCall?: (toolName: string, args: Record<string, unknown>) => string | null | Promise<string | null>;
20
- onAfterToolCall?: (toolName: string, args: Record<string, unknown>, result: string) => void | Promise<void>;
19
+ onBeforeToolCall?: (
20
+ toolName: string,
21
+ args: Record<string, unknown>,
22
+ ) => string | null | Promise<string | null>;
23
+ onAfterToolCall?: (
24
+ toolName: string,
25
+ args: Record<string, unknown>,
26
+ result: string,
27
+ ) => void | Promise<void>;
21
28
  }
22
29
 
23
30
  const DANGEROUS_FILE_CONTENT_PATTERNS: Array<{ re: RegExp; reason: string }> = [
24
31
  {
25
32
  re: /\brm\s+-rf\s+\/(\s|$)/i,
26
- reason: 'Detected destructive root delete pattern (`rm -rf /`) in file content.',
33
+ reason:
34
+ 'Detected destructive root delete pattern (`rm -rf /`) in file content.',
27
35
  },
28
36
  {
29
37
  re: /:\(\)\s*\{.*\};\s*:/i,
@@ -31,7 +39,8 @@ const DANGEROUS_FILE_CONTENT_PATTERNS: Array<{ re: RegExp; reason: string }> = [
31
39
  },
32
40
  {
33
41
  re: /\bcurl\b[^\n|]*\|\s*(sh|bash|zsh)\b/i,
34
- reason: 'Detected remote shell execution pattern (`curl | sh`) in file content.',
42
+ reason:
43
+ 'Detected remote shell execution pattern (`curl | sh`) in file content.',
35
44
  },
36
45
  ];
37
46
 
@@ -50,9 +59,10 @@ const securityHookExtension: RuntimeExtension = {
50
59
  name: 'security-hook',
51
60
  onBeforeToolCall: (toolName, args) => {
52
61
  if (toolName === 'write' || toolName === 'edit') {
53
- const content = toolName === 'write'
54
- ? String(args.contents || '')
55
- : String(args.new || '');
62
+ const content =
63
+ toolName === 'write'
64
+ ? String(args.contents || '')
65
+ : String(args.new || '');
56
66
  for (const pattern of DANGEROUS_FILE_CONTENT_PATTERNS) {
57
67
  if (pattern.re.test(content)) return pattern.reason;
58
68
  }
@@ -74,14 +84,17 @@ const runtimeExtensions: RuntimeExtension[] = [securityHookExtension];
74
84
  function parseArgs(argsJson: string): Record<string, unknown> {
75
85
  try {
76
86
  const parsed = JSON.parse(argsJson) as unknown;
77
- if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return {};
87
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
88
+ return {};
78
89
  return parsed as Record<string, unknown>;
79
90
  } catch {
80
91
  return {};
81
92
  }
82
93
  }
83
94
 
84
- export async function emitRuntimeEvent(payload: RuntimeEventPayload): Promise<void> {
95
+ export async function emitRuntimeEvent(
96
+ payload: RuntimeEventPayload,
97
+ ): Promise<void> {
85
98
  for (const ext of runtimeExtensions) {
86
99
  if (!ext.onEvent) continue;
87
100
  try {
@@ -92,7 +105,10 @@ export async function emitRuntimeEvent(payload: RuntimeEventPayload): Promise<vo
92
105
  }
93
106
  }
94
107
 
95
- export async function runBeforeToolHooks(toolName: string, argsJson: string): Promise<string | null> {
108
+ export async function runBeforeToolHooks(
109
+ toolName: string,
110
+ argsJson: string,
111
+ ): Promise<string | null> {
96
112
  const args = parseArgs(argsJson);
97
113
  for (const ext of runtimeExtensions) {
98
114
  if (!ext.onBeforeToolCall) continue;
@@ -112,11 +128,19 @@ export async function runBeforeToolHooks(toolName: string, argsJson: string): Pr
112
128
  // ignore broken extensions
113
129
  }
114
130
  }
115
- await emitRuntimeEvent({ event: 'before_tool_call', toolName, blocked: false });
131
+ await emitRuntimeEvent({
132
+ event: 'before_tool_call',
133
+ toolName,
134
+ blocked: false,
135
+ });
116
136
  return null;
117
137
  }
118
138
 
119
- export async function runAfterToolHooks(toolName: string, argsJson: string, result: string): Promise<void> {
139
+ export async function runAfterToolHooks(
140
+ toolName: string,
141
+ argsJson: string,
142
+ result: string,
143
+ ): Promise<void> {
120
144
  const args = parseArgs(argsJson);
121
145
  for (const ext of runtimeExtensions) {
122
146
  if (!ext.onAfterToolCall) continue;
@@ -1,4 +1,9 @@
1
- import type { ChatCompletionResponse, ChatMessage, ToolCall, ToolDefinition } from './types.js';
1
+ import type {
2
+ ChatCompletionResponse,
3
+ ChatMessage,
4
+ ToolCall,
5
+ ToolDefinition,
6
+ } from './types.js';
2
7
 
3
8
  export class HybridAIRequestError extends Error {
4
9
  status: number;
@@ -49,8 +54,9 @@ function buildRequestBody(
49
54
  enableRag: boolean,
50
55
  messages: ChatMessage[],
51
56
  tools: ToolDefinition[],
57
+ maxTokens?: number,
52
58
  ): Record<string, unknown> {
53
- return {
59
+ const request: Record<string, unknown> = {
54
60
  model,
55
61
  chatbot_id: chatbotId,
56
62
  messages,
@@ -58,6 +64,14 @@ function buildRequestBody(
58
64
  tool_choice: 'auto',
59
65
  enable_rag: enableRag,
60
66
  };
67
+ if (
68
+ typeof maxTokens === 'number' &&
69
+ Number.isFinite(maxTokens) &&
70
+ maxTokens > 0
71
+ ) {
72
+ request.max_tokens = Math.floor(maxTokens);
73
+ }
74
+ return request;
61
75
  }
62
76
 
63
77
  function parseStreamPayloadLine(rawLine: string): string | null {
@@ -86,7 +100,10 @@ function ensureToolCall(toolCalls: ToolCall[], index: number): ToolCall {
86
100
  return toolCalls[index];
87
101
  }
88
102
 
89
- function mergeToolCallDelta(target: ToolCall, delta: StreamToolCallDelta): void {
103
+ function mergeToolCallDelta(
104
+ target: ToolCall,
105
+ delta: StreamToolCallDelta,
106
+ ): void {
90
107
  if (typeof delta.id === 'string' && delta.id) {
91
108
  target.id = target.id ? `${target.id}${delta.id}` : delta.id;
92
109
  }
@@ -99,7 +116,10 @@ function mergeToolCallDelta(target: ToolCall, delta: StreamToolCallDelta): void
99
116
  ? `${target.function.name}${delta.function.name}`
100
117
  : delta.function.name;
101
118
  }
102
- if (typeof delta.function.arguments === 'string' && delta.function.arguments) {
119
+ if (
120
+ typeof delta.function.arguments === 'string' &&
121
+ delta.function.arguments
122
+ ) {
103
123
  target.function.arguments += delta.function.arguments;
104
124
  }
105
125
  }
@@ -113,10 +133,18 @@ export async function callHybridAI(
113
133
  enableRag: boolean,
114
134
  messages: ChatMessage[],
115
135
  tools: ToolDefinition[],
136
+ maxTokens?: number,
116
137
  ): Promise<ChatCompletionResponse> {
117
138
  const url = `${baseUrl}/v1/chat/completions`;
118
139
 
119
- const body = buildRequestBody(model, chatbotId, enableRag, messages, tools);
140
+ const body = buildRequestBody(
141
+ model,
142
+ chatbotId,
143
+ enableRag,
144
+ messages,
145
+ tools,
146
+ maxTokens,
147
+ );
120
148
 
121
149
  const response = await fetch(url, {
122
150
  method: 'POST',
@@ -144,11 +172,22 @@ export async function callHybridAIStream(
144
172
  messages: ChatMessage[],
145
173
  tools: ToolDefinition[],
146
174
  onTextDelta: (delta: string) => void,
175
+ maxTokens?: number,
147
176
  ): Promise<ChatCompletionResponse> {
148
177
  const url = `${baseUrl}/v1/chat/completions`;
149
178
  const body = {
150
- ...buildRequestBody(model, chatbotId, enableRag, messages, tools),
179
+ ...buildRequestBody(
180
+ model,
181
+ chatbotId,
182
+ enableRag,
183
+ messages,
184
+ tools,
185
+ maxTokens,
186
+ ),
151
187
  stream: true,
188
+ stream_options: {
189
+ include_usage: true,
190
+ },
152
191
  };
153
192
 
154
193
  const response = await fetch(url, {
@@ -166,11 +205,13 @@ export async function callHybridAIStream(
166
205
  throw new HybridAIRequestError(response.status, text);
167
206
  }
168
207
 
169
- const contentType = (response.headers.get('content-type') || '').toLowerCase();
208
+ const contentType = (
209
+ response.headers.get('content-type') || ''
210
+ ).toLowerCase();
170
211
  if (
171
- contentType.includes('application/json')
172
- && !contentType.includes('ndjson')
173
- && !contentType.includes('event-stream')
212
+ contentType.includes('application/json') &&
213
+ !contentType.includes('ndjson') &&
214
+ !contentType.includes('event-stream')
174
215
  ) {
175
216
  return (await response.json()) as ChatCompletionResponse;
176
217
  }
@@ -208,10 +249,14 @@ export async function callHybridAIStream(
208
249
 
209
250
  sawPayload = true;
210
251
  if (typeof payload.id === 'string' && payload.id) streamId = payload.id;
211
- if (typeof payload.model === 'string' && payload.model) streamModel = payload.model;
212
- if (payload.usage && typeof payload.usage === 'object') usage = payload.usage;
213
-
214
- const choice = Array.isArray(payload.choices) ? payload.choices[0] : undefined;
252
+ if (typeof payload.model === 'string' && payload.model)
253
+ streamModel = payload.model;
254
+ if (payload.usage && typeof payload.usage === 'object')
255
+ usage = payload.usage;
256
+
257
+ const choice = Array.isArray(payload.choices)
258
+ ? payload.choices[0]
259
+ : undefined;
215
260
  if (!choice) return;
216
261
 
217
262
  if (choice.message) {
@@ -249,7 +294,10 @@ export async function callHybridAIStream(
249
294
  }
250
295
  if (Array.isArray(delta.tool_calls) && delta.tool_calls.length > 0) {
251
296
  for (const callDelta of delta.tool_calls) {
252
- const index = typeof callDelta.index === 'number' && callDelta.index >= 0 ? callDelta.index : 0;
297
+ const index =
298
+ typeof callDelta.index === 'number' && callDelta.index >= 0
299
+ ? callDelta.index
300
+ : 0;
253
301
  const target = ensureToolCall(toolCalls, index);
254
302
  mergeToolCallDelta(target, callDelta);
255
303
  }
@@ -293,7 +341,8 @@ export async function callHybridAIStream(
293
341
  throw new Error('Streaming response ended without payload');
294
342
  }
295
343
 
296
- const finalFinishReason = finishReason || (toolCalls.length > 0 ? 'tool_calls' : 'stop');
344
+ const finalFinishReason =
345
+ finishReason || (toolCalls.length > 0 ? 'tool_calls' : 'stop');
297
346
  return {
298
347
  id: streamId || 'stream',
299
348
  model: streamModel,