@superdurable/connectors-sdk 0.1.0 → 0.2.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.
- package/README.md +27 -3
- package/dist/{chunk-2NBM7P4T.js → chunk-32YJXBE4.js} +1 -1
- package/dist/chunk-466BPS3M.js +336 -0
- package/dist/{chunk-IVJLNZVL.js → chunk-BG65RPGH.js} +70 -15
- package/dist/{chunk-SOXYPIMO.js → chunk-BQGYGCOQ.js} +35 -73
- package/dist/chunk-IHG6ADQG.js +44 -0
- package/dist/chunk-NQQJS6OC.js +316 -0
- package/dist/{chunk-KD5DYYSU.js → chunk-QDMBAWXD.js} +1 -1
- package/dist/connectormock/index.d.ts +3 -2
- package/dist/connectormock/index.js +5 -3
- package/dist/credentials-JqG97kJ5.d.ts +102 -0
- package/dist/index.d.ts +6 -4
- package/dist/index.js +19 -291
- package/dist/oauthtoken/index.d.ts +36 -2
- package/dist/oauthtoken/index.js +326 -5
- package/dist/{operation-Ca8rfacf.d.ts → operation-CASLOg86.d.ts} +2 -1
- package/dist/projectconfig/index.d.ts +2 -1
- package/dist/projectconfig/index.js +5 -3
- package/dist/providerhttp/index.d.ts +21 -4
- package/dist/providerhttp/index.js +3 -1
- package/dist/query-CeUaW9xU.d.ts +42 -0
- package/dist/singledispatch/index.d.ts +2 -1
- package/dist/singledispatch/index.js +2 -1
- package/dist/textgen/index.d.ts +15 -0
- package/dist/textgen/index.js +628 -0
- package/dist/textgen/openaichat/index.d.ts +44 -0
- package/dist/textgen/openaichat/index.js +232 -0
- package/dist/textgen/textgentest/index.d.ts +106 -0
- package/dist/textgen/textgentest/index.js +537 -0
- package/dist/{credentials-vrvW0d4j.d.ts → types-D6De3sry.d.ts} +1 -100
- package/dist/wire_format-stEVruMs.d.ts +190 -0
- package/package.json +14 -2
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ProviderReportedError,
|
|
3
|
+
temperatureAccepted,
|
|
4
|
+
validateStructuredOutputRules
|
|
5
|
+
} from "../../chunk-466BPS3M.js";
|
|
6
|
+
import {
|
|
7
|
+
readErrorTokens
|
|
8
|
+
} from "../../chunk-BG65RPGH.js";
|
|
9
|
+
import "../../chunk-IHG6ADQG.js";
|
|
10
|
+
|
|
11
|
+
// src/textgen/openaichat/index.ts
|
|
12
|
+
function requestField(value) {
|
|
13
|
+
return /^[a-z][a-z0-9_]{0,63}$/.test(value);
|
|
14
|
+
}
|
|
15
|
+
function validateSettings(field, streaming, efforts, structured) {
|
|
16
|
+
if (field && field !== "max_tokens" && field !== "max_completion_tokens") throw new TypeError("profile max tokens field is invalid");
|
|
17
|
+
if (streaming && streaming !== "never" && streaming !== "always") throw new TypeError("profile streaming policy is invalid");
|
|
18
|
+
for (const [effort, value] of Object.entries(efforts ?? {})) {
|
|
19
|
+
if (!["none", "minimal", "low", "medium", "high", "xhigh", "max"].includes(effort)) throw new TypeError(`profile reasoning effort ${JSON.stringify(effort)} is unknown`);
|
|
20
|
+
if (!value || !requestField(value)) throw new TypeError("profile reasoning effort wire value is invalid");
|
|
21
|
+
}
|
|
22
|
+
if (structured) validateStructuredOutputRules(structured);
|
|
23
|
+
}
|
|
24
|
+
function newWireFormat(profile) {
|
|
25
|
+
if (!profile) throw new TypeError("openaichat profile is required");
|
|
26
|
+
if (!profile.providerName?.trim()) throw new TypeError("openaichat profile provider name is required");
|
|
27
|
+
if (profile.chatCompletionsPath && (!profile.chatCompletionsPath.startsWith("/") || /[?# ]/.test(profile.chatCompletionsPath))) throw new TypeError("openaichat profile chat completions path is invalid");
|
|
28
|
+
if (profile.instructionsRole && !["system", "developer"].includes(profile.instructionsRole)) throw new TypeError("openaichat profile instructions role is invalid");
|
|
29
|
+
validateSettings(profile.maxTokensField, profile.streaming, profile.reasoningEfforts, profile.structuredOutput);
|
|
30
|
+
const credentialHeader = profile.credentialHeader?.name ? profile.credentialHeader : { name: "Authorization", prefix: "Bearer " };
|
|
31
|
+
const reserved = new Set([credentialHeader.name, "Content-Type", "Content-Length", "Accept", "Host"].map((name) => name.toLowerCase()));
|
|
32
|
+
for (const [name, value] of Object.entries(profile.fixedHeaders ?? {})) {
|
|
33
|
+
if (!/^[!#$%&'*+.^_`|~0-9A-Za-z-]{1,128}$/.test(name) || reserved.has(name.toLowerCase()) || !/^[\x20-\x7e]*$/.test(value)) throw new TypeError("openaichat profile fixed header is invalid or reserved");
|
|
34
|
+
}
|
|
35
|
+
for (const field of profile.allowedRequestFields ?? []) if (!requestField(field)) throw new TypeError("openaichat profile allowed request field is invalid");
|
|
36
|
+
for (const [index, rule] of (profile.modelRules ?? []).entries()) {
|
|
37
|
+
if (!!rule.modelIDPrefix === !!rule.modelIDPattern) throw new TypeError(`openaichat profile model rule ${index} must set exactly one model matcher`);
|
|
38
|
+
if (rule.modelIDPattern && (!rule.modelIDPattern.startsWith("^") || !rule.modelIDPattern.endsWith("$"))) throw new TypeError(`openaichat profile model rule ${index} pattern must be anchored`);
|
|
39
|
+
if (rule.modelIDPattern) {
|
|
40
|
+
try {
|
|
41
|
+
new RegExp(rule.modelIDPattern);
|
|
42
|
+
} catch {
|
|
43
|
+
throw new TypeError(`openaichat profile model rule ${index} pattern is invalid`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
validateSettings(rule.maxTokensField, rule.streaming, rule.reasoningEfforts, rule.structuredOutput);
|
|
47
|
+
}
|
|
48
|
+
const allowed = profile.allowedRequestFields?.length ? new Set(profile.allowedRequestFields) : void 0;
|
|
49
|
+
if (allowed) {
|
|
50
|
+
const required = ["model", "messages"];
|
|
51
|
+
if (profile.streaming === "always" || profile.modelRules?.some((rule) => rule.streaming === "always")) required.push("stream");
|
|
52
|
+
if (required.includes("stream") && profile.shouldRequestStreamUsage) required.push("stream_options");
|
|
53
|
+
for (const field of required) if (!allowed.has(field)) throw new TypeError(`openaichat profile allowed request fields must include ${JSON.stringify(field)}, which the wire format sends`);
|
|
54
|
+
}
|
|
55
|
+
const baseSettings = {
|
|
56
|
+
rules: {
|
|
57
|
+
temperature: profile.temperature ?? temperatureAccepted(),
|
|
58
|
+
reasoningEfforts: profile.reasoningEfforts ?? {},
|
|
59
|
+
structuredOutput: profile.structuredOutput ?? { mode: "none" }
|
|
60
|
+
},
|
|
61
|
+
maxTokensField: profile.maxTokensField ?? "max_tokens",
|
|
62
|
+
streaming: profile.streaming ?? "never"
|
|
63
|
+
};
|
|
64
|
+
const settingsForModel = (model) => {
|
|
65
|
+
for (const rule of profile.modelRules ?? []) {
|
|
66
|
+
const matches = rule.modelIDPrefix ? model.startsWith(rule.modelIDPrefix) : new RegExp(rule.modelIDPattern ?? "^$").test(model);
|
|
67
|
+
if (!matches) continue;
|
|
68
|
+
return {
|
|
69
|
+
rules: {
|
|
70
|
+
temperature: rule.temperature ?? baseSettings.rules.temperature,
|
|
71
|
+
reasoningEfforts: rule.reasoningEfforts ?? baseSettings.rules.reasoningEfforts,
|
|
72
|
+
structuredOutput: rule.structuredOutput ?? baseSettings.rules.structuredOutput
|
|
73
|
+
},
|
|
74
|
+
maxTokensField: rule.maxTokensField ?? baseSettings.maxTokensField,
|
|
75
|
+
streaming: rule.streaming ?? baseSettings.streaming
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
return baseSettings;
|
|
79
|
+
};
|
|
80
|
+
const pointers = profile.errorTokenPointers ?? ["/error/type", "/error/code"];
|
|
81
|
+
const reportedError = (body) => new ProviderReportedError(readErrorTokens(body, pointers));
|
|
82
|
+
return {
|
|
83
|
+
providerName: profile.providerName,
|
|
84
|
+
modelIDRule: "body",
|
|
85
|
+
features: { supportsInstructions: true, supportsStructuredOutput: true, supportsMaxOutputTokens: true, supportsTemperature: true, supportsReasoningEffort: true },
|
|
86
|
+
credentialHeader,
|
|
87
|
+
rulesForModel: (model) => settingsForModel(model).rules,
|
|
88
|
+
encodeRequest: ({ request, rules }) => {
|
|
89
|
+
const settings = settingsForModel(request.model ?? "");
|
|
90
|
+
const messages = [];
|
|
91
|
+
if (request.instructions) messages.push({ role: profile.instructionsRole ?? "system", content: request.instructions });
|
|
92
|
+
for (const message of request.messages) messages.push({ role: message.role, content: message.text });
|
|
93
|
+
const body = { model: request.model, messages };
|
|
94
|
+
if (request.maxOutputTokens) body[settings.maxTokensField] = request.maxOutputTokens;
|
|
95
|
+
if (request.temperature != null) body.temperature = request.temperature;
|
|
96
|
+
if (request.reasoningEffort) body.reasoning_effort = rules.reasoningEfforts[request.reasoningEffort];
|
|
97
|
+
if (request.structuredOutput) {
|
|
98
|
+
if (rules.structuredOutput.mode === "jsonSchema") {
|
|
99
|
+
const jsonSchema = { name: request.structuredOutput.name, schema: request.structuredOutput.schema };
|
|
100
|
+
if (request.structuredOutput.description) jsonSchema.description = request.structuredOutput.description;
|
|
101
|
+
if (profile.shouldSendStrictJSONSchema) jsonSchema.strict = true;
|
|
102
|
+
body.response_format = { type: "json_schema", json_schema: jsonSchema };
|
|
103
|
+
} else if (rules.structuredOutput.mode === "jsonObjectWithInstruction") body.response_format = { type: "json_object" };
|
|
104
|
+
}
|
|
105
|
+
const isStreaming = settings.streaming === "always";
|
|
106
|
+
if (isStreaming) {
|
|
107
|
+
body.stream = true;
|
|
108
|
+
if (profile.shouldRequestStreamUsage) body.stream_options = { include_usage: true };
|
|
109
|
+
}
|
|
110
|
+
if (allowed) {
|
|
111
|
+
for (const field of Object.keys(body).sort()) if (!allowed.has(field)) throw new TypeError(`the provider does not accept the ${JSON.stringify(field)} request field`);
|
|
112
|
+
}
|
|
113
|
+
return { path: profile.chatCompletionsPath ?? "/chat/completions", body: Buffer.from(JSON.stringify(body)), headers: { ...profile.fixedHeaders }, isStreaming };
|
|
114
|
+
},
|
|
115
|
+
decodeResponse: (body) => {
|
|
116
|
+
const completion = parseObject(body);
|
|
117
|
+
if (completion.error !== void 0 && completion.error !== null) throw reportedError(body);
|
|
118
|
+
validateCompletion(completion);
|
|
119
|
+
const choice = firstChoice(completion.choices);
|
|
120
|
+
if (!choice) throw new TypeError("chat completion has no choice");
|
|
121
|
+
const response = { parts: [], servedModel: stringOrEmpty(completion.model), responseId: stringOrEmpty(completion.id), usage: readUsage(completion.usage) };
|
|
122
|
+
appendDelta(response, choice.message, void 0);
|
|
123
|
+
if (typeof choice.finish_reason === "string") response.providerFinishReason = choice.finish_reason;
|
|
124
|
+
return response;
|
|
125
|
+
},
|
|
126
|
+
decodeStreamEvent: (event, state, writeTextDelta) => {
|
|
127
|
+
if (event.data.trim() === "[DONE]") {
|
|
128
|
+
if (!state.hasFinished) throw new TypeError("chat completion stream ended without a finish reason");
|
|
129
|
+
state.isDone = true;
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
const chunk = parseObject(Buffer.from(event.data));
|
|
133
|
+
if (chunk.error !== void 0 && chunk.error !== null) throw reportedError(Buffer.from(event.data));
|
|
134
|
+
validateCompletion(chunk);
|
|
135
|
+
state.response.responseId ||= stringOrEmpty(chunk.id);
|
|
136
|
+
state.response.servedModel ||= stringOrEmpty(chunk.model);
|
|
137
|
+
if (chunk.usage !== void 0 && chunk.usage !== null) state.response.usage = readUsage(chunk.usage);
|
|
138
|
+
const choice = firstChoice(chunk.choices);
|
|
139
|
+
if (!choice) return;
|
|
140
|
+
appendDelta(state.response, choice.delta, writeTextDelta);
|
|
141
|
+
if (typeof choice.finish_reason === "string" && choice.finish_reason) {
|
|
142
|
+
state.response.providerFinishReason = choice.finish_reason;
|
|
143
|
+
state.hasFinished = true;
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
finishReasons: { stop: "stop", length: "length", content_filter: "contentPolicy", ...profile.finishReasons },
|
|
147
|
+
errorRules: [...profile.errorRules ?? []],
|
|
148
|
+
errorTokenPointers: [...pointers],
|
|
149
|
+
requestIDHeaders: [...profile.requestIDHeaders ?? ["x-request-id"]],
|
|
150
|
+
rateLimitHeaders: [...profile.rateLimitHeaders ?? []],
|
|
151
|
+
stallTimeoutMs: profile.stallTimeoutMs ?? 0
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
function parseObject(body) {
|
|
155
|
+
const parsed = JSON.parse(Buffer.from(body).toString());
|
|
156
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new TypeError("completion must be an object");
|
|
157
|
+
return parsed;
|
|
158
|
+
}
|
|
159
|
+
function stringOrEmpty(value) {
|
|
160
|
+
return typeof value === "string" ? value : "";
|
|
161
|
+
}
|
|
162
|
+
function firstChoice(value) {
|
|
163
|
+
return Array.isArray(value) ? value.find((choice) => choice && typeof choice === "object" && (choice.index == null || choice.index === 0)) : void 0;
|
|
164
|
+
}
|
|
165
|
+
function validateCompletion(completion) {
|
|
166
|
+
for (const field of ["id", "model"]) if (completion[field] != null && typeof completion[field] !== "string") throw new TypeError("chat completion identifier must be text");
|
|
167
|
+
if (completion.choices != null && !Array.isArray(completion.choices)) throw new TypeError("chat completion choices must be an array");
|
|
168
|
+
for (const choice of completion.choices ?? []) {
|
|
169
|
+
if (choice === null) continue;
|
|
170
|
+
if (typeof choice !== "object" || Array.isArray(choice)) throw new TypeError("chat completion choice must be an object");
|
|
171
|
+
const fields = choice;
|
|
172
|
+
if (fields.index != null && (!Number.isInteger(fields.index) || !Number.isSafeInteger(fields.index))) throw new TypeError("chat completion choice index must be an integer");
|
|
173
|
+
if (fields.finish_reason != null && typeof fields.finish_reason !== "string") throw new TypeError("chat completion finish reason must be text");
|
|
174
|
+
for (const name of ["message", "delta"]) if (fields[name] != null && (typeof fields[name] !== "object" || Array.isArray(fields[name]))) throw new TypeError("chat completion delta must be an object");
|
|
175
|
+
}
|
|
176
|
+
if (completion.usage != null && (typeof completion.usage !== "object" || Array.isArray(completion.usage))) throw new TypeError("chat completion usage must be an object");
|
|
177
|
+
if (completion.usage != null) {
|
|
178
|
+
const usage = completion.usage;
|
|
179
|
+
for (const name of ["prompt_tokens_details", "completion_tokens_details"]) {
|
|
180
|
+
if (usage[name] != null && (typeof usage[name] !== "object" || Array.isArray(usage[name]))) throw new TypeError("chat completion usage details must be an object");
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
function appendDelta(response, value, writeTextDelta) {
|
|
185
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return;
|
|
186
|
+
const delta = value;
|
|
187
|
+
if (delta.reasoning_content !== void 0 && delta.reasoning_content !== null && typeof delta.reasoning_content !== "string") throw new TypeError("chat reasoning content must be text");
|
|
188
|
+
if (delta.refusal !== void 0 && delta.refusal !== null && typeof delta.refusal !== "string") throw new TypeError("chat refusal must be text");
|
|
189
|
+
if (typeof delta.reasoning_content === "string" && delta.reasoning_content) appendPart(response, { text: delta.reasoning_content, isReasoning: true });
|
|
190
|
+
if (typeof delta.refusal === "string" && delta.refusal) response.isRefusal = true;
|
|
191
|
+
const content = delta.content;
|
|
192
|
+
let texts = [];
|
|
193
|
+
if (typeof content === "string") texts = [content];
|
|
194
|
+
else if (Array.isArray(content)) {
|
|
195
|
+
for (const part of content) {
|
|
196
|
+
if (part === null) continue;
|
|
197
|
+
if (!part || typeof part !== "object" || Array.isArray(part)) throw new TypeError("chat content part must be an object");
|
|
198
|
+
if (part.type != null && typeof part.type !== "string" || part.text != null && typeof part.text !== "string") throw new TypeError("chat content part fields must be text");
|
|
199
|
+
if (part.type === "text" && part.text) texts.push(part.text);
|
|
200
|
+
}
|
|
201
|
+
} else if (content !== void 0 && content !== null) throw new TypeError("chat content must be text or typed parts");
|
|
202
|
+
for (const text of texts) if (text) {
|
|
203
|
+
appendPart(response, { text });
|
|
204
|
+
writeTextDelta?.(text);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
function appendPart(response, part) {
|
|
208
|
+
const last = response.parts.at(-1);
|
|
209
|
+
if (last && !!last.isReasoning === !!part.isReasoning) last.text += part.text;
|
|
210
|
+
else response.parts.push(part);
|
|
211
|
+
}
|
|
212
|
+
function readUsage(value) {
|
|
213
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
|
|
214
|
+
const usage = value;
|
|
215
|
+
const tokenCount = (item) => {
|
|
216
|
+
if (item == null) return 0;
|
|
217
|
+
if (typeof item !== "number" || !Number.isSafeInteger(item)) throw new TypeError("chat token count must be an integer");
|
|
218
|
+
return item;
|
|
219
|
+
};
|
|
220
|
+
const prompt = usage.prompt_tokens_details;
|
|
221
|
+
const completion = usage.completion_tokens_details;
|
|
222
|
+
return {
|
|
223
|
+
inputTokens: tokenCount(usage.prompt_tokens),
|
|
224
|
+
outputTokens: tokenCount(usage.completion_tokens),
|
|
225
|
+
totalTokens: tokenCount(usage.total_tokens),
|
|
226
|
+
cachedInputTokens: tokenCount(prompt?.cached_tokens),
|
|
227
|
+
reasoningTokens: tokenCount(completion?.reasoning_tokens)
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
export {
|
|
231
|
+
newWireFormat
|
|
232
|
+
};
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { C as CredentialHeader, U as Usage, T as TextGenerationRequest } from '../../wire_format-stEVruMs.js';
|
|
2
|
+
import { T as TextGenerationQuery } from '../../query-CeUaW9xU.js';
|
|
3
|
+
import '../../types-D6De3sry.js';
|
|
4
|
+
import 'zod';
|
|
5
|
+
import '../../credentials-JqG97kJ5.js';
|
|
6
|
+
import '@superdurable/dex';
|
|
7
|
+
import '../../providerhttp/index.js';
|
|
8
|
+
import 'node:http';
|
|
9
|
+
import '../../operation-CASLOg86.js';
|
|
10
|
+
|
|
11
|
+
/** One delayed piece of an event-stream reply. Durations are milliseconds. */
|
|
12
|
+
interface FakeStreamChunk {
|
|
13
|
+
/** Delay after the prior piece. */ delayMs?: number;
|
|
14
|
+
/** Bytes to flush. */ data: string;
|
|
15
|
+
}
|
|
16
|
+
/** One scripted response, with zero status meaning HTTP 200. */
|
|
17
|
+
interface FakeReply {
|
|
18
|
+
/** HTTP status. */ statusCode?: number;
|
|
19
|
+
/** Public response headers. */ headers?: Record<string, string>;
|
|
20
|
+
/** Initial body bytes. */ body?: string;
|
|
21
|
+
/** Silence before headers or a dropped connection, in milliseconds. */ delayMs?: number;
|
|
22
|
+
/** Ordered flushed body chunks. */ streamChunks?: readonly FakeStreamChunk[];
|
|
23
|
+
/** Drop the connection before sending headers. */ shouldDropConnection?: boolean;
|
|
24
|
+
}
|
|
25
|
+
/** Credential-redacted record of one provider request. */
|
|
26
|
+
interface RecordedRequest {
|
|
27
|
+
/** HTTP method. */ method: string;
|
|
28
|
+
/** Request path and query. */ path: string;
|
|
29
|
+
/** Request headers, excluding the credential slot. */ headers: Record<string, string>;
|
|
30
|
+
/** Request body, bounded and credential-redacted. */ body: Buffer;
|
|
31
|
+
/** Receipt time. */ receivedAt: Date;
|
|
32
|
+
/** Whether the credential appeared in the declared header. */ hasCredentialInSlot: boolean;
|
|
33
|
+
/** Whether the credential appeared anywhere else. */ hasCredentialOutsideSlot: boolean;
|
|
34
|
+
}
|
|
35
|
+
/** Scripted loopback provider for shared text-generation exchange tests. */
|
|
36
|
+
declare class FakeProvider {
|
|
37
|
+
#private;
|
|
38
|
+
private constructor();
|
|
39
|
+
/** Start one fixture server; close it after the suite. */
|
|
40
|
+
static start(credentialHeader: CredentialHeader, credential: string): Promise<FakeProvider>;
|
|
41
|
+
/** Return the loopback base URL. */
|
|
42
|
+
baseURL(): string;
|
|
43
|
+
/** Queue replies in request order. */
|
|
44
|
+
enqueueReplies(...replies: FakeReply[]): void;
|
|
45
|
+
/** Return a snapshot of sanitized requests. */
|
|
46
|
+
requests(): readonly RecordedRequest[];
|
|
47
|
+
/** Stop the fixture and all active connections. */
|
|
48
|
+
close(): Promise<void>;
|
|
49
|
+
private serveRequest;
|
|
50
|
+
private recordRequest;
|
|
51
|
+
private waitForDelay;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Values the provider-specific dialect must render for the exchange suite. */
|
|
55
|
+
interface GeneratedReply {
|
|
56
|
+
/** User-visible answer. */ text: string;
|
|
57
|
+
/** Provider's model echo. */ servedModel: string;
|
|
58
|
+
/** Provider response identity. */ responseId: string;
|
|
59
|
+
/** Distinct token counts for field mapping checks. */ usage: Usage;
|
|
60
|
+
}
|
|
61
|
+
/** Provider-specific wire rendering and request parsing for shared exchange cases. */
|
|
62
|
+
interface ProviderDialect {
|
|
63
|
+
/** Declared credential location. */ credentialHeader: CredentialHeader;
|
|
64
|
+
/** Connection's configured model. */ connectionModel: string;
|
|
65
|
+
/** Different valid request model. */ alternateModel: string;
|
|
66
|
+
/** Optional response header read into the receipt. */ requestIDHeader?: string;
|
|
67
|
+
/** Read the sent model from one sanitized request. */ readRequestModel(request: RecordedRequest): string;
|
|
68
|
+
/** Render a normal completion with all usage counts. */ generatedReply(reply: GeneratedReply): FakeReply;
|
|
69
|
+
/** Render an output-limit stop. */ truncatedReply(reply: GeneratedReply): FakeReply;
|
|
70
|
+
/** Render a content-policy stop or refusal. */ blockedReply(reply: GeneratedReply): FakeReply;
|
|
71
|
+
/** Render one provider status and private message. */ errorReply(statusCode: number, message: string): FakeReply;
|
|
72
|
+
/** Render provider quota exhaustion. */ quotaExhaustedReply(message: string): FakeReply;
|
|
73
|
+
/** Render malformed 2xx output. */ malformedReply(): FakeReply;
|
|
74
|
+
/** Render a 2xx embedded error when supported. */ reportedErrorReply?(token: string, message: string): FakeReply;
|
|
75
|
+
/** Render an interrupted event stream when the dialect streams. */ interruptedStreamReply?(reply: GeneratedReply): FakeReply;
|
|
76
|
+
/** Render a complete body for a streaming request when supported. */ unstreamedReply?(reply: GeneratedReply): FakeReply;
|
|
77
|
+
/** JSON pointers whose optional fields must be absent in the basic request. */ optionalRequestFieldPointers?: readonly string[];
|
|
78
|
+
}
|
|
79
|
+
/** Loopback connection passed to a connector's ordinary query factory. */
|
|
80
|
+
interface FakeConnection {
|
|
81
|
+
/** Fake provider base URL. */ baseURL: string;
|
|
82
|
+
/** Configured provider model. */ model: string;
|
|
83
|
+
/** Canary API key; never put it in Flow state. */ apiKey: string;
|
|
84
|
+
/** Successful-response byte limit. */ maxResponseBytes: number;
|
|
85
|
+
/** Optional minimum exchange timeout for a silent-provider Dex scenario. */ requestTimeoutMs?: number;
|
|
86
|
+
}
|
|
87
|
+
/** One shared exchange suite invocation for a connector dialect. */
|
|
88
|
+
interface TextGenerationExchangeSuite {
|
|
89
|
+
/** Provider-specific request and response shapes. */ dialect: ProviderDialect;
|
|
90
|
+
/** Build the connector's normal generateText Query. */ newQuery(connection: FakeConnection): TextGenerationQuery | Promise<TextGenerationQuery>;
|
|
91
|
+
/** Connector-specific invalid requests that must cause zero provider calls. */ locallyRejectedRequests?: readonly TextGenerationRequest[];
|
|
92
|
+
}
|
|
93
|
+
/** Run Go-equivalent model, branch, error, safety, and stream exchanges. */
|
|
94
|
+
declare function runTextGenerationExchangeSuite(suite: TextGenerationExchangeSuite): Promise<void>;
|
|
95
|
+
|
|
96
|
+
/** Real Dex Worker/Client scenarios for one provider's ordinary Query factory. */
|
|
97
|
+
interface TextGenerationDexScenarioSuite {
|
|
98
|
+
/** Provider wire dialect shared with the exchange suite. */ dialect: ProviderDialect;
|
|
99
|
+
/** Build the ordinary connector Query with a request timeout of at least 15 seconds. */
|
|
100
|
+
newQuery(connection: FakeConnection): TextGenerationQuery | Promise<TextGenerationQuery>;
|
|
101
|
+
/** Dex FlowService address; defaults to DEX_SERVER_ADDRESS or 127.0.0.1:18891. */ serverAddress?: string;
|
|
102
|
+
}
|
|
103
|
+
/** Exercise retry, model precedence, Streams, heartbeat, unwired branches, and Worker loss through Dex. */
|
|
104
|
+
declare function runTextGenerationDexScenarios(suite: TextGenerationDexScenarioSuite): Promise<void>;
|
|
105
|
+
|
|
106
|
+
export { type FakeConnection, FakeProvider, type FakeReply, type FakeStreamChunk, type GeneratedReply, type ProviderDialect, type RecordedRequest, type TextGenerationDexScenarioSuite, type TextGenerationExchangeSuite, runTextGenerationDexScenarios, runTextGenerationExchangeSuite };
|