@standardagents/builder 0.11.12 → 0.12.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.
- package/dist/built-in-routes.js +3498 -498
- package/dist/built-in-routes.js.map +1 -1
- package/dist/client/assets/index.css +1 -1
- package/dist/client/index.js +56 -25
- package/dist/client/vendor.js +78 -5
- package/dist/client/vue.js +1 -1
- package/dist/discovery-CpMs68Yx.d.ts +97 -0
- package/dist/index-DkbUJ4MM.d.ts +1043 -0
- package/dist/index.d.ts +205 -999
- package/dist/index.js +4563 -956
- package/dist/index.js.map +1 -1
- package/dist/packing.d.ts +266 -0
- package/dist/packing.js +2202 -0
- package/dist/packing.js.map +1 -0
- package/dist/plugin.js +3477 -608
- package/dist/plugin.js.map +1 -1
- package/dist/test.d.ts +256 -0
- package/dist/test.js +391 -0
- package/dist/test.js.map +1 -0
- package/dist/types-pLkJx8vg.d.ts +306 -0
- package/package.json +25 -4
package/dist/test.d.ts
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { ProviderFactoryConfig, LLMProviderInterface, ProviderRequest, ProviderResponse, ProviderStreamChunk } from '@standardagents/spec';
|
|
2
|
+
import { l as ToolCall } from './index-DkbUJ4MM.js';
|
|
3
|
+
import '@cloudflare/workers-types';
|
|
4
|
+
import 'zod';
|
|
5
|
+
import './types-pLkJx8vg.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* TestScript - Fluent builder for defining deterministic LLM response sequences.
|
|
9
|
+
*
|
|
10
|
+
* Use this to script exact responses for tests, enabling deterministic testing
|
|
11
|
+
* of agent behavior without calling real LLM APIs.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* const script = TestScript.create()
|
|
16
|
+
* .addTextResponse("Hello! How can I help?")
|
|
17
|
+
* .addToolCallResponse([
|
|
18
|
+
* { name: "search", arguments: { query: "test" } }
|
|
19
|
+
* ])
|
|
20
|
+
* .addTextResponse("Based on the search, here's what I found...");
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* A single scripted response from the LLM
|
|
26
|
+
*/
|
|
27
|
+
interface ScriptedResponse {
|
|
28
|
+
/** Text content to return (null for pure tool calls) */
|
|
29
|
+
content?: string | null;
|
|
30
|
+
/** Tool calls to return (OpenAI format) */
|
|
31
|
+
toolCalls?: ToolCall[];
|
|
32
|
+
/** Reasoning content for o1-style models */
|
|
33
|
+
reasoningContent?: string | null;
|
|
34
|
+
/** Reasoning details for OpenRouter structured reasoning */
|
|
35
|
+
reasoningDetails?: any[];
|
|
36
|
+
/** Finish reason */
|
|
37
|
+
finishReason?: "stop" | "tool_calls" | "length" | "content_filter";
|
|
38
|
+
/** Token usage to report */
|
|
39
|
+
usage?: {
|
|
40
|
+
promptTokens?: number;
|
|
41
|
+
completionTokens?: number;
|
|
42
|
+
reasoningTokens?: number;
|
|
43
|
+
};
|
|
44
|
+
/** Simulate an error instead of a successful response */
|
|
45
|
+
error?: {
|
|
46
|
+
message: string;
|
|
47
|
+
code?: string;
|
|
48
|
+
status?: number;
|
|
49
|
+
};
|
|
50
|
+
/** Optional: Validate the input matches expectations */
|
|
51
|
+
expectInput?: InputExpectation;
|
|
52
|
+
/** Optional: Delay before responding (ms) - useful for streaming tests */
|
|
53
|
+
delayMs?: number;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Input validation expectations
|
|
57
|
+
*/
|
|
58
|
+
interface InputExpectation {
|
|
59
|
+
/** Check that a message contains this text (string or regex) */
|
|
60
|
+
containsMessage?: string | RegExp;
|
|
61
|
+
/** Check that a tool result for this tool exists */
|
|
62
|
+
containsToolResult?: {
|
|
63
|
+
toolName: string;
|
|
64
|
+
resultContains?: string;
|
|
65
|
+
};
|
|
66
|
+
/** Exact message count expected */
|
|
67
|
+
messageCount?: number;
|
|
68
|
+
/** Check that the system prompt contains this text */
|
|
69
|
+
systemPromptContains?: string | RegExp;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Configuration for streaming simulation
|
|
73
|
+
*/
|
|
74
|
+
interface StreamingConfig {
|
|
75
|
+
/** Characters per chunk */
|
|
76
|
+
chunkSize: number;
|
|
77
|
+
/** Delay between chunks in milliseconds */
|
|
78
|
+
chunkDelayMs: number;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Shorthand for creating tool calls
|
|
82
|
+
*/
|
|
83
|
+
interface ToolCallSpec {
|
|
84
|
+
name: string;
|
|
85
|
+
arguments: Record<string, any>;
|
|
86
|
+
id?: string;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Builder class for creating deterministic test scripts.
|
|
90
|
+
*
|
|
91
|
+
* Each call to addResponse/addTextResponse/etc adds a response to the sequence.
|
|
92
|
+
* When the TestProvider is used, it returns these responses in order.
|
|
93
|
+
*/
|
|
94
|
+
declare class TestScript {
|
|
95
|
+
private responses;
|
|
96
|
+
private streamingConfig?;
|
|
97
|
+
/**
|
|
98
|
+
* Create a new TestScript instance
|
|
99
|
+
*/
|
|
100
|
+
static create(): TestScript;
|
|
101
|
+
/**
|
|
102
|
+
* Add a response to the sequence
|
|
103
|
+
*/
|
|
104
|
+
addResponse(response: ScriptedResponse): this;
|
|
105
|
+
/**
|
|
106
|
+
* Add a simple text response (stops after response)
|
|
107
|
+
*/
|
|
108
|
+
addTextResponse(content: string, options?: Partial<ScriptedResponse>): this;
|
|
109
|
+
/**
|
|
110
|
+
* Add a response with tool calls
|
|
111
|
+
*/
|
|
112
|
+
addToolCallResponse(toolCalls: ToolCallSpec[], content?: string, options?: Partial<ScriptedResponse>): this;
|
|
113
|
+
/**
|
|
114
|
+
* Add an error response (simulates provider error)
|
|
115
|
+
*/
|
|
116
|
+
addErrorResponse(message: string, code?: string, status?: number, options?: Partial<ScriptedResponse>): this;
|
|
117
|
+
/**
|
|
118
|
+
* Add a response with reasoning content (for o1-style models)
|
|
119
|
+
*/
|
|
120
|
+
addReasoningResponse(content: string, reasoningContent: string, options?: Partial<ScriptedResponse>): this;
|
|
121
|
+
/**
|
|
122
|
+
* Enable streaming simulation for all responses
|
|
123
|
+
*/
|
|
124
|
+
withStreamingSimulation(config: StreamingConfig): this;
|
|
125
|
+
/**
|
|
126
|
+
* Get all scripted responses
|
|
127
|
+
*/
|
|
128
|
+
getResponses(): ScriptedResponse[];
|
|
129
|
+
/**
|
|
130
|
+
* Get streaming configuration
|
|
131
|
+
*/
|
|
132
|
+
getStreamingConfig(): StreamingConfig | undefined;
|
|
133
|
+
/**
|
|
134
|
+
* Get total number of responses
|
|
135
|
+
*/
|
|
136
|
+
get length(): number;
|
|
137
|
+
/**
|
|
138
|
+
* Check if script is empty
|
|
139
|
+
*/
|
|
140
|
+
isEmpty(): boolean;
|
|
141
|
+
/**
|
|
142
|
+
* Create a clone of this script
|
|
143
|
+
*/
|
|
144
|
+
clone(): TestScript;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* TestProvider - Deterministic LLM provider for testing.
|
|
149
|
+
*
|
|
150
|
+
* Returns scripted responses in sequence, enabling deterministic testing
|
|
151
|
+
* of agent execution without real API calls.
|
|
152
|
+
*
|
|
153
|
+
* Implements the new LLMProviderInterface with generate() and stream() methods.
|
|
154
|
+
*
|
|
155
|
+
* @example
|
|
156
|
+
* ```typescript
|
|
157
|
+
* const script = TestScript.create()
|
|
158
|
+
* .addTextResponse("Hello!")
|
|
159
|
+
* .addToolCallResponse([{ name: "search", arguments: { q: "test" } }])
|
|
160
|
+
* .addTextResponse("Found results.");
|
|
161
|
+
*
|
|
162
|
+
* const provider = createTestProvider(script);
|
|
163
|
+
* ```
|
|
164
|
+
*/
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Extended provider config for TestProvider
|
|
168
|
+
*/
|
|
169
|
+
interface TestProviderConfig extends ProviderFactoryConfig {
|
|
170
|
+
/** The test script defining response sequence. If not provided, uses a default that echoes responses. */
|
|
171
|
+
script?: TestScript;
|
|
172
|
+
/** Whether to validate inputs match expectations (default: false) */
|
|
173
|
+
validateInputs?: boolean;
|
|
174
|
+
/** Log all requests for debugging (default: false) */
|
|
175
|
+
debugLog?: boolean;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Deterministic test provider that returns scripted LLM responses.
|
|
179
|
+
*
|
|
180
|
+
* Key features:
|
|
181
|
+
* - Returns responses in sequence as defined by TestScript
|
|
182
|
+
* - Supports streaming simulation
|
|
183
|
+
* - Optional input validation
|
|
184
|
+
* - Request logging for assertions
|
|
185
|
+
* - Clear error when script is exhausted
|
|
186
|
+
*/
|
|
187
|
+
declare class TestProvider implements LLMProviderInterface {
|
|
188
|
+
readonly name = "test";
|
|
189
|
+
readonly specificationVersion: "1";
|
|
190
|
+
private script;
|
|
191
|
+
private responseIndex;
|
|
192
|
+
private validateInputs;
|
|
193
|
+
private debugLog;
|
|
194
|
+
private requestLog;
|
|
195
|
+
private useDefaultResponse;
|
|
196
|
+
constructor(config: TestProviderConfig);
|
|
197
|
+
/**
|
|
198
|
+
* Test provider supports any model ID
|
|
199
|
+
*/
|
|
200
|
+
supportsModel(_modelId: string): boolean;
|
|
201
|
+
/**
|
|
202
|
+
* Non-streaming generation - returns the next scripted response
|
|
203
|
+
*/
|
|
204
|
+
generate(request: ProviderRequest): Promise<ProviderResponse>;
|
|
205
|
+
/**
|
|
206
|
+
* Streaming generation - yields chunks for the next scripted response
|
|
207
|
+
*/
|
|
208
|
+
stream(request: ProviderRequest): Promise<AsyncIterable<ProviderStreamChunk>>;
|
|
209
|
+
/**
|
|
210
|
+
* Validate request matches expectations
|
|
211
|
+
*/
|
|
212
|
+
private validateInput;
|
|
213
|
+
/**
|
|
214
|
+
* Simple token estimation (for mock usage stats)
|
|
215
|
+
*/
|
|
216
|
+
private estimateTokens;
|
|
217
|
+
private sleep;
|
|
218
|
+
/**
|
|
219
|
+
* Get logged requests (for test assertions)
|
|
220
|
+
*/
|
|
221
|
+
getRequestLog(): ProviderRequest[];
|
|
222
|
+
/**
|
|
223
|
+
* Get the last request made
|
|
224
|
+
*/
|
|
225
|
+
getLastRequest(): ProviderRequest | undefined;
|
|
226
|
+
/**
|
|
227
|
+
* Reset provider state for reuse across tests
|
|
228
|
+
*/
|
|
229
|
+
reset(): void;
|
|
230
|
+
/**
|
|
231
|
+
* Replace the script with a new one and reset
|
|
232
|
+
*/
|
|
233
|
+
setScript(script: TestScript): void;
|
|
234
|
+
/**
|
|
235
|
+
* Check if all scripted responses were consumed
|
|
236
|
+
*/
|
|
237
|
+
isScriptComplete(): boolean;
|
|
238
|
+
/**
|
|
239
|
+
* Get remaining unconsumed response count
|
|
240
|
+
*/
|
|
241
|
+
remainingResponses(): number;
|
|
242
|
+
/**
|
|
243
|
+
* Get current response index
|
|
244
|
+
*/
|
|
245
|
+
getCurrentIndex(): number;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Factory function to create a TestProvider
|
|
249
|
+
*/
|
|
250
|
+
declare function createTestProvider(script?: TestScript): TestProvider;
|
|
251
|
+
/**
|
|
252
|
+
* ProviderFactory-compatible factory for test provider
|
|
253
|
+
*/
|
|
254
|
+
declare const test: (config: ProviderFactoryConfig) => LLMProviderInterface;
|
|
255
|
+
|
|
256
|
+
export { type InputExpectation, type ScriptedResponse, TestProvider, type TestProviderConfig, TestScript, createTestProvider, test };
|
package/dist/test.js
ADDED
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
import { ProviderError } from '@standardagents/spec';
|
|
2
|
+
|
|
3
|
+
// src/agents/providers/TestProvider.ts
|
|
4
|
+
|
|
5
|
+
// src/agents/providers/TestScript.ts
|
|
6
|
+
var TestScript = class _TestScript {
|
|
7
|
+
responses = [];
|
|
8
|
+
streamingConfig;
|
|
9
|
+
/**
|
|
10
|
+
* Create a new TestScript instance
|
|
11
|
+
*/
|
|
12
|
+
static create() {
|
|
13
|
+
return new _TestScript();
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Add a response to the sequence
|
|
17
|
+
*/
|
|
18
|
+
addResponse(response) {
|
|
19
|
+
this.responses.push(response);
|
|
20
|
+
return this;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Add a simple text response (stops after response)
|
|
24
|
+
*/
|
|
25
|
+
addTextResponse(content, options) {
|
|
26
|
+
return this.addResponse({
|
|
27
|
+
content,
|
|
28
|
+
finishReason: "stop",
|
|
29
|
+
...options
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Add a response with tool calls
|
|
34
|
+
*/
|
|
35
|
+
addToolCallResponse(toolCalls, content, options) {
|
|
36
|
+
return this.addResponse({
|
|
37
|
+
content: content ?? null,
|
|
38
|
+
toolCalls: toolCalls.map((tc) => ({
|
|
39
|
+
id: tc.id || `call_${crypto.randomUUID().substring(0, 8)}`,
|
|
40
|
+
type: "function",
|
|
41
|
+
function: {
|
|
42
|
+
name: tc.name,
|
|
43
|
+
arguments: JSON.stringify(tc.arguments)
|
|
44
|
+
}
|
|
45
|
+
})),
|
|
46
|
+
finishReason: "tool_calls",
|
|
47
|
+
...options
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Add an error response (simulates provider error)
|
|
52
|
+
*/
|
|
53
|
+
addErrorResponse(message, code, status, options) {
|
|
54
|
+
return this.addResponse({
|
|
55
|
+
error: { message, code, status },
|
|
56
|
+
...options
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Add a response with reasoning content (for o1-style models)
|
|
61
|
+
*/
|
|
62
|
+
addReasoningResponse(content, reasoningContent, options) {
|
|
63
|
+
return this.addResponse({
|
|
64
|
+
content,
|
|
65
|
+
reasoningContent,
|
|
66
|
+
finishReason: "stop",
|
|
67
|
+
...options
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Enable streaming simulation for all responses
|
|
72
|
+
*/
|
|
73
|
+
withStreamingSimulation(config) {
|
|
74
|
+
this.streamingConfig = config;
|
|
75
|
+
return this;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Get all scripted responses
|
|
79
|
+
*/
|
|
80
|
+
getResponses() {
|
|
81
|
+
return [...this.responses];
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Get streaming configuration
|
|
85
|
+
*/
|
|
86
|
+
getStreamingConfig() {
|
|
87
|
+
return this.streamingConfig;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Get total number of responses
|
|
91
|
+
*/
|
|
92
|
+
get length() {
|
|
93
|
+
return this.responses.length;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Check if script is empty
|
|
97
|
+
*/
|
|
98
|
+
isEmpty() {
|
|
99
|
+
return this.responses.length === 0;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Create a clone of this script
|
|
103
|
+
*/
|
|
104
|
+
clone() {
|
|
105
|
+
const cloned = new _TestScript();
|
|
106
|
+
cloned.responses = [...this.responses];
|
|
107
|
+
cloned.streamingConfig = this.streamingConfig ? { ...this.streamingConfig } : void 0;
|
|
108
|
+
return cloned;
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
// src/agents/providers/TestProvider.ts
|
|
113
|
+
function contentToString(content) {
|
|
114
|
+
if (!content) return "";
|
|
115
|
+
if (typeof content === "string") return content;
|
|
116
|
+
return content.filter((part) => part.type === "text").map((part) => part.text).join(" ");
|
|
117
|
+
}
|
|
118
|
+
var TestProvider = class {
|
|
119
|
+
name = "test";
|
|
120
|
+
specificationVersion = "1";
|
|
121
|
+
script;
|
|
122
|
+
responseIndex = 0;
|
|
123
|
+
validateInputs;
|
|
124
|
+
debugLog;
|
|
125
|
+
requestLog = [];
|
|
126
|
+
useDefaultResponse;
|
|
127
|
+
constructor(config) {
|
|
128
|
+
this.useDefaultResponse = !config.script;
|
|
129
|
+
this.script = config.script ?? TestScript.create().addTextResponse("Test response received.");
|
|
130
|
+
this.validateInputs = config.validateInputs ?? false;
|
|
131
|
+
this.debugLog = config.debugLog ?? false;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Test provider supports any model ID
|
|
135
|
+
*/
|
|
136
|
+
supportsModel(_modelId) {
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Non-streaming generation - returns the next scripted response
|
|
141
|
+
*/
|
|
142
|
+
async generate(request) {
|
|
143
|
+
if (this.debugLog) {
|
|
144
|
+
console.log(`[TestProvider] Request ${this.responseIndex + 1}:`, {
|
|
145
|
+
messageCount: request.messages.length,
|
|
146
|
+
lastMessage: request.messages.slice(-1)[0]
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
this.requestLog.push({ ...request });
|
|
150
|
+
if (request.signal?.aborted) {
|
|
151
|
+
throw new ProviderError("Request aborted", "timeout");
|
|
152
|
+
}
|
|
153
|
+
const responses = this.script.getResponses();
|
|
154
|
+
let scripted;
|
|
155
|
+
if (this.responseIndex >= responses.length) {
|
|
156
|
+
if (this.useDefaultResponse) {
|
|
157
|
+
scripted = responses[0];
|
|
158
|
+
} else {
|
|
159
|
+
const lastMessage = request.messages.slice(-1)[0];
|
|
160
|
+
const lastContent = lastMessage?.role === "user" ? contentToString(lastMessage.content) : lastMessage?.role === "assistant" ? lastMessage.content ?? "" : "";
|
|
161
|
+
throw new ProviderError(
|
|
162
|
+
`TestProvider: Script exhausted after ${this.responseIndex} responses. Expected ${responses.length} total requests. Received request with ${request.messages.length} messages. Last message role: "${lastMessage?.role}", content: "${String(lastContent).substring(0, 100)}..."`,
|
|
163
|
+
"invalid_request"
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
} else {
|
|
167
|
+
scripted = responses[this.responseIndex];
|
|
168
|
+
}
|
|
169
|
+
this.responseIndex++;
|
|
170
|
+
if (this.validateInputs && scripted.expectInput) {
|
|
171
|
+
this.validateInput(request, scripted.expectInput);
|
|
172
|
+
}
|
|
173
|
+
if (scripted.delayMs) {
|
|
174
|
+
await this.sleep(scripted.delayMs);
|
|
175
|
+
}
|
|
176
|
+
if (scripted.error) {
|
|
177
|
+
throw new ProviderError(
|
|
178
|
+
scripted.error.message,
|
|
179
|
+
scripted.error.code || "unknown",
|
|
180
|
+
scripted.error.status
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
const promptTokens = scripted.usage?.promptTokens ?? this.estimateTokens(request);
|
|
184
|
+
const completionTokens = scripted.usage?.completionTokens ?? Math.ceil((scripted.content?.length ?? 0) / 4);
|
|
185
|
+
const reasoningTokens = scripted.usage?.reasoningTokens ?? 0;
|
|
186
|
+
const toolCalls = scripted.toolCalls?.map((tc) => ({
|
|
187
|
+
id: tc.id,
|
|
188
|
+
name: tc.function.name,
|
|
189
|
+
arguments: JSON.parse(tc.function.arguments)
|
|
190
|
+
}));
|
|
191
|
+
const finishReason = scripted.finishReason === "tool_calls" ? "tool_calls" : "stop";
|
|
192
|
+
return {
|
|
193
|
+
content: scripted.content ?? null,
|
|
194
|
+
reasoning: scripted.reasoningContent ?? null,
|
|
195
|
+
reasoningDetails: scripted.reasoningDetails,
|
|
196
|
+
toolCalls,
|
|
197
|
+
finishReason,
|
|
198
|
+
usage: {
|
|
199
|
+
promptTokens,
|
|
200
|
+
completionTokens,
|
|
201
|
+
totalTokens: promptTokens + completionTokens,
|
|
202
|
+
reasoningTokens: reasoningTokens || void 0
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Streaming generation - yields chunks for the next scripted response
|
|
208
|
+
*/
|
|
209
|
+
async stream(request) {
|
|
210
|
+
const response = await this.generate(request);
|
|
211
|
+
const streamingConfig = this.script.getStreamingConfig();
|
|
212
|
+
const self = this;
|
|
213
|
+
return {
|
|
214
|
+
async *[Symbol.asyncIterator]() {
|
|
215
|
+
if (request.signal?.aborted) {
|
|
216
|
+
yield { type: "error", error: "Request aborted", code: "timeout" };
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
if (response.content) {
|
|
220
|
+
if (streamingConfig) {
|
|
221
|
+
for (let i = 0; i < response.content.length; i += streamingConfig.chunkSize) {
|
|
222
|
+
const chunk = response.content.substring(i, i + streamingConfig.chunkSize);
|
|
223
|
+
yield { type: "content-delta", delta: chunk };
|
|
224
|
+
if (streamingConfig.chunkDelayMs > 0) {
|
|
225
|
+
await self.sleep(streamingConfig.chunkDelayMs);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
} else {
|
|
229
|
+
yield { type: "content-delta", delta: response.content };
|
|
230
|
+
}
|
|
231
|
+
yield { type: "content-done" };
|
|
232
|
+
}
|
|
233
|
+
if (response.reasoning) {
|
|
234
|
+
yield { type: "reasoning-delta", delta: response.reasoning };
|
|
235
|
+
yield { type: "reasoning-done" };
|
|
236
|
+
}
|
|
237
|
+
if (response.toolCalls) {
|
|
238
|
+
for (const tc of response.toolCalls) {
|
|
239
|
+
yield { type: "tool-call-start", id: tc.id, name: tc.name };
|
|
240
|
+
yield { type: "tool-call-done", id: tc.id, arguments: tc.arguments };
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
yield {
|
|
244
|
+
type: "finish",
|
|
245
|
+
finishReason: response.finishReason,
|
|
246
|
+
usage: response.usage
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Validate request matches expectations
|
|
253
|
+
*/
|
|
254
|
+
validateInput(request, expectations) {
|
|
255
|
+
if (expectations.messageCount !== void 0) {
|
|
256
|
+
if (request.messages.length !== expectations.messageCount) {
|
|
257
|
+
throw new ProviderError(
|
|
258
|
+
`TestProvider: Expected ${expectations.messageCount} messages, got ${request.messages.length}`,
|
|
259
|
+
"invalid_request"
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
if (expectations.containsMessage) {
|
|
264
|
+
const pattern = expectations.containsMessage;
|
|
265
|
+
const found = request.messages.some((m) => {
|
|
266
|
+
const content = m.role === "user" ? contentToString(m.content) : m.role === "assistant" ? m.content : "";
|
|
267
|
+
if (!content) return false;
|
|
268
|
+
return typeof pattern === "string" ? content.includes(pattern) : pattern.test(content);
|
|
269
|
+
});
|
|
270
|
+
if (!found) {
|
|
271
|
+
throw new ProviderError(
|
|
272
|
+
`TestProvider: Expected message containing "${pattern}" not found`,
|
|
273
|
+
"invalid_request"
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
if (expectations.containsToolResult) {
|
|
278
|
+
const { toolName, resultContains } = expectations.containsToolResult;
|
|
279
|
+
const found = request.messages.some((m) => {
|
|
280
|
+
if (m.role !== "tool") return false;
|
|
281
|
+
const content = typeof m.content === "string" ? m.content : "";
|
|
282
|
+
return !resultContains || content.includes(resultContains);
|
|
283
|
+
});
|
|
284
|
+
if (!found) {
|
|
285
|
+
throw new ProviderError(
|
|
286
|
+
`TestProvider: Expected tool result for "${toolName}" not found`,
|
|
287
|
+
"invalid_request"
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
if (expectations.systemPromptContains) {
|
|
292
|
+
const pattern = expectations.systemPromptContains;
|
|
293
|
+
const systemMessage = request.messages.find((m) => m.role === "system");
|
|
294
|
+
const systemContent = systemMessage?.role === "system" ? systemMessage.content : "";
|
|
295
|
+
if (!systemContent) {
|
|
296
|
+
throw new ProviderError(`TestProvider: No system message found`, "invalid_request");
|
|
297
|
+
}
|
|
298
|
+
const matches = typeof pattern === "string" ? systemContent.includes(pattern) : pattern.test(systemContent);
|
|
299
|
+
if (!matches) {
|
|
300
|
+
throw new ProviderError(
|
|
301
|
+
`TestProvider: System prompt does not contain "${pattern}"`,
|
|
302
|
+
"invalid_request"
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Simple token estimation (for mock usage stats)
|
|
309
|
+
*/
|
|
310
|
+
estimateTokens(request) {
|
|
311
|
+
return request.messages.reduce((sum, m) => {
|
|
312
|
+
let content = "";
|
|
313
|
+
if (m.role === "system") {
|
|
314
|
+
content = m.content;
|
|
315
|
+
} else if (m.role === "user") {
|
|
316
|
+
content = contentToString(m.content);
|
|
317
|
+
} else if (m.role === "assistant") {
|
|
318
|
+
content = m.content ?? "";
|
|
319
|
+
} else if (m.role === "tool") {
|
|
320
|
+
content = typeof m.content === "string" ? m.content : "";
|
|
321
|
+
}
|
|
322
|
+
return sum + Math.ceil(content.length / 4);
|
|
323
|
+
}, 0);
|
|
324
|
+
}
|
|
325
|
+
sleep(ms) {
|
|
326
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
327
|
+
}
|
|
328
|
+
// ============ Test Utility Methods ============
|
|
329
|
+
/**
|
|
330
|
+
* Get logged requests (for test assertions)
|
|
331
|
+
*/
|
|
332
|
+
getRequestLog() {
|
|
333
|
+
return [...this.requestLog];
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Get the last request made
|
|
337
|
+
*/
|
|
338
|
+
getLastRequest() {
|
|
339
|
+
return this.requestLog[this.requestLog.length - 1];
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Reset provider state for reuse across tests
|
|
343
|
+
*/
|
|
344
|
+
reset() {
|
|
345
|
+
this.responseIndex = 0;
|
|
346
|
+
this.requestLog = [];
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Replace the script with a new one and reset
|
|
350
|
+
*/
|
|
351
|
+
setScript(script) {
|
|
352
|
+
this.script = script;
|
|
353
|
+
this.reset();
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Check if all scripted responses were consumed
|
|
357
|
+
*/
|
|
358
|
+
isScriptComplete() {
|
|
359
|
+
return this.responseIndex === this.script.length;
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Get remaining unconsumed response count
|
|
363
|
+
*/
|
|
364
|
+
remainingResponses() {
|
|
365
|
+
return this.script.length - this.responseIndex;
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Get current response index
|
|
369
|
+
*/
|
|
370
|
+
getCurrentIndex() {
|
|
371
|
+
return this.responseIndex;
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
function createTestProvider(script) {
|
|
375
|
+
return new TestProvider({
|
|
376
|
+
apiKey: "test",
|
|
377
|
+
script
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
var test = (config) => {
|
|
381
|
+
return new TestProvider({
|
|
382
|
+
...config,
|
|
383
|
+
script: config.script,
|
|
384
|
+
validateInputs: config.validateInputs,
|
|
385
|
+
debugLog: config.debugLog
|
|
386
|
+
});
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
export { TestProvider, TestScript, createTestProvider, test };
|
|
390
|
+
//# sourceMappingURL=test.js.map
|
|
391
|
+
//# sourceMappingURL=test.js.map
|