@renxqoo/renx-code 0.0.4 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +82 -51
- package/bin/renx.cjs +16 -0
- package/package.json +2 -45
- package/src/agent/runtime/runtime.context-usage.test.ts +4 -5
- package/src/agent/runtime/runtime.error-handling.test.ts +4 -5
- package/src/agent/runtime/runtime.test.ts +7 -4
- package/src/agent/runtime/runtime.ts +3 -9
- package/src/agent/runtime/runtime.usage-forwarding.test.ts +4 -5
- package/src/agent/runtime/source-modules.test.ts +16 -35
- package/src/agent/runtime/source-modules.ts +17 -0
- package/vendor/agent-root/src/agent/ENTERPRISE_ACCEPTANCE_CHECKLIST.md +95 -0
- package/vendor/agent-root/src/agent/ENTERPRISE_REALTIME.html +1345 -0
- package/vendor/agent-root/src/agent/ENTERPRISE_REALTIME.md +1353 -0
- package/vendor/agent-root/src/agent/ERROR_CONTRACT.md +60 -0
- package/vendor/agent-root/src/agent/TEST_COVERAGE_ANALYSIS.md +278 -0
- package/vendor/agent-root/src/agent/__test__/error-contract.test.ts +72 -0
- package/vendor/agent-root/src/agent/__test__/types.test.ts +137 -0
- package/vendor/agent-root/src/agent/agent/__test__/abort-runtime.test.ts +83 -0
- package/vendor/agent-root/src/agent/agent/__test__/callback-safety.test.ts +34 -0
- package/vendor/agent-root/src/agent/agent/__test__/compaction.test.ts +323 -0
- package/vendor/agent-root/src/agent/agent/__test__/concurrency.test.ts +290 -0
- package/vendor/agent-root/src/agent/agent/__test__/error-normalizer.test.ts +377 -0
- package/vendor/agent-root/src/agent/agent/__test__/error.test.ts +212 -0
- package/vendor/agent-root/src/agent/agent/__test__/fault-injection.test.ts +295 -0
- package/vendor/agent-root/src/agent/agent/__test__/index.test.ts +3607 -0
- package/vendor/agent-root/src/agent/agent/__test__/logger.test.ts +35 -0
- package/vendor/agent-root/src/agent/agent/__test__/message-utils.test.ts +517 -0
- package/vendor/agent-root/src/agent/agent/__test__/telemetry.test.ts +97 -0
- package/vendor/agent-root/src/agent/agent/__test__/timeout-budget.test.ts +479 -0
- package/vendor/agent-root/src/agent/agent/__test__/tool-call-merge.test.ts +80 -0
- package/vendor/agent-root/src/agent/agent/__test__/tool-execution-ledger.test.ts +76 -0
- package/vendor/agent-root/src/agent/agent/__test__/write-buffer.test.ts +173 -0
- package/vendor/agent-root/src/agent/agent/__test__/write-file-session.test.ts +109 -0
- package/vendor/agent-root/src/agent/agent/abort-runtime.ts +71 -0
- package/vendor/agent-root/src/agent/agent/callback-safety.ts +33 -0
- package/vendor/agent-root/src/agent/agent/compaction.ts +291 -0
- package/vendor/agent-root/src/agent/agent/concurrency.ts +103 -0
- package/vendor/agent-root/src/agent/agent/error-normalizer.ts +190 -0
- package/vendor/agent-root/src/agent/agent/error.ts +198 -0
- package/vendor/agent-root/src/agent/agent/index.ts +1772 -0
- package/vendor/agent-root/src/agent/agent/logger.ts +65 -0
- package/vendor/agent-root/src/agent/agent/message-utils.ts +101 -0
- package/vendor/agent-root/src/agent/agent/stream-events.ts +61 -0
- package/vendor/agent-root/src/agent/agent/telemetry.ts +123 -0
- package/vendor/agent-root/src/agent/agent/timeout-budget.ts +227 -0
- package/vendor/agent-root/src/agent/agent/tool-call-merge.ts +111 -0
- package/vendor/agent-root/src/agent/agent/tool-execution-ledger.ts +164 -0
- package/vendor/agent-root/src/agent/agent/write-buffer.ts +188 -0
- package/vendor/agent-root/src/agent/agent/write-file-session.ts +238 -0
- package/vendor/agent-root/src/agent/app/__test__/agent-app-service.test.ts +1053 -0
- package/vendor/agent-root/src/agent/app/__test__/minimal-agent-application.test.ts +158 -0
- package/vendor/agent-root/src/agent/app/__test__/sqlite-agent-app-store.test.ts +437 -0
- package/vendor/agent-root/src/agent/app/agent-app-service.ts +748 -0
- package/vendor/agent-root/src/agent/app/contracts.ts +109 -0
- package/vendor/agent-root/src/agent/app/index.ts +5 -0
- package/vendor/agent-root/src/agent/app/minimal-agent-application.ts +151 -0
- package/vendor/agent-root/src/agent/app/ports.ts +72 -0
- package/vendor/agent-root/src/agent/app/sqlite-agent-app-store.ts +1182 -0
- package/vendor/agent-root/src/agent/app/sqlite-client.ts +177 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/00-README.md +36 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/01-scope-and-goals.md +33 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/02-architecture-overview.md +40 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/03-domain-model-and-contracts.md +91 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/04-ports-and-interfaces.md +116 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/05-run-orchestration-and-state-machine.md +52 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/06-cli-commands-and-ux.md +53 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/07-storage-design-local.md +52 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/08-error-and-observability.md +40 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/09-security-and-policy-boundary.md +19 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/10-test-plan-and-acceptance.md +28 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/11-implementation-phases.md +26 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/12-open-questions-and-risks.md +30 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/13-sqlite-schema-fields-and-rationale.md +567 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/14-project-flow-mermaid.md +583 -0
- package/vendor/agent-root/src/agent/docs/cli-app-layer/15-openclaw-style-project-blueprint.md +972 -0
- package/vendor/agent-root/src/agent/error-contract.ts +154 -0
- package/vendor/agent-root/src/agent/prompts/system.ts +246 -0
- package/vendor/agent-root/src/agent/prompts/system1.ts +208 -0
- package/vendor/agent-root/src/agent/storage/__test__/file-history-store.test.ts +98 -0
- package/vendor/agent-root/src/agent/storage/file-history-store.ts +313 -0
- package/vendor/agent-root/src/agent/storage/file-storage-config.ts +94 -0
- package/vendor/agent-root/src/agent/storage/file-system.ts +31 -0
- package/vendor/agent-root/src/agent/storage/file-write-service.ts +21 -0
- package/vendor/agent-root/src/agent/tool/__test__/base-tool.test.ts +413 -0
- package/vendor/agent-root/src/agent/tool/__test__/bash-policy.test.ts +356 -0
- package/vendor/agent-root/src/agent/tool/__test__/bash.mocked-coverage.test.ts +375 -0
- package/vendor/agent-root/src/agent/tool/__test__/bash.test.ts +372 -0
- package/vendor/agent-root/src/agent/tool/__test__/error.test.ts +108 -0
- package/vendor/agent-root/src/agent/tool/__test__/file-edit-tool.test.ts +258 -0
- package/vendor/agent-root/src/agent/tool/__test__/file-history-tools.test.ts +121 -0
- package/vendor/agent-root/src/agent/tool/__test__/file-read-tool.test.ts +210 -0
- package/vendor/agent-root/src/agent/tool/__test__/glob.test.ts +139 -0
- package/vendor/agent-root/src/agent/tool/__test__/grep.mocked-coverage.test.ts +456 -0
- package/vendor/agent-root/src/agent/tool/__test__/grep.test.ts +192 -0
- package/vendor/agent-root/src/agent/tool/__test__/lsp.test.ts +300 -0
- package/vendor/agent-root/src/agent/tool/__test__/outside-workspace-confirmation.test.ts +214 -0
- package/vendor/agent-root/src/agent/tool/__test__/path-security.test.ts +336 -0
- package/vendor/agent-root/src/agent/tool/__test__/skill-loader.test.ts +494 -0
- package/vendor/agent-root/src/agent/tool/__test__/skill-parser.test.ts +543 -0
- package/vendor/agent-root/src/agent/tool/__test__/skill-tool.test.ts +172 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-concurrency-and-version.test.ts +116 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-create-get-list-update.test.ts +267 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-create.test.ts +519 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-errors.test.ts +225 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-output-blocking.test.ts +223 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-output.test.ts +184 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-parent-abort.test.ts +287 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-real-runner-adapter.test.ts +190 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-run-lifecycle.test.ts +352 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-store-runner-branches.test.ts +395 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-store.test.ts +391 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-subagent-config-integration.test.ts +176 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-subagent-config.test.ts +68 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-tools-core-edges.test.ts +630 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-tools-runtime-edges.test.ts +732 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-types.test.ts +494 -0
- package/vendor/agent-root/src/agent/tool/__test__/task-utils-branches.test.ts +175 -0
- package/vendor/agent-root/src/agent/tool/__test__/tool-manager.test.ts +505 -0
- package/vendor/agent-root/src/agent/tool/__test__/types.test.ts +55 -0
- package/vendor/agent-root/src/agent/tool/__test__/web-fetch.test.ts +244 -0
- package/vendor/agent-root/src/agent/tool/__test__/web-search.test.ts +290 -0
- package/vendor/agent-root/src/agent/tool/__test__/write-file.test.ts +368 -0
- package/vendor/agent-root/src/agent/tool/base-tool.ts +345 -0
- package/vendor/agent-root/src/agent/tool/bash-policy.ts +636 -0
- package/vendor/agent-root/src/agent/tool/bash.ts +688 -0
- package/vendor/agent-root/src/agent/tool/error.ts +131 -0
- package/vendor/agent-root/src/agent/tool/file-edit-tool.ts +264 -0
- package/vendor/agent-root/src/agent/tool/file-history-list.ts +103 -0
- package/vendor/agent-root/src/agent/tool/file-history-restore.ts +149 -0
- package/vendor/agent-root/src/agent/tool/file-read-tool.ts +211 -0
- package/vendor/agent-root/src/agent/tool/glob.ts +171 -0
- package/vendor/agent-root/src/agent/tool/grep.ts +496 -0
- package/vendor/agent-root/src/agent/tool/lsp.ts +481 -0
- package/vendor/agent-root/src/agent/tool/path-security.ts +117 -0
- package/vendor/agent-root/src/agent/tool/search/common.ts +153 -0
- package/vendor/agent-root/src/agent/tool/skill/index.ts +13 -0
- package/vendor/agent-root/src/agent/tool/skill/loader.ts +229 -0
- package/vendor/agent-root/src/agent/tool/skill/parser.ts +124 -0
- package/vendor/agent-root/src/agent/tool/skill/types.ts +27 -0
- package/vendor/agent-root/src/agent/tool/skill-tool.ts +143 -0
- package/vendor/agent-root/src/agent/tool/task-create.ts +186 -0
- package/vendor/agent-root/src/agent/tool/task-errors.ts +42 -0
- package/vendor/agent-root/src/agent/tool/task-get.ts +116 -0
- package/vendor/agent-root/src/agent/tool/task-graph.ts +78 -0
- package/vendor/agent-root/src/agent/tool/task-list.ts +141 -0
- package/vendor/agent-root/src/agent/tool/task-mock-runner-adapter.ts +232 -0
- package/vendor/agent-root/src/agent/tool/task-output.ts +223 -0
- package/vendor/agent-root/src/agent/tool/task-parent-abort.ts +115 -0
- package/vendor/agent-root/src/agent/tool/task-real-runner-adapter.ts +336 -0
- package/vendor/agent-root/src/agent/tool/task-runner-adapter.ts +55 -0
- package/vendor/agent-root/src/agent/tool/task-stop.ts +187 -0
- package/vendor/agent-root/src/agent/tool/task-store.ts +217 -0
- package/vendor/agent-root/src/agent/tool/task-subagent-config.ts +149 -0
- package/vendor/agent-root/src/agent/tool/task-types.ts +264 -0
- package/vendor/agent-root/src/agent/tool/task-update.ts +315 -0
- package/vendor/agent-root/src/agent/tool/task.ts +209 -0
- package/vendor/agent-root/src/agent/tool/tool-manager.ts +362 -0
- package/vendor/agent-root/src/agent/tool/tool-prompts.ts +242 -0
- package/vendor/agent-root/src/agent/tool/types.ts +116 -0
- package/vendor/agent-root/src/agent/tool/web-fetch.ts +227 -0
- package/vendor/agent-root/src/agent/tool/web-search.ts +208 -0
- package/vendor/agent-root/src/agent/tool/write-file.ts +497 -0
- package/vendor/agent-root/src/agent/types.ts +232 -0
- package/vendor/agent-root/src/agent/utils/__tests__/index.test.ts +18 -0
- package/vendor/agent-root/src/agent/utils/__tests__/message-utils.test.ts +610 -0
- package/vendor/agent-root/src/agent/utils/__tests__/message.test.ts +223 -0
- package/vendor/agent-root/src/agent/utils/__tests__/token.test.ts +42 -0
- package/vendor/agent-root/src/agent/utils/index.ts +16 -0
- package/vendor/agent-root/src/agent/utils/message.ts +171 -0
- package/vendor/agent-root/src/agent/utils/token.ts +28 -0
- package/vendor/agent-root/src/config/__tests__/load-config-to-env.test.ts +129 -0
- package/vendor/agent-root/src/config/__tests__/loader.test.ts +247 -0
- package/vendor/agent-root/src/config/__tests__/runtime.test.ts +88 -0
- package/vendor/agent-root/src/config/index.ts +54 -0
- package/vendor/agent-root/src/config/loader.ts +431 -0
- package/vendor/agent-root/src/config/paths.ts +30 -0
- package/vendor/agent-root/src/config/runtime.ts +163 -0
- package/vendor/agent-root/src/config/types.ts +70 -0
- package/vendor/agent-root/src/logger/index.ts +57 -0
- package/vendor/agent-root/src/logger/logger.ts +819 -0
- package/vendor/agent-root/src/logger/types.ts +150 -0
- package/vendor/agent-root/src/providers/__tests__/errors.test.ts +441 -0
- package/vendor/agent-root/src/providers/__tests__/index.test.ts +16 -0
- package/vendor/agent-root/src/providers/__tests__/openai-compatible.options.test.ts +318 -0
- package/vendor/agent-root/src/providers/__tests__/openai-compatible.test.ts +600 -0
- package/vendor/agent-root/src/providers/__tests__/registry.test.ts +449 -0
- package/vendor/agent-root/src/providers/__tests__/responses-adapter.test.ts +298 -0
- package/vendor/agent-root/src/providers/adapters/__tests__/anthropic.test.ts +354 -0
- package/vendor/agent-root/src/providers/adapters/__tests__/kimi.test.ts +58 -0
- package/vendor/agent-root/src/providers/adapters/__tests__/standard.test.ts +261 -0
- package/vendor/agent-root/src/providers/adapters/anthropic.ts +572 -0
- package/vendor/agent-root/src/providers/adapters/base.ts +131 -0
- package/vendor/agent-root/src/providers/adapters/kimi.ts +48 -0
- package/vendor/agent-root/src/providers/adapters/responses.ts +732 -0
- package/vendor/agent-root/src/providers/adapters/standard.ts +120 -0
- package/vendor/agent-root/src/providers/http/__tests__/client.timeout.test.ts +313 -0
- package/vendor/agent-root/src/providers/http/client.ts +289 -0
- package/vendor/agent-root/src/providers/http/stream-parser.ts +109 -0
- package/vendor/agent-root/src/providers/index.ts +76 -0
- package/vendor/agent-root/src/providers/kimi-headers.ts +177 -0
- package/vendor/agent-root/src/providers/openai-compatible.ts +387 -0
- package/vendor/agent-root/src/providers/registry/model-config.ts +230 -0
- package/vendor/agent-root/src/providers/registry/provider-factory.ts +123 -0
- package/vendor/agent-root/src/providers/registry.ts +135 -0
- package/vendor/agent-root/src/providers/types/api.ts +284 -0
- package/vendor/agent-root/src/providers/types/config.ts +58 -0
- package/vendor/agent-root/src/providers/types/errors.ts +323 -0
- package/vendor/agent-root/src/providers/types/index.ts +72 -0
- package/vendor/agent-root/src/providers/types/provider.ts +45 -0
- package/vendor/agent-root/src/providers/types/registry.ts +88 -0
|
@@ -0,0 +1,600 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAI Compatible Provider 测试
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
6
|
+
import { OpenAICompatibleProvider } from '../openai-compatible';
|
|
7
|
+
import { ResponsesAdapter } from '../adapters/responses';
|
|
8
|
+
import { StandardAdapter } from '../adapters/standard';
|
|
9
|
+
import { LLMBadRequestError, LLMPermanentError, LLMRetryableError } from '../types';
|
|
10
|
+
import type { Chunk, LLMGenerateOptions, LLMRequestMessage } from '../types';
|
|
11
|
+
|
|
12
|
+
async function collectChunks(stream: AsyncGenerator<Chunk>): Promise<Chunk[]> {
|
|
13
|
+
const chunks: Chunk[] = [];
|
|
14
|
+
for await (const chunk of stream) {
|
|
15
|
+
chunks.push(chunk);
|
|
16
|
+
}
|
|
17
|
+
return chunks;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function collectTextFromChunks(chunks: Chunk[]): string {
|
|
21
|
+
return chunks
|
|
22
|
+
.flatMap((chunk) => chunk.choices || [])
|
|
23
|
+
.map((choice) => choice.delta?.content || '')
|
|
24
|
+
.join('');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
describe('OpenAICompatibleProvider', () => {
|
|
28
|
+
let provider: OpenAICompatibleProvider;
|
|
29
|
+
|
|
30
|
+
beforeEach(() => {
|
|
31
|
+
vi.clearAllMocks();
|
|
32
|
+
|
|
33
|
+
const config = {
|
|
34
|
+
apiKey: 'test-api-key',
|
|
35
|
+
baseURL: 'https://api.example.com',
|
|
36
|
+
model: 'gpt-4',
|
|
37
|
+
temperature: 0.7,
|
|
38
|
+
max_tokens: 2000,
|
|
39
|
+
LLMMAX_TOKENS: 8000,
|
|
40
|
+
timeout: 30000,
|
|
41
|
+
maxRetries: 3,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
provider = new OpenAICompatibleProvider(config);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
describe('constructor', () => {
|
|
48
|
+
it('should create provider with required config', () => {
|
|
49
|
+
expect(provider.config.apiKey).toBe('test-api-key');
|
|
50
|
+
expect(provider.config.baseURL).toBe('https://api.example.com');
|
|
51
|
+
expect(provider.config.model).toBe('gpt-4');
|
|
52
|
+
expect(provider.config.temperature).toBe(0.7);
|
|
53
|
+
expect(provider.config.max_tokens).toBe(2000);
|
|
54
|
+
expect(provider.config.LLMMAX_TOKENS).toBe(8000);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('should create default adapter when none provided', () => {
|
|
58
|
+
expect(provider.adapter).toBeInstanceOf(StandardAdapter);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('should create with custom adapter', () => {
|
|
62
|
+
const customAdapter = new StandardAdapter();
|
|
63
|
+
const customProvider = new OpenAICompatibleProvider(
|
|
64
|
+
{
|
|
65
|
+
apiKey: 'test-key',
|
|
66
|
+
baseURL: 'https://api.test.com',
|
|
67
|
+
model: 'gpt-4',
|
|
68
|
+
temperature: 0.5,
|
|
69
|
+
max_tokens: 1000,
|
|
70
|
+
LLMMAX_TOKENS: 4000,
|
|
71
|
+
},
|
|
72
|
+
customAdapter
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
expect(customProvider.adapter).toBe(customAdapter);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('should normalize baseURL (remove trailing slash)', () => {
|
|
79
|
+
const providerWithSlash = new OpenAICompatibleProvider({
|
|
80
|
+
apiKey: 'test-key',
|
|
81
|
+
baseURL: 'https://api.example.com/',
|
|
82
|
+
model: 'gpt-4',
|
|
83
|
+
temperature: 0.5,
|
|
84
|
+
max_tokens: 1000,
|
|
85
|
+
LLMMAX_TOKENS: 4000,
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
expect(providerWithSlash.config.baseURL).toBe('https://api.example.com');
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('should create HTTPClient with config options', () => {
|
|
92
|
+
expect(provider.httpClient).toBeDefined();
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
describe('generate', () => {
|
|
97
|
+
const mockMessages: LLMRequestMessage[] = [{ role: 'user', content: 'Hello' }];
|
|
98
|
+
|
|
99
|
+
it('should throw bad request error for empty messages', async () => {
|
|
100
|
+
await expect(provider.generate([])).rejects.toBeInstanceOf(LLMBadRequestError);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('should make non-stream request when stream is false/undefined', async () => {
|
|
104
|
+
const mockResponse = {
|
|
105
|
+
id: 'test-id',
|
|
106
|
+
object: 'chat.completion',
|
|
107
|
+
created: 1234567890,
|
|
108
|
+
model: 'gpt-4',
|
|
109
|
+
choices: [
|
|
110
|
+
{
|
|
111
|
+
index: 0,
|
|
112
|
+
message: { role: 'assistant', content: 'Hello!' },
|
|
113
|
+
finish_reason: 'stop',
|
|
114
|
+
},
|
|
115
|
+
],
|
|
116
|
+
usage: {
|
|
117
|
+
prompt_tokens: 10,
|
|
118
|
+
completion_tokens: 5,
|
|
119
|
+
total_tokens: 15,
|
|
120
|
+
prompt_cache_miss_tokens: 10,
|
|
121
|
+
prompt_cache_hit_tokens: 0,
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
// Mock the httpClient.fetch method
|
|
126
|
+
vi.spyOn(provider.httpClient, 'fetch').mockResolvedValueOnce({
|
|
127
|
+
ok: true,
|
|
128
|
+
status: 200,
|
|
129
|
+
json: async () => mockResponse,
|
|
130
|
+
} as Response);
|
|
131
|
+
|
|
132
|
+
const result = await provider.generate(mockMessages);
|
|
133
|
+
|
|
134
|
+
expect(result).toEqual(mockResponse);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('should make streaming request via generateStream', async () => {
|
|
138
|
+
const mockChunks = [
|
|
139
|
+
'data: {"id": "chunk1", "index": 0, "choices": [{"index": 0, "delta": {"content": "Hello"}}]}\n\n',
|
|
140
|
+
'data: {"id": "chunk2", "index": 0, "choices": [{"index": 0, "delta": {"content": " World"}}]}\n\n',
|
|
141
|
+
'data: [DONE]\n\n',
|
|
142
|
+
];
|
|
143
|
+
|
|
144
|
+
// Create a mock ReadableStream
|
|
145
|
+
const stream = new ReadableStream({
|
|
146
|
+
async start(controller) {
|
|
147
|
+
for (const chunk of mockChunks) {
|
|
148
|
+
controller.enqueue(new TextEncoder().encode(chunk));
|
|
149
|
+
}
|
|
150
|
+
controller.close();
|
|
151
|
+
},
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
vi.spyOn(provider.httpClient, 'fetch').mockResolvedValueOnce({
|
|
155
|
+
ok: true,
|
|
156
|
+
status: 200,
|
|
157
|
+
body: stream,
|
|
158
|
+
} as Response);
|
|
159
|
+
|
|
160
|
+
const streamResult = provider.generateStream(mockMessages);
|
|
161
|
+
const chunks = await collectChunks(streamResult);
|
|
162
|
+
expect(chunks.length).toBeGreaterThan(0);
|
|
163
|
+
expect(collectTextFromChunks(chunks)).toBe('Hello World');
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('should reject stream=true in generate()', async () => {
|
|
167
|
+
await expect(provider.generate(mockMessages, { stream: true })).rejects.toBeInstanceOf(
|
|
168
|
+
LLMBadRequestError
|
|
169
|
+
);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it('should pass options to adapter transformRequest', async () => {
|
|
173
|
+
const mockResponse = {
|
|
174
|
+
id: 'test-id',
|
|
175
|
+
choices: [{ index: 0, message: { role: 'assistant', content: 'Hi' } }],
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
vi.spyOn(provider.httpClient, 'fetch').mockResolvedValueOnce({
|
|
179
|
+
ok: true,
|
|
180
|
+
status: 200,
|
|
181
|
+
json: async () => mockResponse,
|
|
182
|
+
} as Response);
|
|
183
|
+
|
|
184
|
+
const options: LLMGenerateOptions = {
|
|
185
|
+
model: 'gpt-3.5-turbo',
|
|
186
|
+
temperature: 0.5,
|
|
187
|
+
max_tokens: 1000,
|
|
188
|
+
stream: false,
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
await provider.generate(mockMessages, options);
|
|
192
|
+
|
|
193
|
+
expect(provider.httpClient['fetch']).toHaveBeenCalled();
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it('should include tools in request when provided', async () => {
|
|
197
|
+
const mockResponse = {
|
|
198
|
+
id: 'test-id',
|
|
199
|
+
choices: [{ index: 0, message: { role: 'assistant', content: 'Hi' } }],
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
vi.spyOn(provider.httpClient, 'fetch').mockResolvedValueOnce({
|
|
203
|
+
ok: true,
|
|
204
|
+
status: 200,
|
|
205
|
+
json: async () => mockResponse,
|
|
206
|
+
} as Response);
|
|
207
|
+
|
|
208
|
+
const tools = [
|
|
209
|
+
{
|
|
210
|
+
type: 'function',
|
|
211
|
+
function: {
|
|
212
|
+
name: 'test_tool',
|
|
213
|
+
description: 'A test tool',
|
|
214
|
+
parameters: { type: 'object', properties: {} },
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
];
|
|
218
|
+
|
|
219
|
+
await provider.generate(mockMessages, { tools });
|
|
220
|
+
|
|
221
|
+
expect(provider.httpClient['fetch']).toHaveBeenCalled();
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
describe('resolveEndpoint', () => {
|
|
226
|
+
it('should combine baseURL and endpoint path', () => {
|
|
227
|
+
expect(provider.config.baseURL).toBe('https://api.example.com');
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
describe('error handling', () => {
|
|
232
|
+
it('should throw error when fetch fails', async () => {
|
|
233
|
+
vi.spyOn(provider.httpClient, 'fetch').mockRejectedValueOnce(new Error('Network error'));
|
|
234
|
+
|
|
235
|
+
await expect(provider.generate([{ role: 'user', content: 'Hello' }])).rejects.toThrow();
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it('should handle HTTP errors', async () => {
|
|
239
|
+
vi.spyOn(provider.httpClient, 'fetch').mockRejectedValueOnce(
|
|
240
|
+
new Error('500 Internal Server Error')
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
await expect(provider.generate([{ role: 'user', content: 'Hello' }])).rejects.toThrow();
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
describe('stream response handling', () => {
|
|
248
|
+
it('should accumulate content across chunks', async () => {
|
|
249
|
+
const mockChunks = [
|
|
250
|
+
'data: {"id": "c1", "index": 0, "choices": [{"index": 0, "delta": {"content": "A"}}]}\n\n',
|
|
251
|
+
'data: {"id": "c2", "index": 0, "choices": [{"index": 0, "delta": {"content": "B"}}]}\n\n',
|
|
252
|
+
'data: {"id": "c3", "index": 0, "choices": [{"index": 0, "delta": {"content": "C"}}]}\n\n',
|
|
253
|
+
'data: [DONE]\n\n',
|
|
254
|
+
];
|
|
255
|
+
|
|
256
|
+
const stream = new ReadableStream({
|
|
257
|
+
async start(controller) {
|
|
258
|
+
for (const chunk of mockChunks) {
|
|
259
|
+
controller.enqueue(new TextEncoder().encode(chunk));
|
|
260
|
+
}
|
|
261
|
+
controller.close();
|
|
262
|
+
},
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
vi.spyOn(provider.httpClient, 'fetch').mockResolvedValueOnce({
|
|
266
|
+
ok: true,
|
|
267
|
+
status: 200,
|
|
268
|
+
body: stream,
|
|
269
|
+
} as Response);
|
|
270
|
+
|
|
271
|
+
const streamResult = provider.generateStream([{ role: 'user', content: 'Hi' }]);
|
|
272
|
+
const chunks = await collectChunks(streamResult);
|
|
273
|
+
expect(collectTextFromChunks(chunks)).toBe('ABC');
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it('should handle tool calls in stream', async () => {
|
|
277
|
+
const mockChunks = [
|
|
278
|
+
'data: {"id": "c1", "index": 0, "choices": [{"index": 0, "delta": {"tool_calls": [{"index": 0, "id": "call_1", "function": {"name": "test", "arguments": ""}}]}}]}\n\n',
|
|
279
|
+
'data: {"id": "c2", "index": 0, "choices": [{"index": 0, "delta": {"tool_calls": [{"index": 0, "function": {"arguments": "{}"}}]}}]}\n\n',
|
|
280
|
+
'data: [DONE]\n\n',
|
|
281
|
+
];
|
|
282
|
+
|
|
283
|
+
const stream = new ReadableStream({
|
|
284
|
+
async start(controller) {
|
|
285
|
+
for (const chunk of mockChunks) {
|
|
286
|
+
controller.enqueue(new TextEncoder().encode(chunk));
|
|
287
|
+
}
|
|
288
|
+
controller.close();
|
|
289
|
+
},
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
vi.spyOn(provider.httpClient, 'fetch').mockResolvedValueOnce({
|
|
293
|
+
ok: true,
|
|
294
|
+
status: 200,
|
|
295
|
+
body: stream,
|
|
296
|
+
} as Response);
|
|
297
|
+
|
|
298
|
+
const streamResult = provider.generateStream([{ role: 'user', content: 'Hi' }]);
|
|
299
|
+
const chunks = await collectChunks(streamResult);
|
|
300
|
+
const mergedCalls = new Map<number, { id?: string; name?: string; arguments?: string }>();
|
|
301
|
+
for (const chunk of chunks) {
|
|
302
|
+
for (const choice of chunk.choices || []) {
|
|
303
|
+
const toolCalls = choice.delta?.tool_calls || [];
|
|
304
|
+
for (const call of toolCalls) {
|
|
305
|
+
const index = call.index ?? 0;
|
|
306
|
+
const prev = mergedCalls.get(index) || {};
|
|
307
|
+
mergedCalls.set(index, {
|
|
308
|
+
id: call.id || prev.id,
|
|
309
|
+
name: call.function?.name || prev.name,
|
|
310
|
+
arguments: `${prev.arguments || ''}${call.function?.arguments || ''}`,
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
const call0 = mergedCalls.get(0);
|
|
316
|
+
expect(call0).toBeDefined();
|
|
317
|
+
expect(call0?.name).toBe('test');
|
|
318
|
+
expect(call0?.arguments).toBe('{}');
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
it('should not throw error for empty stream with length finish reason', async () => {
|
|
322
|
+
const mockChunks = [
|
|
323
|
+
'data: {"id": "c1", "index": 0, "choices": [{"index": 0, "finish_reason": "length"}]}\n\n',
|
|
324
|
+
'data: [DONE]\n\n',
|
|
325
|
+
];
|
|
326
|
+
|
|
327
|
+
const stream = new ReadableStream({
|
|
328
|
+
async start(controller) {
|
|
329
|
+
for (const chunk of mockChunks) {
|
|
330
|
+
controller.enqueue(new TextEncoder().encode(chunk));
|
|
331
|
+
}
|
|
332
|
+
controller.close();
|
|
333
|
+
},
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
vi.spyOn(provider.httpClient, 'fetch').mockResolvedValueOnce({
|
|
337
|
+
ok: true,
|
|
338
|
+
status: 200,
|
|
339
|
+
body: stream,
|
|
340
|
+
} as Response);
|
|
341
|
+
|
|
342
|
+
const streamResult = provider.generateStream([{ role: 'user', content: 'Hi' }]);
|
|
343
|
+
const chunks = await collectChunks(streamResult);
|
|
344
|
+
const hasLength = chunks.some((chunk) =>
|
|
345
|
+
(chunk.choices || []).some((choice) => choice.finish_reason === 'length')
|
|
346
|
+
);
|
|
347
|
+
expect(hasLength).toBe(true);
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
it('should handle empty stream with stop reason', async () => {
|
|
351
|
+
const mockChunks = [
|
|
352
|
+
'data: {"id": "c1", "index": 0, "choices": [{"index": 0, "finish_reason": "stop"}]}\n\n',
|
|
353
|
+
'data: [DONE]\n\n',
|
|
354
|
+
];
|
|
355
|
+
|
|
356
|
+
const stream = new ReadableStream({
|
|
357
|
+
async start(controller) {
|
|
358
|
+
for (const chunk of mockChunks) {
|
|
359
|
+
controller.enqueue(new TextEncoder().encode(chunk));
|
|
360
|
+
}
|
|
361
|
+
controller.close();
|
|
362
|
+
},
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
vi.spyOn(provider.httpClient, 'fetch').mockResolvedValueOnce({
|
|
366
|
+
ok: true,
|
|
367
|
+
status: 200,
|
|
368
|
+
body: stream,
|
|
369
|
+
} as Response);
|
|
370
|
+
|
|
371
|
+
const streamResult = provider.generateStream([{ role: 'user', content: 'Hi' }]);
|
|
372
|
+
const chunks = await collectChunks(streamResult);
|
|
373
|
+
const hasStop = chunks.some((chunk) =>
|
|
374
|
+
(chunk.choices || []).some((choice) => choice.finish_reason === 'stop')
|
|
375
|
+
);
|
|
376
|
+
expect(hasStop).toBe(true);
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
it('should parse SSE data lines without a space after data colon', async () => {
|
|
380
|
+
const mockChunks = [
|
|
381
|
+
'data:{"id":"c1","index":0,"choices":[{"index":0,"delta":{"content":"Hello"}}]}\n\n',
|
|
382
|
+
'data:{"id":"c2","index":0,"choices":[{"index":0,"delta":{"content":" world"}}]}\n\n',
|
|
383
|
+
'data:[DONE]\n\n',
|
|
384
|
+
];
|
|
385
|
+
|
|
386
|
+
const stream = new ReadableStream({
|
|
387
|
+
async start(controller) {
|
|
388
|
+
for (const chunk of mockChunks) {
|
|
389
|
+
controller.enqueue(new TextEncoder().encode(chunk));
|
|
390
|
+
}
|
|
391
|
+
controller.close();
|
|
392
|
+
},
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
vi.spyOn(provider.httpClient, 'fetch').mockResolvedValueOnce({
|
|
396
|
+
ok: true,
|
|
397
|
+
status: 200,
|
|
398
|
+
body: stream,
|
|
399
|
+
} as Response);
|
|
400
|
+
|
|
401
|
+
const streamResult = provider.generateStream([{ role: 'user', content: 'Hi' }]);
|
|
402
|
+
const chunks = await collectChunks(streamResult);
|
|
403
|
+
|
|
404
|
+
expect(collectTextFromChunks(chunks)).toBe('Hello world');
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
it('should update metadata from stream chunks', async () => {
|
|
408
|
+
const mockChunks = [
|
|
409
|
+
'data: {"id": "test-id", "object": "chat.completion.chunk", "created": 1234567890, "model": "gpt-4", "index": 0, "choices": [{"index": 0, "delta": {"content": "Hi"}}]}\n\n',
|
|
410
|
+
'data: [DONE]\n\n',
|
|
411
|
+
];
|
|
412
|
+
|
|
413
|
+
const stream = new ReadableStream({
|
|
414
|
+
async start(controller) {
|
|
415
|
+
for (const chunk of mockChunks) {
|
|
416
|
+
controller.enqueue(new TextEncoder().encode(chunk));
|
|
417
|
+
}
|
|
418
|
+
controller.close();
|
|
419
|
+
},
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
vi.spyOn(provider.httpClient, 'fetch').mockResolvedValueOnce({
|
|
423
|
+
ok: true,
|
|
424
|
+
status: 200,
|
|
425
|
+
body: stream,
|
|
426
|
+
} as Response);
|
|
427
|
+
|
|
428
|
+
const streamResult = provider.generateStream([{ role: 'user', content: 'Hello' }]);
|
|
429
|
+
const chunks = await collectChunks(streamResult);
|
|
430
|
+
expect(chunks[0]?.id).toBe('test-id');
|
|
431
|
+
expect(chunks[0]?.object).toBe('chat.completion.chunk');
|
|
432
|
+
expect(chunks[0]?.created).toBe(1234567890);
|
|
433
|
+
expect(chunks[0]?.model).toBe('gpt-4');
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
it('should throw permanent error when stream chunk returns permanent error code', async () => {
|
|
437
|
+
const mockChunks = [
|
|
438
|
+
'data: {"id":"err-1","index":0,"error":{"code":"invalid_request_error","message":"invalid parameter"}}\n\n',
|
|
439
|
+
'data: [DONE]\n\n',
|
|
440
|
+
];
|
|
441
|
+
|
|
442
|
+
const stream = new ReadableStream({
|
|
443
|
+
async start(controller) {
|
|
444
|
+
for (const chunk of mockChunks) {
|
|
445
|
+
controller.enqueue(new TextEncoder().encode(chunk));
|
|
446
|
+
}
|
|
447
|
+
controller.close();
|
|
448
|
+
},
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
vi.spyOn(provider.httpClient, 'fetch').mockResolvedValueOnce({
|
|
452
|
+
ok: true,
|
|
453
|
+
status: 200,
|
|
454
|
+
body: stream,
|
|
455
|
+
} as Response);
|
|
456
|
+
|
|
457
|
+
await expect(
|
|
458
|
+
collectChunks(provider.generateStream([{ role: 'user', content: 'Hi' }]))
|
|
459
|
+
).rejects.toBeInstanceOf(LLMPermanentError);
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
it('should throw retryable error when stream chunk returns transient error code', async () => {
|
|
463
|
+
const mockChunks = [
|
|
464
|
+
'data: {"id":"err-2","index":0,"error":{"code":"upstream_timeout","message":"temporary upstream timeout"}}\n\n',
|
|
465
|
+
'data: [DONE]\n\n',
|
|
466
|
+
];
|
|
467
|
+
|
|
468
|
+
const stream = new ReadableStream({
|
|
469
|
+
async start(controller) {
|
|
470
|
+
for (const chunk of mockChunks) {
|
|
471
|
+
controller.enqueue(new TextEncoder().encode(chunk));
|
|
472
|
+
}
|
|
473
|
+
controller.close();
|
|
474
|
+
},
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
vi.spyOn(provider.httpClient, 'fetch').mockResolvedValueOnce({
|
|
478
|
+
ok: true,
|
|
479
|
+
status: 200,
|
|
480
|
+
body: stream,
|
|
481
|
+
} as Response);
|
|
482
|
+
|
|
483
|
+
await expect(
|
|
484
|
+
collectChunks(provider.generateStream([{ role: 'user', content: 'Hi' }]))
|
|
485
|
+
).rejects.toBeInstanceOf(LLMRetryableError);
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
it('should classify responses response.failed events as retryable upstream errors', async () => {
|
|
489
|
+
const responsesProvider = new OpenAICompatibleProvider(
|
|
490
|
+
{
|
|
491
|
+
apiKey: 'test-api-key',
|
|
492
|
+
baseURL: 'https://api.example.com',
|
|
493
|
+
model: 'glm-5',
|
|
494
|
+
temperature: 0.7,
|
|
495
|
+
max_tokens: 2000,
|
|
496
|
+
LLMMAX_TOKENS: 8000,
|
|
497
|
+
timeout: 30000,
|
|
498
|
+
maxRetries: 3,
|
|
499
|
+
},
|
|
500
|
+
new ResponsesAdapter({ defaultModel: 'glm-5' })
|
|
501
|
+
);
|
|
502
|
+
|
|
503
|
+
const stream = new ReadableStream({
|
|
504
|
+
start(controller) {
|
|
505
|
+
controller.enqueue(
|
|
506
|
+
new TextEncoder().encode(
|
|
507
|
+
[
|
|
508
|
+
'event: response.created\n',
|
|
509
|
+
'data: {"type":"response.created","response":{"id":"resp_failed","model":"glm-5","created_at":1762675000}}\n\n',
|
|
510
|
+
'event: response.failed\n',
|
|
511
|
+
'data: {"type":"response.failed","error":{"code":"upstream_timeout","type":"server_error","message":"temporary upstream timeout"}}\n\n',
|
|
512
|
+
].join('')
|
|
513
|
+
)
|
|
514
|
+
);
|
|
515
|
+
controller.close();
|
|
516
|
+
},
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
vi.spyOn(responsesProvider.httpClient, 'fetch').mockResolvedValueOnce({
|
|
520
|
+
ok: true,
|
|
521
|
+
status: 200,
|
|
522
|
+
body: stream,
|
|
523
|
+
} as Response);
|
|
524
|
+
|
|
525
|
+
await expect(
|
|
526
|
+
collectChunks(responsesProvider.generateStream([{ role: 'user', content: 'Hi' }]))
|
|
527
|
+
).rejects.toMatchObject({
|
|
528
|
+
name: 'LLMRetryableError',
|
|
529
|
+
code: 'upstream_timeout',
|
|
530
|
+
message: 'temporary upstream timeout (chunk: resp_failed)',
|
|
531
|
+
});
|
|
532
|
+
});
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
describe('abort signal', () => {
|
|
536
|
+
it('should support abort signal in non-streaming mode', async () => {
|
|
537
|
+
const controller = new AbortController();
|
|
538
|
+
controller.abort();
|
|
539
|
+
|
|
540
|
+
// Mock fetch to reject with AbortError when signal is aborted
|
|
541
|
+
vi.spyOn(provider.httpClient, 'fetch').mockRejectedValueOnce(
|
|
542
|
+
new DOMException('Aborted', 'AbortError')
|
|
543
|
+
);
|
|
544
|
+
|
|
545
|
+
await expect(
|
|
546
|
+
provider.generate([{ role: 'user', content: 'Hello' }], {
|
|
547
|
+
abortSignal: controller.signal,
|
|
548
|
+
})
|
|
549
|
+
).rejects.toThrow();
|
|
550
|
+
}, 10000);
|
|
551
|
+
|
|
552
|
+
it('should fallback to provider timeout when abortSignal is missing', async () => {
|
|
553
|
+
const timeoutMs = 80;
|
|
554
|
+
const shortTimeoutProvider = new OpenAICompatibleProvider({
|
|
555
|
+
apiKey: 'test-api-key',
|
|
556
|
+
baseURL: 'https://api.example.com',
|
|
557
|
+
model: 'gpt-4',
|
|
558
|
+
temperature: 0.7,
|
|
559
|
+
max_tokens: 2000,
|
|
560
|
+
LLMMAX_TOKENS: 8000,
|
|
561
|
+
timeout: timeoutMs,
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
const fetchSpy = vi
|
|
565
|
+
.spyOn(globalThis, 'fetch')
|
|
566
|
+
.mockImplementation(async (_input: string | URL | Request, init?: RequestInit) => {
|
|
567
|
+
const signal = init?.signal;
|
|
568
|
+
expect(signal).toBeInstanceOf(AbortSignal);
|
|
569
|
+
|
|
570
|
+
await new Promise<void>((_resolve, reject) => {
|
|
571
|
+
if (signal?.aborted) {
|
|
572
|
+
reject(new DOMException('Aborted', 'AbortError'));
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
signal?.addEventListener(
|
|
577
|
+
'abort',
|
|
578
|
+
() => {
|
|
579
|
+
reject(new DOMException('Aborted', 'AbortError'));
|
|
580
|
+
},
|
|
581
|
+
{ once: true }
|
|
582
|
+
);
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
throw new Error('should not reach successful fetch path');
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
const result = shortTimeoutProvider.generate([{ role: 'user', content: 'Hello' }]);
|
|
589
|
+
// generate() returns Promise when not streaming
|
|
590
|
+
const error = await (result as Promise<unknown>).then(
|
|
591
|
+
() => null,
|
|
592
|
+
(err) => err
|
|
593
|
+
);
|
|
594
|
+
fetchSpy.mockRestore();
|
|
595
|
+
|
|
596
|
+
expect(error).toBeInstanceOf(Error);
|
|
597
|
+
expect((error as Error & { code?: string }).code).toBe('TIMEOUT');
|
|
598
|
+
}, 10000);
|
|
599
|
+
});
|
|
600
|
+
});
|