@clinebot/llms 0.0.0

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 (219) hide show
  1. package/README.md +198 -0
  2. package/dist/config-browser.d.ts +3 -0
  3. package/dist/config.d.ts +3 -0
  4. package/dist/index.browser.d.ts +4 -0
  5. package/dist/index.browser.js +1 -0
  6. package/dist/index.d.ts +5 -0
  7. package/dist/index.js +7 -0
  8. package/dist/models/generated-access.d.ts +4 -0
  9. package/dist/models/generated-provider-loaders.d.ts +13 -0
  10. package/dist/models/generated.d.ts +14 -0
  11. package/dist/models/index.d.ts +43 -0
  12. package/dist/models/models-dev-catalog.d.ts +32 -0
  13. package/dist/models/providers/aihubmix.d.ts +5 -0
  14. package/dist/models/providers/anthropic.d.ts +53 -0
  15. package/dist/models/providers/asksage.d.ts +5 -0
  16. package/dist/models/providers/baseten.d.ts +5 -0
  17. package/dist/models/providers/bedrock.d.ts +7 -0
  18. package/dist/models/providers/cerebras.d.ts +7 -0
  19. package/dist/models/providers/claude-code.d.ts +4 -0
  20. package/dist/models/providers/cline.d.ts +34 -0
  21. package/dist/models/providers/deepseek.d.ts +8 -0
  22. package/dist/models/providers/dify.d.ts +5 -0
  23. package/dist/models/providers/doubao.d.ts +7 -0
  24. package/dist/models/providers/fireworks.d.ts +8 -0
  25. package/dist/models/providers/gemini.d.ts +9 -0
  26. package/dist/models/providers/groq.d.ts +8 -0
  27. package/dist/models/providers/hicap.d.ts +5 -0
  28. package/dist/models/providers/huawei-cloud-maas.d.ts +5 -0
  29. package/dist/models/providers/huggingface.d.ts +6 -0
  30. package/dist/models/providers/index.d.ts +45 -0
  31. package/dist/models/providers/litellm.d.ts +5 -0
  32. package/dist/models/providers/lmstudio.d.ts +5 -0
  33. package/dist/models/providers/minimax.d.ts +7 -0
  34. package/dist/models/providers/mistral.d.ts +5 -0
  35. package/dist/models/providers/moonshot.d.ts +7 -0
  36. package/dist/models/providers/nebius.d.ts +7 -0
  37. package/dist/models/providers/nous-research.d.ts +7 -0
  38. package/dist/models/providers/oca.d.ts +9 -0
  39. package/dist/models/providers/ollama.d.ts +5 -0
  40. package/dist/models/providers/openai-codex.d.ts +10 -0
  41. package/dist/models/providers/openai.d.ts +9 -0
  42. package/dist/models/providers/opencode.d.ts +10 -0
  43. package/dist/models/providers/openrouter.d.ts +7 -0
  44. package/dist/models/providers/qwen-code.d.ts +7 -0
  45. package/dist/models/providers/qwen.d.ts +7 -0
  46. package/dist/models/providers/requesty.d.ts +6 -0
  47. package/dist/models/providers/sambanova.d.ts +7 -0
  48. package/dist/models/providers/sapaicore.d.ts +7 -0
  49. package/dist/models/providers/together.d.ts +8 -0
  50. package/dist/models/providers/vercel-ai-gateway.d.ts +5 -0
  51. package/dist/models/providers/vertex.d.ts +7 -0
  52. package/dist/models/providers/xai.d.ts +8 -0
  53. package/dist/models/providers/zai.d.ts +7 -0
  54. package/dist/models/query.d.ts +181 -0
  55. package/dist/models/registry.d.ts +123 -0
  56. package/dist/models/schemas/index.d.ts +7 -0
  57. package/dist/models/schemas/model.d.ts +340 -0
  58. package/dist/models/schemas/query.d.ts +191 -0
  59. package/dist/providers/handlers/ai-sdk-community.d.ts +46 -0
  60. package/dist/providers/handlers/ai-sdk-provider-base.d.ts +32 -0
  61. package/dist/providers/handlers/anthropic-base.d.ts +26 -0
  62. package/dist/providers/handlers/asksage.d.ts +12 -0
  63. package/dist/providers/handlers/auth.d.ts +5 -0
  64. package/dist/providers/handlers/base.d.ts +55 -0
  65. package/dist/providers/handlers/bedrock-base.d.ts +23 -0
  66. package/dist/providers/handlers/bedrock-client.d.ts +4 -0
  67. package/dist/providers/handlers/community-sdk.d.ts +97 -0
  68. package/dist/providers/handlers/fetch-base.d.ts +18 -0
  69. package/dist/providers/handlers/gemini-base.d.ts +25 -0
  70. package/dist/providers/handlers/index.d.ts +19 -0
  71. package/dist/providers/handlers/openai-base.d.ts +54 -0
  72. package/dist/providers/handlers/openai-responses.d.ts +64 -0
  73. package/dist/providers/handlers/providers.d.ts +43 -0
  74. package/dist/providers/handlers/r1-base.d.ts +62 -0
  75. package/dist/providers/handlers/registry.d.ts +106 -0
  76. package/dist/providers/handlers/vertex.d.ts +32 -0
  77. package/dist/providers/index.d.ts +100 -0
  78. package/dist/providers/public.browser.d.ts +2 -0
  79. package/dist/providers/public.d.ts +3 -0
  80. package/dist/providers/shared/openai-compatible.d.ts +10 -0
  81. package/dist/providers/transform/ai-sdk-community-format.d.ts +9 -0
  82. package/dist/providers/transform/anthropic-format.d.ts +24 -0
  83. package/dist/providers/transform/content-format.d.ts +3 -0
  84. package/dist/providers/transform/gemini-format.d.ts +19 -0
  85. package/dist/providers/transform/index.d.ts +10 -0
  86. package/dist/providers/transform/openai-format.d.ts +36 -0
  87. package/dist/providers/transform/r1-format.d.ts +26 -0
  88. package/dist/providers/types/config.d.ts +261 -0
  89. package/dist/providers/types/handler.d.ts +71 -0
  90. package/dist/providers/types/index.d.ts +11 -0
  91. package/dist/providers/types/messages.d.ts +139 -0
  92. package/dist/providers/types/model-info.d.ts +32 -0
  93. package/dist/providers/types/provider-ids.d.ts +63 -0
  94. package/dist/providers/types/settings.d.ts +308 -0
  95. package/dist/providers/types/stream.d.ts +106 -0
  96. package/dist/providers/utils/index.d.ts +7 -0
  97. package/dist/providers/utils/retry.d.ts +38 -0
  98. package/dist/providers/utils/stream-processor.d.ts +110 -0
  99. package/dist/providers/utils/tool-processor.d.ts +34 -0
  100. package/dist/sdk.d.ts +18 -0
  101. package/dist/types.d.ts +60 -0
  102. package/package.json +66 -0
  103. package/src/catalog.ts +20 -0
  104. package/src/config-browser.ts +11 -0
  105. package/src/config.ts +49 -0
  106. package/src/index.browser.ts +9 -0
  107. package/src/index.ts +10 -0
  108. package/src/live-providers.test.ts +137 -0
  109. package/src/models/generated-access.ts +41 -0
  110. package/src/models/generated-provider-loaders.ts +166 -0
  111. package/src/models/generated.ts +11997 -0
  112. package/src/models/index.ts +271 -0
  113. package/src/models/models-dev-catalog.test.ts +161 -0
  114. package/src/models/models-dev-catalog.ts +161 -0
  115. package/src/models/providers/aihubmix.ts +19 -0
  116. package/src/models/providers/anthropic.ts +60 -0
  117. package/src/models/providers/asksage.ts +19 -0
  118. package/src/models/providers/baseten.ts +21 -0
  119. package/src/models/providers/bedrock.ts +30 -0
  120. package/src/models/providers/cerebras.ts +24 -0
  121. package/src/models/providers/claude-code.ts +51 -0
  122. package/src/models/providers/cline.ts +25 -0
  123. package/src/models/providers/deepseek.ts +33 -0
  124. package/src/models/providers/dify.ts +17 -0
  125. package/src/models/providers/doubao.ts +33 -0
  126. package/src/models/providers/fireworks.ts +34 -0
  127. package/src/models/providers/gemini.ts +43 -0
  128. package/src/models/providers/groq.ts +33 -0
  129. package/src/models/providers/hicap.ts +18 -0
  130. package/src/models/providers/huawei-cloud-maas.ts +18 -0
  131. package/src/models/providers/huggingface.ts +22 -0
  132. package/src/models/providers/index.ts +162 -0
  133. package/src/models/providers/litellm.ts +19 -0
  134. package/src/models/providers/lmstudio.ts +22 -0
  135. package/src/models/providers/minimax.ts +34 -0
  136. package/src/models/providers/mistral.ts +19 -0
  137. package/src/models/providers/moonshot.ts +34 -0
  138. package/src/models/providers/nebius.ts +24 -0
  139. package/src/models/providers/nous-research.ts +21 -0
  140. package/src/models/providers/oca.ts +30 -0
  141. package/src/models/providers/ollama.ts +18 -0
  142. package/src/models/providers/openai-codex.ts +30 -0
  143. package/src/models/providers/openai.ts +43 -0
  144. package/src/models/providers/opencode.ts +28 -0
  145. package/src/models/providers/openrouter.ts +24 -0
  146. package/src/models/providers/qwen-code.ts +33 -0
  147. package/src/models/providers/qwen.ts +34 -0
  148. package/src/models/providers/requesty.ts +23 -0
  149. package/src/models/providers/sambanova.ts +23 -0
  150. package/src/models/providers/sapaicore.ts +34 -0
  151. package/src/models/providers/together.ts +35 -0
  152. package/src/models/providers/vercel-ai-gateway.ts +23 -0
  153. package/src/models/providers/vertex.ts +36 -0
  154. package/src/models/providers/xai.ts +34 -0
  155. package/src/models/providers/zai.ts +25 -0
  156. package/src/models/query.ts +407 -0
  157. package/src/models/registry.ts +511 -0
  158. package/src/models/schemas/index.ts +62 -0
  159. package/src/models/schemas/model.ts +308 -0
  160. package/src/models/schemas/query.ts +336 -0
  161. package/src/providers/browser.ts +4 -0
  162. package/src/providers/handlers/ai-sdk-community.ts +226 -0
  163. package/src/providers/handlers/ai-sdk-provider-base.ts +193 -0
  164. package/src/providers/handlers/anthropic-base.ts +372 -0
  165. package/src/providers/handlers/asksage.test.ts +103 -0
  166. package/src/providers/handlers/asksage.ts +138 -0
  167. package/src/providers/handlers/auth.test.ts +19 -0
  168. package/src/providers/handlers/auth.ts +121 -0
  169. package/src/providers/handlers/base.test.ts +46 -0
  170. package/src/providers/handlers/base.ts +160 -0
  171. package/src/providers/handlers/bedrock-base.ts +390 -0
  172. package/src/providers/handlers/bedrock-client.ts +100 -0
  173. package/src/providers/handlers/codex.test.ts +123 -0
  174. package/src/providers/handlers/community-sdk.test.ts +288 -0
  175. package/src/providers/handlers/community-sdk.ts +392 -0
  176. package/src/providers/handlers/fetch-base.ts +68 -0
  177. package/src/providers/handlers/gemini-base.ts +302 -0
  178. package/src/providers/handlers/index.ts +67 -0
  179. package/src/providers/handlers/openai-base.ts +277 -0
  180. package/src/providers/handlers/openai-responses.ts +598 -0
  181. package/src/providers/handlers/providers.test.ts +120 -0
  182. package/src/providers/handlers/providers.ts +563 -0
  183. package/src/providers/handlers/r1-base.ts +280 -0
  184. package/src/providers/handlers/registry.ts +185 -0
  185. package/src/providers/handlers/vertex.test.ts +124 -0
  186. package/src/providers/handlers/vertex.ts +292 -0
  187. package/src/providers/index.ts +534 -0
  188. package/src/providers/public.browser.ts +20 -0
  189. package/src/providers/public.ts +51 -0
  190. package/src/providers/shared/openai-compatible.ts +63 -0
  191. package/src/providers/transform/ai-sdk-community-format.test.ts +73 -0
  192. package/src/providers/transform/ai-sdk-community-format.ts +115 -0
  193. package/src/providers/transform/anthropic-format.ts +218 -0
  194. package/src/providers/transform/content-format.ts +34 -0
  195. package/src/providers/transform/format-conversion.test.ts +310 -0
  196. package/src/providers/transform/gemini-format.ts +167 -0
  197. package/src/providers/transform/index.ts +22 -0
  198. package/src/providers/transform/openai-format.ts +247 -0
  199. package/src/providers/transform/r1-format.ts +287 -0
  200. package/src/providers/types/config.ts +388 -0
  201. package/src/providers/types/handler.ts +87 -0
  202. package/src/providers/types/index.ts +120 -0
  203. package/src/providers/types/messages.ts +158 -0
  204. package/src/providers/types/model-info.test.ts +57 -0
  205. package/src/providers/types/model-info.ts +65 -0
  206. package/src/providers/types/provider-ids.test.ts +12 -0
  207. package/src/providers/types/provider-ids.ts +89 -0
  208. package/src/providers/types/settings.test.ts +49 -0
  209. package/src/providers/types/settings.ts +533 -0
  210. package/src/providers/types/stream.ts +117 -0
  211. package/src/providers/utils/index.ts +27 -0
  212. package/src/providers/utils/retry.test.ts +140 -0
  213. package/src/providers/utils/retry.ts +188 -0
  214. package/src/providers/utils/stream-processor.test.ts +232 -0
  215. package/src/providers/utils/stream-processor.ts +472 -0
  216. package/src/providers/utils/tool-processor.test.ts +34 -0
  217. package/src/providers/utils/tool-processor.ts +111 -0
  218. package/src/sdk.ts +264 -0
  219. package/src/types.ts +79 -0
@@ -0,0 +1,140 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+ import {
3
+ calculateRetryDelay,
4
+ isRetriableError,
5
+ RetriableError,
6
+ retryAsync,
7
+ retryStream,
8
+ } from "./retry";
9
+
10
+ describe("retry utils", () => {
11
+ it("detects retriable errors from known patterns", () => {
12
+ expect(isRetriableError(new RetriableError("try again"))).toBe(true);
13
+ expect(isRetriableError(new Error("HTTP 429: Too Many Requests"))).toBe(
14
+ true,
15
+ );
16
+ expect(isRetriableError(new Error("upstream 503 failure"))).toBe(true);
17
+ expect(isRetriableError(new Error("network timeout occurred"))).toBe(true);
18
+ expect(isRetriableError(new Error("validation failed"))).toBe(false);
19
+ expect(isRetriableError("not-an-error")).toBe(false);
20
+ });
21
+
22
+ it("calculates exponential delay with jitter and max cap", () => {
23
+ const randomSpy = vi.spyOn(Math, "random").mockReturnValue(0);
24
+
25
+ const noJitter = calculateRetryDelay(2, { baseDelay: 10, maxDelay: 1000 });
26
+ expect(noJitter).toBe(40);
27
+
28
+ randomSpy.mockReturnValue(1);
29
+ const capped = calculateRetryDelay(10, { baseDelay: 1000, maxDelay: 1500 });
30
+ expect(capped).toBe(1500);
31
+
32
+ randomSpy.mockRestore();
33
+ });
34
+
35
+ it("retries async function until success and calls onRetryAttempt", async () => {
36
+ let attempts = 0;
37
+ const onRetryAttempt = vi.fn();
38
+
39
+ const value = await retryAsync(
40
+ async () => {
41
+ attempts++;
42
+ if (attempts < 3) {
43
+ throw new RetriableError("retry me");
44
+ }
45
+ return "ok";
46
+ },
47
+ {
48
+ maxRetries: 3,
49
+ baseDelay: 0,
50
+ maxDelay: 0,
51
+ onRetryAttempt,
52
+ },
53
+ );
54
+
55
+ expect(value).toBe("ok");
56
+ expect(attempts).toBe(3);
57
+ expect(onRetryAttempt).toHaveBeenCalledTimes(2);
58
+ expect(onRetryAttempt).toHaveBeenNthCalledWith(
59
+ 1,
60
+ 1,
61
+ 3,
62
+ 0,
63
+ expect.any(RetriableError),
64
+ );
65
+ expect(onRetryAttempt).toHaveBeenNthCalledWith(
66
+ 2,
67
+ 2,
68
+ 3,
69
+ 0,
70
+ expect.any(RetriableError),
71
+ );
72
+ });
73
+
74
+ it("respects retry-after seconds for retriable errors", async () => {
75
+ let attempts = 0;
76
+ const onRetryAttempt = vi.fn();
77
+
78
+ vi.useFakeTimers();
79
+
80
+ const pending = retryAsync(
81
+ async () => {
82
+ attempts++;
83
+ if (attempts === 1) {
84
+ throw new RetriableError("rate limited", 2);
85
+ }
86
+ return "done";
87
+ },
88
+ {
89
+ maxRetries: 2,
90
+ onRetryAttempt,
91
+ },
92
+ );
93
+ await vi.runAllTimersAsync();
94
+ await pending;
95
+ vi.useRealTimers();
96
+
97
+ expect(onRetryAttempt).toHaveBeenCalledWith(
98
+ 1,
99
+ 2,
100
+ 2000,
101
+ expect.any(RetriableError),
102
+ );
103
+ });
104
+
105
+ it("does not retry non-retriable errors", async () => {
106
+ const fn = vi.fn(async () => {
107
+ throw new Error("bad input");
108
+ });
109
+
110
+ await expect(
111
+ retryAsync(fn, {
112
+ maxRetries: 3,
113
+ baseDelay: 0,
114
+ maxDelay: 0,
115
+ }),
116
+ ).rejects.toThrow("bad input");
117
+ expect(fn).toHaveBeenCalledTimes(1);
118
+ });
119
+
120
+ it("retries async generators and preserves yielded chunks", async () => {
121
+ let attempts = 0;
122
+ const chunks: Array<{ type: string; text?: string }> = [];
123
+ for await (const chunk of retryStream(
124
+ async function* () {
125
+ attempts++;
126
+ if (attempts < 3) {
127
+ throw new RetriableError("temporary");
128
+ }
129
+ yield { type: "text", text: "hello" };
130
+ yield { type: "done" };
131
+ },
132
+ { maxRetries: 3, baseDelay: 0, maxDelay: 0 },
133
+ )) {
134
+ chunks.push(chunk);
135
+ }
136
+
137
+ expect(chunks).toEqual([{ type: "text", text: "hello" }, { type: "done" }]);
138
+ expect(attempts).toBe(3);
139
+ });
140
+ });
@@ -0,0 +1,188 @@
1
+ /**
2
+ * Retry Utilities
3
+ *
4
+ * Provides retry logic with exponential backoff for API calls.
5
+ */
6
+
7
+ export interface RetryOptions {
8
+ maxRetries?: number;
9
+ baseDelay?: number;
10
+ maxDelay?: number;
11
+ onRetryAttempt?: (
12
+ attempt: number,
13
+ maxRetries: number,
14
+ delay: number,
15
+ error: unknown,
16
+ ) => void;
17
+ }
18
+
19
+ const DEFAULT_OPTIONS: Required<Omit<RetryOptions, "onRetryAttempt">> = {
20
+ maxRetries: 4,
21
+ baseDelay: 2000,
22
+ maxDelay: 15000,
23
+ };
24
+
25
+ /**
26
+ * Error that indicates the operation should be retried
27
+ */
28
+ export class RetriableError extends Error {
29
+ constructor(
30
+ message: string,
31
+ public readonly retryAfterSeconds?: number,
32
+ options?: ErrorOptions,
33
+ ) {
34
+ super(message, options);
35
+ this.name = "RetriableError";
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Check if an error is retriable
41
+ */
42
+ export function isRetriableError(error: unknown): boolean {
43
+ if (error instanceof RetriableError) {
44
+ return true;
45
+ }
46
+
47
+ if (error instanceof Error) {
48
+ const message = error.message.toLowerCase();
49
+
50
+ // Rate limit errors
51
+ if (
52
+ message.includes("429") ||
53
+ message.includes("rate limit") ||
54
+ message.includes("too many requests")
55
+ ) {
56
+ return true;
57
+ }
58
+
59
+ // Server errors (5xx)
60
+ if (
61
+ message.includes("500") ||
62
+ message.includes("502") ||
63
+ message.includes("503") ||
64
+ message.includes("504")
65
+ ) {
66
+ return true;
67
+ }
68
+
69
+ // Network errors
70
+ if (
71
+ message.includes("network") ||
72
+ message.includes("timeout") ||
73
+ message.includes("econnreset") ||
74
+ message.includes("econnrefused")
75
+ ) {
76
+ return true;
77
+ }
78
+ }
79
+
80
+ return false;
81
+ }
82
+
83
+ /**
84
+ * Calculate delay for retry attempt with exponential backoff and jitter
85
+ */
86
+ export function calculateRetryDelay(
87
+ attempt: number,
88
+ options: RetryOptions = {},
89
+ ): number {
90
+ const {
91
+ baseDelay = DEFAULT_OPTIONS.baseDelay,
92
+ maxDelay = DEFAULT_OPTIONS.maxDelay,
93
+ } = options;
94
+
95
+ // Exponential backoff: 2^attempt * baseDelay
96
+ const exponentialDelay = 2 ** attempt * baseDelay;
97
+
98
+ // Add jitter (0-25% of delay)
99
+ const jitter = Math.random() * 0.25 * exponentialDelay;
100
+
101
+ // Cap at maxDelay
102
+ return Math.min(exponentialDelay + jitter, maxDelay);
103
+ }
104
+
105
+ /**
106
+ * Sleep for a given number of milliseconds
107
+ */
108
+ export function sleep(ms: number): Promise<void> {
109
+ return new Promise((resolve) => setTimeout(resolve, ms));
110
+ }
111
+
112
+ /**
113
+ * Retry an async generator factory using the same retry policy as retryAsync.
114
+ */
115
+ export async function* retryStream<T>(
116
+ createStream: () => AsyncGenerator<T, void, unknown>,
117
+ options: RetryOptions = {},
118
+ ): AsyncGenerator<T, void, unknown> {
119
+ const { maxRetries = DEFAULT_OPTIONS.maxRetries, onRetryAttempt } = options;
120
+
121
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
122
+ try {
123
+ yield* createStream();
124
+ return;
125
+ } catch (error) {
126
+ const isLastAttempt = attempt === maxRetries;
127
+ const shouldRetry = !isLastAttempt && isRetriableError(error);
128
+
129
+ if (!shouldRetry) {
130
+ throw error;
131
+ }
132
+
133
+ let delay: number;
134
+ if (error instanceof RetriableError && error.retryAfterSeconds) {
135
+ delay = error.retryAfterSeconds * 1000;
136
+ } else {
137
+ delay = calculateRetryDelay(attempt, options);
138
+ }
139
+
140
+ if (onRetryAttempt) {
141
+ onRetryAttempt(attempt + 1, maxRetries, delay, error);
142
+ }
143
+
144
+ await sleep(delay);
145
+ }
146
+ }
147
+
148
+ throw new Error("Retry logic exhausted without returning or throwing");
149
+ }
150
+
151
+ /**
152
+ * Wrap an async function with retry logic
153
+ */
154
+ export async function retryAsync<T>(
155
+ fn: () => Promise<T>,
156
+ options: RetryOptions = {},
157
+ ): Promise<T> {
158
+ const { maxRetries = DEFAULT_OPTIONS.maxRetries, onRetryAttempt } = options;
159
+
160
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
161
+ try {
162
+ return await fn();
163
+ } catch (error) {
164
+ const isLastAttempt = attempt === maxRetries;
165
+ const shouldRetry = !isLastAttempt && isRetriableError(error);
166
+
167
+ if (!shouldRetry) {
168
+ throw error;
169
+ }
170
+
171
+ let delay: number;
172
+ if (error instanceof RetriableError && error.retryAfterSeconds) {
173
+ delay = error.retryAfterSeconds * 1000;
174
+ } else {
175
+ delay = calculateRetryDelay(attempt, options);
176
+ }
177
+
178
+ if (onRetryAttempt) {
179
+ onRetryAttempt(attempt + 1, maxRetries, delay, error);
180
+ }
181
+
182
+ await sleep(delay);
183
+ }
184
+ }
185
+
186
+ // This should never be reached
187
+ throw new Error("Retry logic exhausted without returning or throwing");
188
+ }
@@ -0,0 +1,232 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { StreamResponseProcessor } from "./stream-processor";
3
+
4
+ describe("StreamResponseProcessor", () => {
5
+ it("assembles text, reasoning, tool calls, usage, and completion metadata", () => {
6
+ const processor = new StreamResponseProcessor();
7
+
8
+ processor.process({
9
+ type: "reasoning",
10
+ id: "resp_1",
11
+ reasoning: "thinking...",
12
+ details: {
13
+ type: "reasoning.text",
14
+ text: "step 1",
15
+ signature: "sig-r",
16
+ format: "anthropic-claude-v1",
17
+ index: 0,
18
+ },
19
+ redacted_data: "encrypted",
20
+ });
21
+ processor.process({
22
+ type: "text",
23
+ id: "resp_1",
24
+ text: "Hello ",
25
+ });
26
+ processor.process({
27
+ type: "text",
28
+ id: "resp_1",
29
+ text: "world",
30
+ signature: "sig-text",
31
+ });
32
+ processor.process({
33
+ type: "tool_calls",
34
+ id: "resp_1",
35
+ signature: "sig-tool",
36
+ tool_call: {
37
+ call_id: "call_1",
38
+ function: {
39
+ id: "tool_1",
40
+ name: "read_file",
41
+ arguments: '{"path":"/tmp/a',
42
+ },
43
+ },
44
+ });
45
+ processor.process({
46
+ type: "tool_calls",
47
+ id: "resp_1",
48
+ tool_call: {
49
+ function: {
50
+ id: "tool_1",
51
+ arguments: '.ts"}',
52
+ },
53
+ },
54
+ });
55
+ processor.process({
56
+ type: "usage",
57
+ id: "resp_1",
58
+ inputTokens: 10,
59
+ outputTokens: 20,
60
+ cacheReadTokens: 5,
61
+ cacheWriteTokens: 0,
62
+ thoughtsTokenCount: 3,
63
+ totalCost: 0.001,
64
+ });
65
+ const partial = processor.process({
66
+ type: "done",
67
+ id: "resp_1",
68
+ success: true,
69
+ incompleteReason: "max_output_tokens",
70
+ });
71
+
72
+ expect(partial.responseId).toBe("resp_1");
73
+ expect(partial.content[0]).toMatchObject({
74
+ type: "thinking",
75
+ thinking: "thinking...",
76
+ signature: "sig-r",
77
+ });
78
+ expect(partial.content[1]).toMatchObject({
79
+ type: "text",
80
+ text: "Hello world",
81
+ signature: "sig-text",
82
+ });
83
+ expect(partial.content[2]).toMatchObject({
84
+ type: "tool_use",
85
+ id: "tool_1",
86
+ name: "read_file",
87
+ input: { path: "/tmp/a.ts" },
88
+ signature: "sig-tool",
89
+ });
90
+
91
+ const final = processor.finalize();
92
+ expect(final.responseId).toBe("resp_1");
93
+ expect(final.incompleteReason).toBe("max_output_tokens");
94
+ expect(final.usage).toEqual({
95
+ inputTokens: 10,
96
+ outputTokens: 20,
97
+ cacheReadTokens: 5,
98
+ cacheWriteTokens: 0,
99
+ thoughtsTokenCount: 3,
100
+ totalCost: 0.001,
101
+ });
102
+ expect(final.content[0]).toMatchObject({
103
+ type: "thinking",
104
+ thinking: "thinking...",
105
+ summary: [
106
+ {
107
+ type: "reasoning.text",
108
+ text: "step 1",
109
+ signature: "sig-r",
110
+ format: "anthropic-claude-v1",
111
+ index: 0,
112
+ },
113
+ ],
114
+ });
115
+ expect(final.content[1]).toEqual({
116
+ type: "redacted_thinking",
117
+ data: "encrypted",
118
+ call_id: "resp_1",
119
+ });
120
+ expect(final.content[2]).toMatchObject({
121
+ type: "text",
122
+ text: "Hello world",
123
+ signature: "sig-text",
124
+ reasoning_details: [
125
+ {
126
+ type: "reasoning.text",
127
+ text: "step 1",
128
+ signature: "sig-r",
129
+ format: "anthropic-claude-v1",
130
+ index: 0,
131
+ },
132
+ ],
133
+ });
134
+ expect(final.content[3]).toMatchObject({
135
+ type: "tool_use",
136
+ id: "tool_1",
137
+ name: "read_file",
138
+ input: { path: "/tmp/a.ts" },
139
+ signature: "sig-tool",
140
+ });
141
+ });
142
+
143
+ it("extracts partial JSON fields while arguments are incomplete", () => {
144
+ const processor = new StreamResponseProcessor();
145
+
146
+ const partial = processor.process({
147
+ type: "tool_calls",
148
+ id: "resp_2",
149
+ tool_call: {
150
+ call_id: "call_partial",
151
+ function: {
152
+ id: "tool_partial",
153
+ name: "run_command",
154
+ arguments:
155
+ '{"command":"echo \\"hi\\"","cwd":"/Users/beatrix/dev/cline-packages',
156
+ },
157
+ },
158
+ });
159
+
160
+ expect(partial.content).toEqual([
161
+ {
162
+ type: "tool_use",
163
+ id: "tool_partial",
164
+ name: "run_command",
165
+ input: {
166
+ command: 'echo "hi"',
167
+ cwd: "/Users/beatrix/dev/cline-packages",
168
+ },
169
+ call_id: "call_partial",
170
+ signature: undefined,
171
+ },
172
+ ]);
173
+ });
174
+
175
+ it("handles object arguments and reset()", () => {
176
+ const processor = new StreamResponseProcessor();
177
+
178
+ processor.process({
179
+ type: "tool_calls",
180
+ id: "resp_3",
181
+ tool_call: {
182
+ call_id: "call_obj",
183
+ function: {
184
+ id: "tool_obj",
185
+ name: "edit_file",
186
+ arguments: {
187
+ path: "a.ts",
188
+ content: "x",
189
+ },
190
+ },
191
+ },
192
+ });
193
+
194
+ const finalBeforeReset = processor.finalize();
195
+ expect(finalBeforeReset.content).toEqual([
196
+ {
197
+ type: "tool_use",
198
+ id: "tool_obj",
199
+ name: "edit_file",
200
+ input: { path: "a.ts", content: "x" },
201
+ call_id: "call_obj",
202
+ signature: undefined,
203
+ reasoning_details: undefined,
204
+ },
205
+ ]);
206
+
207
+ processor.reset();
208
+ expect(processor.finalize()).toEqual({
209
+ content: [],
210
+ usage: undefined,
211
+ responseId: undefined,
212
+ incompleteReason: undefined,
213
+ });
214
+ });
215
+
216
+ it("ignores tool call chunks that do not have an id", () => {
217
+ const processor = new StreamResponseProcessor();
218
+
219
+ processor.process({
220
+ type: "tool_calls",
221
+ id: "resp_4",
222
+ tool_call: {
223
+ function: {
224
+ name: "ignored",
225
+ arguments: "{}",
226
+ },
227
+ },
228
+ });
229
+
230
+ expect(processor.finalize().content).toEqual([]);
231
+ });
232
+ });