@superdurable/connectors-sdk 0.1.1 → 0.3.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 +33 -2
- package/dist/{chunk-2NBM7P4T.js → chunk-32YJXBE4.js} +1 -1
- package/dist/chunk-466BPS3M.js +336 -0
- 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/{chunk-2IDXFKMY.js → chunk-UWB627CI.js} +82 -11
- 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 +344 -3
- 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 -3
- 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,628 @@
|
|
|
1
|
+
import {
|
|
2
|
+
validateQueryDefinition
|
|
3
|
+
} from "../chunk-32YJXBE4.js";
|
|
4
|
+
import {
|
|
5
|
+
SecretString
|
|
6
|
+
} from "../chunk-QDMBAWXD.js";
|
|
7
|
+
import {
|
|
8
|
+
newQueryBranch,
|
|
9
|
+
newQueryRetry
|
|
10
|
+
} from "../chunk-BQGYGCOQ.js";
|
|
11
|
+
import {
|
|
12
|
+
ProviderReportedError,
|
|
13
|
+
blockedOutcome,
|
|
14
|
+
canonicalizePortableSchema,
|
|
15
|
+
classifyError,
|
|
16
|
+
extractStructuredAnswer,
|
|
17
|
+
extractStructuredAnswerFromSchema,
|
|
18
|
+
invalidResponseOutcome,
|
|
19
|
+
jsonObjectInstruction,
|
|
20
|
+
providerRejectedOutcome,
|
|
21
|
+
quotaExhaustedOutcome,
|
|
22
|
+
retryOutcome,
|
|
23
|
+
temperatureAccepted,
|
|
24
|
+
temperatureNotAccepted,
|
|
25
|
+
temperatureRange,
|
|
26
|
+
transformSchemaForProvider,
|
|
27
|
+
validateStructuredOutputRules,
|
|
28
|
+
validateWireFormat
|
|
29
|
+
} from "../chunk-466BPS3M.js";
|
|
30
|
+
import {
|
|
31
|
+
AddressRefusal,
|
|
32
|
+
BodyTooLargeError,
|
|
33
|
+
ProviderStalledError,
|
|
34
|
+
ProviderTransportError,
|
|
35
|
+
isHeaderSafeCredential,
|
|
36
|
+
parseRetryAfter,
|
|
37
|
+
readErrorTokens,
|
|
38
|
+
sendProviderRequest,
|
|
39
|
+
validateBaseURL
|
|
40
|
+
} from "../chunk-UWB627CI.js";
|
|
41
|
+
import "../chunk-IHG6ADQG.js";
|
|
42
|
+
|
|
43
|
+
// src/textgen/contract.ts
|
|
44
|
+
var TextGenerationOperationID = "generateText";
|
|
45
|
+
var GeneratedBranchID = "generated";
|
|
46
|
+
var TruncatedBranchID = "truncated";
|
|
47
|
+
var BlockedBranchID = "blocked";
|
|
48
|
+
var ProviderRejectedBranchID = "providerRejected";
|
|
49
|
+
var InvalidResponseBranchID = "invalidResponse";
|
|
50
|
+
var DefectBranchID = "defect";
|
|
51
|
+
function textGenerationBranchDefinitions() {
|
|
52
|
+
return [
|
|
53
|
+
{ id: GeneratedBranchID, description: "The model finished normally and returned text." },
|
|
54
|
+
{ id: TruncatedBranchID, description: "The model stopped at the output token limit and returned any partial text.", optional: true },
|
|
55
|
+
{ id: BlockedBranchID, description: "The provider stopped the response for a content policy, or the model refused.", optional: true },
|
|
56
|
+
{ id: ProviderRejectedBranchID, description: "The provider conclusively rejected the request, such as invalid credentials, an unknown model, or exhausted quota.", optional: true },
|
|
57
|
+
{ id: InvalidResponseBranchID, description: "The provider returned a malformed, oversized, or unusable response, including structured output that does not match its schema.", optional: true },
|
|
58
|
+
{ id: DefectBranchID, description: "Local input, connection configuration, or connector definition is invalid.", optional: true }
|
|
59
|
+
];
|
|
60
|
+
}
|
|
61
|
+
function validatePortableRequest(request) {
|
|
62
|
+
const message = checkPortableRequest(request).error;
|
|
63
|
+
return message ? { kind: "VALIDATION", provider: "", operation: "", message } : void 0;
|
|
64
|
+
}
|
|
65
|
+
function checkPortableRequest(request) {
|
|
66
|
+
if (!Array.isArray(request.messages) || request.messages.length === 0) return { error: "at least one message is required" };
|
|
67
|
+
for (const message of request.messages) {
|
|
68
|
+
if (message.role !== "user" && message.role !== "assistant") return { error: "message role must be user or assistant" };
|
|
69
|
+
if (typeof message.text !== "string" || message.text === "") return { error: "every message requires text" };
|
|
70
|
+
}
|
|
71
|
+
if ((request.maxOutputTokens ?? 0) < 0) return { error: "maxOutputTokens cannot be negative" };
|
|
72
|
+
if (request.structuredOutput != null) {
|
|
73
|
+
if (!/^[A-Za-z0-9_-]{1,64}$/.test(request.structuredOutput.name)) return { error: "structured output name must match ^[A-Za-z0-9_-]{1,64}$" };
|
|
74
|
+
if (request.structuredOutput.schema === void 0 || request.structuredOutput.schema === null) return { error: "structured output schema is required" };
|
|
75
|
+
try {
|
|
76
|
+
return { schema: canonicalizePortableSchema(request.structuredOutput.schema) };
|
|
77
|
+
} catch (error) {
|
|
78
|
+
return { error: `structured output schema is not portable ${error instanceof Error ? error.message : ""}` };
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return {};
|
|
82
|
+
}
|
|
83
|
+
function resolveModel(requestModel, connectionModel) {
|
|
84
|
+
return requestModel.trim() || connectionModel;
|
|
85
|
+
}
|
|
86
|
+
function validateModelID(rule, value) {
|
|
87
|
+
let model = value.trim();
|
|
88
|
+
if (rule === "body") {
|
|
89
|
+
if (!/^[\x21-\x7e]{1,256}$/.test(model)) throw new TypeError("model ID must be 1 to 256 printable ASCII characters without spaces");
|
|
90
|
+
return model;
|
|
91
|
+
}
|
|
92
|
+
if (rule === "pathSegment") {
|
|
93
|
+
if (model.startsWith("models/")) model = model.slice(7);
|
|
94
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(model)) throw new TypeError('model ID must start with a letter or digit and contain at most 128 letters, digits, ".", "_", or "-"');
|
|
95
|
+
return model;
|
|
96
|
+
}
|
|
97
|
+
throw new TypeError("model ID rule is invalid");
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// src/providerhttp/server_sent_events.ts
|
|
101
|
+
var ServerSentEventDecoder = class {
|
|
102
|
+
#maxEventBytes;
|
|
103
|
+
#maxTotalBytes;
|
|
104
|
+
#totalBytes = 0;
|
|
105
|
+
#pending = Buffer.alloc(0);
|
|
106
|
+
#eventBytes = 0;
|
|
107
|
+
#dataLines = [];
|
|
108
|
+
#eventType = "";
|
|
109
|
+
constructor(maxEventBytes, maxTotalBytes = Number.MAX_SAFE_INTEGER) {
|
|
110
|
+
this.#maxEventBytes = maxEventBytes;
|
|
111
|
+
this.#maxTotalBytes = maxTotalBytes;
|
|
112
|
+
}
|
|
113
|
+
/** Decode complete events; false from onEvent stops the current response. */
|
|
114
|
+
feed(chunk, onEvent) {
|
|
115
|
+
this.#pending = Buffer.concat([this.#pending, chunk]);
|
|
116
|
+
for (; ; ) {
|
|
117
|
+
const newline = this.#pending.indexOf(10);
|
|
118
|
+
if (newline < 0) {
|
|
119
|
+
if (this.#totalBytes + this.#pending.length > this.#maxTotalBytes) throw new BodyTooLargeError();
|
|
120
|
+
if (this.#pending.length > this.#maxEventBytes + 2) throw new BodyTooLargeError();
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
this.#totalBytes += newline + 1;
|
|
124
|
+
if (this.#totalBytes > this.#maxTotalBytes) throw new BodyTooLargeError();
|
|
125
|
+
let line = this.#pending.subarray(0, newline);
|
|
126
|
+
this.#pending = this.#pending.subarray(newline + 1);
|
|
127
|
+
const hasCR = line.at(-1) === 13;
|
|
128
|
+
if (hasCR) line = line.subarray(0, line.length - 1);
|
|
129
|
+
if (line.length > this.#maxEventBytes + (hasCR ? 0 : 1)) throw new BodyTooLargeError();
|
|
130
|
+
if (line.length === 0) {
|
|
131
|
+
if (this.#dataLines.length > 0) {
|
|
132
|
+
const event = { data: this.#dataLines.join("\n"), ...this.#eventType ? { event: this.#eventType } : {} };
|
|
133
|
+
this.#resetEvent();
|
|
134
|
+
if (onEvent(event) === false) return false;
|
|
135
|
+
} else this.#resetEvent();
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
if (line[0] === 58) continue;
|
|
139
|
+
this.#eventBytes += line.length;
|
|
140
|
+
if (this.#eventBytes > this.#maxEventBytes) throw new BodyTooLargeError();
|
|
141
|
+
const colon = line.indexOf(58);
|
|
142
|
+
const field = (colon < 0 ? line : line.subarray(0, colon)).toString("utf8");
|
|
143
|
+
let value = colon < 0 ? Buffer.alloc(0) : line.subarray(colon + 1);
|
|
144
|
+
if (value[0] === 32) value = value.subarray(1);
|
|
145
|
+
if (field === "data") this.#dataLines.push(value.toString("utf8"));
|
|
146
|
+
if (field === "event") this.#eventType = value.toString("utf8");
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
#resetEvent() {
|
|
150
|
+
this.#eventBytes = 0;
|
|
151
|
+
this.#dataLines = [];
|
|
152
|
+
this.#eventType = "";
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
// src/textgen/query.ts
|
|
157
|
+
var safeToken = /^[A-Za-z0-9_.:-]{1,64}$/;
|
|
158
|
+
var headerName = /^[!#$%&'*+.^_`|~0-9A-Za-z-]{1,128}$/;
|
|
159
|
+
var heartbeatIntervalMs = 5e3;
|
|
160
|
+
var TextStreamWriteError = class extends Error {
|
|
161
|
+
};
|
|
162
|
+
var LocalDefectError = class extends Error {
|
|
163
|
+
};
|
|
164
|
+
var TextGenerationQuery = class {
|
|
165
|
+
#definition;
|
|
166
|
+
#wireFormat;
|
|
167
|
+
#baseURL;
|
|
168
|
+
#connectionModel;
|
|
169
|
+
#requestTimeoutMs;
|
|
170
|
+
#httpClient;
|
|
171
|
+
#resolveCredential;
|
|
172
|
+
#maxResponseBytes;
|
|
173
|
+
#maxStreamEventBytes;
|
|
174
|
+
/** Validate configuration before a provider request can be made. */
|
|
175
|
+
constructor(config) {
|
|
176
|
+
if (!config) throw new TypeError("text generation query config is required");
|
|
177
|
+
validateQueryDefinition(config.definition);
|
|
178
|
+
if (config.definition.operation.operationId !== TextGenerationOperationID) throw new TypeError('text generation query definition must use operation ID "generateText"');
|
|
179
|
+
const expected = textGenerationBranchDefinitions();
|
|
180
|
+
if (config.definition.branches.length !== expected.length) throw new TypeError("generateText must declare exactly six branches");
|
|
181
|
+
for (const branch of expected) if (!config.definition.branches.some((actual) => actual.id === branch.id && !!actual.optional === !!branch.optional)) throw new TypeError(`generateText branch ${branch.id} has invalid optionality`);
|
|
182
|
+
if (config.definition.stepDefaults.executeDurability !== "sync") throw new TypeError("generateText must use sync Execute durability");
|
|
183
|
+
if (config.definition.stepDefaults.heartbeatTimeoutMs && config.definition.stepDefaults.heartbeatTimeoutMs < 2 * heartbeatIntervalMs) throw new TypeError("generateText heartbeat timeout must be zero or at least 10 seconds");
|
|
184
|
+
if (config.definition.stepDefaults.executeMethodTimeoutMs && config.definition.stepDefaults.executeMethodTimeoutMs <= config.requestTimeoutMs) throw new TypeError("generateText Execute timeout must exceed the request timeout");
|
|
185
|
+
validateWireFormat(config.wireFormat);
|
|
186
|
+
this.#wireFormat = {
|
|
187
|
+
...config.wireFormat,
|
|
188
|
+
features: { ...config.wireFormat.features },
|
|
189
|
+
credentialHeader: { ...config.wireFormat.credentialHeader },
|
|
190
|
+
finishReasons: { ...config.wireFormat.finishReasons },
|
|
191
|
+
errorRules: [...config.wireFormat.errorRules ?? []],
|
|
192
|
+
errorTokenPointers: [...config.wireFormat.errorTokenPointers ?? []],
|
|
193
|
+
requestIDHeaders: [...config.wireFormat.requestIDHeaders ?? []],
|
|
194
|
+
rateLimitHeaders: [...config.wireFormat.rateLimitHeaders ?? []]
|
|
195
|
+
};
|
|
196
|
+
this.#baseURL = validateBaseURL(config.baseURL);
|
|
197
|
+
this.#connectionModel = (config.connectionModel ?? "").trim();
|
|
198
|
+
if (this.#connectionModel) validateModelID(this.#wireFormat.modelIDRule, this.#connectionModel);
|
|
199
|
+
if (!Number.isSafeInteger(config.requestTimeoutMs) || config.requestTimeoutMs <= 0) throw new TypeError("text generation query request timeout must be positive");
|
|
200
|
+
if (!config.resolveCredential) throw new TypeError("text generation query credential resolver is required");
|
|
201
|
+
if (!Number.isSafeInteger(config.maxResponseBytes) || config.maxResponseBytes < 1) throw new TypeError("text generation query maxResponseBytes must be positive");
|
|
202
|
+
if (this.#wireFormat.decodeStreamEvent && (!Number.isSafeInteger(config.maxStreamEventBytes) || (config.maxStreamEventBytes ?? 0) < 1)) {
|
|
203
|
+
throw new TypeError("text generation query maxStreamEventBytes must be positive for a streaming wire format");
|
|
204
|
+
}
|
|
205
|
+
this.#definition = config.definition;
|
|
206
|
+
this.#requestTimeoutMs = config.requestTimeoutMs;
|
|
207
|
+
this.#httpClient = config.httpClient;
|
|
208
|
+
this.#resolveCredential = config.resolveCredential;
|
|
209
|
+
this.#maxResponseBytes = config.maxResponseBytes;
|
|
210
|
+
this.#maxStreamEventBytes = config.maxStreamEventBytes ?? 0;
|
|
211
|
+
}
|
|
212
|
+
/** Return the connector's generateText definition. */
|
|
213
|
+
definition() {
|
|
214
|
+
return this.#definition;
|
|
215
|
+
}
|
|
216
|
+
/** Return optional request features declared by the provider. */
|
|
217
|
+
requestFeatures() {
|
|
218
|
+
return this.#wireFormat.features;
|
|
219
|
+
}
|
|
220
|
+
/** Return the provider model ID rule. */
|
|
221
|
+
modelIDRule() {
|
|
222
|
+
return this.#wireFormat.modelIDRule;
|
|
223
|
+
}
|
|
224
|
+
/** Validate in fixed order, dispatch once, and classify a branch or safe retry without exposing secrets. */
|
|
225
|
+
async invoke(call, request) {
|
|
226
|
+
const response = emptyResponse();
|
|
227
|
+
const format = this.#wireFormat;
|
|
228
|
+
const attempt = new TextGenerationAttemptResult(format, response);
|
|
229
|
+
if (!call?.context) return attempt.branch(DefectBranchID, "LOCAL_DEFECT", "a Dex Step context is required");
|
|
230
|
+
let schema;
|
|
231
|
+
let rules;
|
|
232
|
+
let encoded;
|
|
233
|
+
let credential;
|
|
234
|
+
try {
|
|
235
|
+
const model = resolveModel(request.model ?? "", this.#connectionModel);
|
|
236
|
+
if (!model) throw new TypeError("no model is selected; set the request Model or the connection model");
|
|
237
|
+
response.requestedModel = validateModelID(format.modelIDRule, model);
|
|
238
|
+
const portable = checkPortableRequest(request);
|
|
239
|
+
if (portable.error) throw new TypeError(portable.error);
|
|
240
|
+
schema = portable.schema;
|
|
241
|
+
validateFeatures(request, format.features);
|
|
242
|
+
rules = format.rulesForModel(response.requestedModel);
|
|
243
|
+
validateSampling(request, rules);
|
|
244
|
+
const prepared = { ...request, model: response.requestedModel };
|
|
245
|
+
if (request.structuredOutput && schema) {
|
|
246
|
+
try {
|
|
247
|
+
validateStructuredOutputRules(rules.structuredOutput);
|
|
248
|
+
} catch (error) {
|
|
249
|
+
throw new LocalDefectError(`the wire format's structured output rules are invalid: ${error instanceof Error ? error.message : ""}`);
|
|
250
|
+
}
|
|
251
|
+
if (rules.structuredOutput.mode === "none") throw new TypeError("the model does not support structured output");
|
|
252
|
+
if (rules.structuredOutput.mode === "jsonObjectWithInstruction" && !format.features.supportsInstructions) throw new LocalDefectError("the JSON object mode requires the Instructions feature");
|
|
253
|
+
prepared.structuredOutput = { ...request.structuredOutput, schema: transformSchemaForProvider(schema, rules.structuredOutput) };
|
|
254
|
+
if (rules.structuredOutput.mode === "jsonObjectWithInstruction") {
|
|
255
|
+
const instruction = jsonObjectInstruction(request.structuredOutput.name, request.structuredOutput.description ?? "", schema);
|
|
256
|
+
prepared.instructions = `${request.instructions ? `${request.instructions}
|
|
257
|
+
|
|
258
|
+
` : ""}${instruction}`;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
try {
|
|
262
|
+
const resolved = await this.#resolveCredential(call);
|
|
263
|
+
credential = resolved instanceof SecretString ? resolved.reveal() : resolved;
|
|
264
|
+
} catch {
|
|
265
|
+
return attempt.branch(DefectBranchID, "AUTHENTICATION", "connection credentials are unavailable");
|
|
266
|
+
}
|
|
267
|
+
if (!isHeaderSafeCredential(credential)) return attempt.branch(DefectBranchID, "AUTHENTICATION", "connection credentials are invalid");
|
|
268
|
+
try {
|
|
269
|
+
encoded = format.encodeRequest({ request: prepared, rules });
|
|
270
|
+
} catch (error) {
|
|
271
|
+
return attempt.branch(DefectBranchID, "VALIDATION", `the request cannot be encoded for the provider: ${error instanceof Error ? error.message : "invalid request"}`);
|
|
272
|
+
}
|
|
273
|
+
try {
|
|
274
|
+
validateEncodedRequest(encoded, format.credentialHeader.name);
|
|
275
|
+
} catch (error) {
|
|
276
|
+
throw new LocalDefectError(`the wire format built an invalid request: ${error instanceof Error ? error.message : ""}`);
|
|
277
|
+
}
|
|
278
|
+
if (encoded.isStreaming && !format.decodeStreamEvent || !encoded.isStreaming && !format.decodeResponse) return attempt.branch(DefectBranchID, "LOCAL_DEFECT", "the wire format cannot decode the response form it requested");
|
|
279
|
+
} catch (error) {
|
|
280
|
+
return attempt.branch(DefectBranchID, error instanceof LocalDefectError ? "LOCAL_DEFECT" : "VALIDATION", error instanceof Error ? error.message : "invalid request");
|
|
281
|
+
}
|
|
282
|
+
const headers = {
|
|
283
|
+
...encoded.headers,
|
|
284
|
+
"Content-Type": "application/json",
|
|
285
|
+
Accept: encoded.isStreaming ? "text/event-stream" : "application/json",
|
|
286
|
+
[format.credentialHeader.name]: format.credentialHeader.prefix + credential
|
|
287
|
+
};
|
|
288
|
+
const stream = new TextGenerationStreamAttempt(call, format, encoded.isStreaming, this.#maxStreamEventBytes, this.#maxResponseBytes);
|
|
289
|
+
let hasCompleteResponse = false;
|
|
290
|
+
const heartbeats = new TextGenerationHeartbeat(call);
|
|
291
|
+
heartbeats.start();
|
|
292
|
+
try {
|
|
293
|
+
const providerRequest = {
|
|
294
|
+
url: this.#baseURL + encoded.path,
|
|
295
|
+
method: "POST",
|
|
296
|
+
headers,
|
|
297
|
+
body: encoded.body,
|
|
298
|
+
maxResponseBytes: this.#maxResponseBytes,
|
|
299
|
+
maxErrorResponseBytes: 64 << 10,
|
|
300
|
+
ignoreErrorBodyReadFailure: true,
|
|
301
|
+
addressPolicy: "unrestricted",
|
|
302
|
+
timeoutMs: this.#requestTimeoutMs,
|
|
303
|
+
signal: call.context.cancellationSignal,
|
|
304
|
+
...format.stallTimeoutMs ? { stallTimeoutMs: format.stallTimeoutMs } : {},
|
|
305
|
+
onResponseChunk: stream.onChunk.bind(stream)
|
|
306
|
+
};
|
|
307
|
+
const providerResponse = await (this.#httpClient ? this.#httpClient.send(providerRequest) : sendProviderRequest(providerRequest));
|
|
308
|
+
hasCompleteResponse = true;
|
|
309
|
+
const receipt = makeReceipt(call, format, providerResponse.headers, credential);
|
|
310
|
+
const status = providerResponse.status;
|
|
311
|
+
stream.wasEventStream = encoded.isStreaming && isEventStream(providerResponse.headers);
|
|
312
|
+
if (status < 200 || status >= 300) {
|
|
313
|
+
const tokens = safeTokens(readErrorTokens(providerResponse.body.slice(0, 64 << 10), format.errorTokenPointers ?? []), credential);
|
|
314
|
+
const outcome = classifyError(format.errorRules ?? [], status, tokens);
|
|
315
|
+
let delay = 0;
|
|
316
|
+
if (outcome.disposition === "retry") {
|
|
317
|
+
try {
|
|
318
|
+
const providerDelay = format.readErrorRetryDelay?.(providerResponse.body);
|
|
319
|
+
if (typeof providerDelay === "number" && Number.isFinite(providerDelay) && providerDelay > 0) delay = providerDelay;
|
|
320
|
+
} catch {
|
|
321
|
+
}
|
|
322
|
+
if (!delay) delay = parseRetryAfter(String(providerResponse.headers["retry-after"] ?? ""));
|
|
323
|
+
}
|
|
324
|
+
return classifyOutcome(outcome, `provider returned HTTP ${status}${tokens.length ? ` ${tokens.join(" ")}` : ""}`, delay, receipt, attempt);
|
|
325
|
+
}
|
|
326
|
+
let decoded;
|
|
327
|
+
try {
|
|
328
|
+
if (stream.wasEventStream) {
|
|
329
|
+
if (!stream.hasObservedChunk) stream.onChunk(providerResponse.body, status, providerResponse.headers);
|
|
330
|
+
if (stream.error) throw stream.error;
|
|
331
|
+
if (!stream.state.isDone && !stream.state.hasFinished) return attempt.retry("TRANSPORT", "provider response ended before it was complete");
|
|
332
|
+
decoded = stream.state.response;
|
|
333
|
+
} else {
|
|
334
|
+
if (!format.decodeResponse) return attempt.branch(InvalidResponseBranchID, "PROTOCOL", "provider did not return the event stream it was asked for", receipt);
|
|
335
|
+
decoded = format.decodeResponse(providerResponse.body);
|
|
336
|
+
}
|
|
337
|
+
} catch (error) {
|
|
338
|
+
if (error instanceof ProviderReportedError) {
|
|
339
|
+
const tokens = safeTokens(error.errorTokens, credential);
|
|
340
|
+
return classifyOutcome(classifyError(format.errorRules ?? [], 0, tokens, true), `provider reported an error inside a 2xx response${tokens.length ? ` ${tokens.join(" ")}` : ""}`, 0, receipt, attempt);
|
|
341
|
+
}
|
|
342
|
+
if (error instanceof BodyTooLargeError) return attempt.branch(InvalidResponseBranchID, "RESPONSE_TOO_LARGE", "provider response exceeds the configured size limit", receipt);
|
|
343
|
+
if (error instanceof TextStreamWriteError) return attempt.retry("AVAILABILITY", "the text stream could not be written");
|
|
344
|
+
return attempt.branch(InvalidResponseBranchID, "PROTOCOL", "provider response does not match its documented format", receipt);
|
|
345
|
+
}
|
|
346
|
+
if (!isDecodedResponse(decoded)) return attempt.branch(InvalidResponseBranchID, "PROTOCOL", "provider response does not match its documented format", receipt);
|
|
347
|
+
if (!boundedIdentifier(decoded.servedModel ?? "") || !boundedIdentifier(decoded.responseId ?? "") || revealsCredential(decoded.servedModel ?? "", credential) || revealsCredential(decoded.responseId ?? "", credential)) {
|
|
348
|
+
return attempt.branch(InvalidResponseBranchID, "PROTOCOL", "provider returned an invalid model or response identifier", receipt);
|
|
349
|
+
}
|
|
350
|
+
response.servedModel = decoded.servedModel ?? "";
|
|
351
|
+
response.responseId = decoded.responseId ?? "";
|
|
352
|
+
if (response.responseId) receipt.providerObjectId = response.responseId;
|
|
353
|
+
const usage = completeUsage(decoded.usage ?? {});
|
|
354
|
+
if (!usage) return attempt.branch(InvalidResponseBranchID, "PROTOCOL", "provider reported negative token usage", receipt);
|
|
355
|
+
response.usage = usage;
|
|
356
|
+
const token = decoded.providerFinishReason ?? "";
|
|
357
|
+
if (revealsCredential(token, credential)) return attempt.branch(InvalidResponseBranchID, "PROTOCOL", "provider returned an invalid finish reason", receipt);
|
|
358
|
+
if (safeToken.test(token)) response.providerFinishReason = token;
|
|
359
|
+
if (decoded.isRefusal) response.finishReason = "refusal";
|
|
360
|
+
else if (!response.providerFinishReason) return attempt.branch(InvalidResponseBranchID, "PROTOCOL", "provider returned no recognizable finish reason", receipt);
|
|
361
|
+
else {
|
|
362
|
+
const reason = Object.hasOwn(format.finishReasons, response.providerFinishReason) ? format.finishReasons[response.providerFinishReason] : void 0;
|
|
363
|
+
if (!reason) return attempt.branch(InvalidResponseBranchID, "PROTOCOL", `provider stopped for an unsupported reason: ${response.providerFinishReason}`, receipt);
|
|
364
|
+
response.finishReason = reason;
|
|
365
|
+
}
|
|
366
|
+
const text = decoded.parts.filter((part) => !part.isReasoning).map((part) => part.text).join("");
|
|
367
|
+
if (response.finishReason === "stop") {
|
|
368
|
+
if (!text) return attempt.branch(InvalidResponseBranchID, "PROTOCOL", "provider finished without text", receipt);
|
|
369
|
+
try {
|
|
370
|
+
response.text = schema ? extractStructuredAnswerFromSchema(schema, text) : text;
|
|
371
|
+
} catch (error) {
|
|
372
|
+
return attempt.branch(InvalidResponseBranchID, "PROTOCOL", `structured output does not match its schema ${error instanceof Error ? error.message : ""}`, receipt);
|
|
373
|
+
}
|
|
374
|
+
if (!stream.wasEventStream && call.hasTextStream()) {
|
|
375
|
+
try {
|
|
376
|
+
call.writeText(response.text);
|
|
377
|
+
} catch {
|
|
378
|
+
return attempt.retry("AVAILABILITY", "the text stream could not be written");
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
return newQueryBranch(GeneratedBranchID, response, receipt);
|
|
382
|
+
}
|
|
383
|
+
if (response.finishReason === "length") {
|
|
384
|
+
response.text = text;
|
|
385
|
+
if (!stream.wasEventStream && text && call.hasTextStream()) {
|
|
386
|
+
try {
|
|
387
|
+
call.writeText(text);
|
|
388
|
+
} catch {
|
|
389
|
+
return attempt.retry("AVAILABILITY", "the text stream could not be written");
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
return newQueryBranch(TruncatedBranchID, response, receipt, attempt.failure("RESPONSE_TOO_LARGE", "provider stopped at the output token limit"));
|
|
393
|
+
}
|
|
394
|
+
return attempt.branch(BlockedBranchID, "PROVIDER_REJECTION", response.finishReason === "refusal" ? "the model refused the request" : "provider stopped the response for a content policy", receipt);
|
|
395
|
+
} catch (error) {
|
|
396
|
+
if (stream.error !== void 0) {
|
|
397
|
+
const receipt = stream.responseHeaders ? makeReceipt(call, format, stream.responseHeaders, credential) : void 0;
|
|
398
|
+
if (stream.error instanceof BodyTooLargeError) return attempt.branch(InvalidResponseBranchID, "RESPONSE_TOO_LARGE", "provider response exceeds the configured size limit", receipt);
|
|
399
|
+
if (stream.error instanceof TextStreamWriteError) return attempt.retry("AVAILABILITY", "the text stream could not be written");
|
|
400
|
+
if (stream.error instanceof ProviderReportedError) {
|
|
401
|
+
const tokens = safeTokens(stream.error.errorTokens, credential);
|
|
402
|
+
return classifyOutcome(
|
|
403
|
+
classifyError(format.errorRules ?? [], 0, tokens, true),
|
|
404
|
+
`provider reported an error inside a 2xx response${tokens.length ? ` ${tokens.join(" ")}` : ""}`,
|
|
405
|
+
0,
|
|
406
|
+
receipt ?? { provider: format.providerName },
|
|
407
|
+
attempt
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
return attempt.branch(InvalidResponseBranchID, "PROTOCOL", "provider response does not match its documented format", receipt);
|
|
411
|
+
}
|
|
412
|
+
if (error instanceof BodyTooLargeError) {
|
|
413
|
+
const receipt = error.headers ? makeReceipt(call, format, error.headers, credential) : void 0;
|
|
414
|
+
return attempt.branch(InvalidResponseBranchID, "RESPONSE_TOO_LARGE", "provider response exceeds the configured size limit", receipt);
|
|
415
|
+
}
|
|
416
|
+
if (error instanceof AddressRefusal) return attempt.branch(DefectBranchID, "LOCAL_DEFECT", "provider address is not allowed");
|
|
417
|
+
if (error instanceof ProviderStalledError) return attempt.retry("AVAILABILITY", "provider sent no bytes within the stall timeout");
|
|
418
|
+
if (error instanceof ProviderTransportError) return attempt.retry("TRANSPORT", error.isProvablyNotSent ? "provider request failed before a response was received" : "provider response ended before it was complete");
|
|
419
|
+
return hasCompleteResponse ? attempt.branch(InvalidResponseBranchID, "PROTOCOL", "provider response does not match its documented format") : attempt.retry("TRANSPORT", "provider response ended before it was complete");
|
|
420
|
+
} finally {
|
|
421
|
+
await heartbeats.stop();
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
};
|
|
425
|
+
var TextGenerationAttemptResult = class {
|
|
426
|
+
constructor(format, response) {
|
|
427
|
+
this.format = format;
|
|
428
|
+
this.response = response;
|
|
429
|
+
}
|
|
430
|
+
format;
|
|
431
|
+
response;
|
|
432
|
+
failure(kind, message) {
|
|
433
|
+
return { kind, provider: this.format.providerName, operation: TextGenerationOperationID, message };
|
|
434
|
+
}
|
|
435
|
+
branch(id, kind, message, receipt = { provider: this.format.providerName }) {
|
|
436
|
+
return newQueryBranch(id, this.response, receipt, this.failure(kind, message));
|
|
437
|
+
}
|
|
438
|
+
retry(kind, message, delay = 0) {
|
|
439
|
+
return newQueryRetry(this.failure(kind, message), delay);
|
|
440
|
+
}
|
|
441
|
+
};
|
|
442
|
+
var TextGenerationStreamAttempt = class {
|
|
443
|
+
constructor(call, format, isStreaming, maxEventBytes, maxTotalBytes) {
|
|
444
|
+
this.call = call;
|
|
445
|
+
this.format = format;
|
|
446
|
+
this.isStreaming = isStreaming;
|
|
447
|
+
this.#decoder = new ServerSentEventDecoder(maxEventBytes, maxTotalBytes);
|
|
448
|
+
}
|
|
449
|
+
call;
|
|
450
|
+
format;
|
|
451
|
+
isStreaming;
|
|
452
|
+
state = { response: { parts: [] }, hasFinished: false, isDone: false };
|
|
453
|
+
#decoder;
|
|
454
|
+
error;
|
|
455
|
+
wasEventStream = false;
|
|
456
|
+
hasObservedChunk = false;
|
|
457
|
+
responseHeaders;
|
|
458
|
+
onChunk(chunk, status, headers) {
|
|
459
|
+
if (status < 200 || status >= 300 || !this.isStreaming || !isEventStream(headers)) return;
|
|
460
|
+
this.wasEventStream = true;
|
|
461
|
+
this.hasObservedChunk = true;
|
|
462
|
+
this.responseHeaders = headers;
|
|
463
|
+
try {
|
|
464
|
+
return this.#decoder.feed(chunk, this.decodeEvent.bind(this));
|
|
465
|
+
} catch (error) {
|
|
466
|
+
this.error = error;
|
|
467
|
+
throw error;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
decodeEvent(event) {
|
|
471
|
+
this.format.decodeStreamEvent?.(event, this.state, this.writeTextDelta.bind(this));
|
|
472
|
+
return !this.state.isDone;
|
|
473
|
+
}
|
|
474
|
+
writeTextDelta(delta) {
|
|
475
|
+
if (!delta || !this.call.hasTextStream()) return;
|
|
476
|
+
try {
|
|
477
|
+
this.call.writeText(delta);
|
|
478
|
+
} catch {
|
|
479
|
+
throw new TextStreamWriteError();
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
};
|
|
483
|
+
var TextGenerationHeartbeat = class {
|
|
484
|
+
constructor(call) {
|
|
485
|
+
this.call = call;
|
|
486
|
+
}
|
|
487
|
+
call;
|
|
488
|
+
#inFlight = Promise.resolve();
|
|
489
|
+
#timer;
|
|
490
|
+
start() {
|
|
491
|
+
this.#timer = setInterval(this.recordHeartbeat.bind(this), heartbeatIntervalMs);
|
|
492
|
+
}
|
|
493
|
+
async stop() {
|
|
494
|
+
if (this.#timer) clearInterval(this.#timer);
|
|
495
|
+
await this.#inFlight;
|
|
496
|
+
}
|
|
497
|
+
recordHeartbeat() {
|
|
498
|
+
this.#inFlight = this.#inFlight.then(async () => {
|
|
499
|
+
try {
|
|
500
|
+
await this.call.context.recordHeartbeat(void 0);
|
|
501
|
+
} catch {
|
|
502
|
+
}
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
};
|
|
506
|
+
function isEventStream(headers) {
|
|
507
|
+
return /^text\/event-stream(?:\s*;|$)/i.test(String(headers["content-type"] ?? "").trim());
|
|
508
|
+
}
|
|
509
|
+
function newTextGenerationQuery(config) {
|
|
510
|
+
return new TextGenerationQuery(config);
|
|
511
|
+
}
|
|
512
|
+
function emptyResponse() {
|
|
513
|
+
return {
|
|
514
|
+
text: "",
|
|
515
|
+
requestedModel: "",
|
|
516
|
+
servedModel: "",
|
|
517
|
+
responseId: "",
|
|
518
|
+
finishReason: "",
|
|
519
|
+
providerFinishReason: "",
|
|
520
|
+
usage: { inputTokens: 0, cachedInputTokens: 0, outputTokens: 0, reasoningTokens: 0, totalTokens: 0 }
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
function isDecodedResponse(value) {
|
|
524
|
+
if (value === null || typeof value !== "object") return false;
|
|
525
|
+
const response = value;
|
|
526
|
+
if (!Array.isArray(response.parts) || !response.parts.every((part) => part !== null && typeof part === "object" && typeof part.text === "string" && (part.isReasoning === void 0 || typeof part.isReasoning === "boolean"))) return false;
|
|
527
|
+
if (["servedModel", "responseId", "providerFinishReason"].some((name) => response[name] !== void 0 && typeof response[name] !== "string")) return false;
|
|
528
|
+
return (response.isRefusal === void 0 || typeof response.isRefusal === "boolean") && (response.usage === void 0 || response.usage !== null && typeof response.usage === "object" && !Array.isArray(response.usage));
|
|
529
|
+
}
|
|
530
|
+
function validateFeatures(request, features) {
|
|
531
|
+
const fields = [
|
|
532
|
+
[!!request.instructions, !!features.supportsInstructions, "Instructions"],
|
|
533
|
+
[!!request.structuredOutput, !!features.supportsStructuredOutput, "StructuredOutput"],
|
|
534
|
+
[!!request.maxOutputTokens, !!features.supportsMaxOutputTokens, "MaxOutputTokens"],
|
|
535
|
+
[request.temperature != null, !!features.supportsTemperature, "Temperature"],
|
|
536
|
+
[!!request.reasoningEffort, !!features.supportsReasoningEffort, "ReasoningEffort"]
|
|
537
|
+
];
|
|
538
|
+
for (const [used, supported, name] of fields) if (used && !supported) throw new TypeError(`this connector's wire format does not send ${name}; leave it unset`);
|
|
539
|
+
}
|
|
540
|
+
function validateSampling(request, rules) {
|
|
541
|
+
if (request.temperature != null) {
|
|
542
|
+
const value = request.temperature;
|
|
543
|
+
if (!Number.isFinite(value) || value < 0) throw new TypeError("temperature must be a finite, non-negative number");
|
|
544
|
+
if (rules.temperature.kind === "notAccepted") throw new TypeError("the model does not accept a temperature; leave Temperature nil");
|
|
545
|
+
if (rules.temperature.kind === "range") {
|
|
546
|
+
if (!Number.isFinite(rules.temperature.minimum) || !Number.isFinite(rules.temperature.maximum) || rules.temperature.minimum > rules.temperature.maximum) throw new TypeError("the wire format's temperature range is invalid");
|
|
547
|
+
if (value < rules.temperature.minimum || value > rules.temperature.maximum) throw new TypeError(`the model accepts a temperature from ${rules.temperature.minimum} through ${rules.temperature.maximum}`);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
if (request.reasoningEffort) {
|
|
551
|
+
if (!["none", "minimal", "low", "medium", "high", "xhigh", "max"].includes(request.reasoningEffort)) throw new TypeError("reasoning effort must be none, minimal, low, medium, high, xhigh, or max");
|
|
552
|
+
if (!(request.reasoningEffort in rules.reasoningEfforts)) throw new TypeError(`the model does not accept reasoning effort ${JSON.stringify(request.reasoningEffort)}`);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
function validateEncodedRequest(encoded, credentialHeaderName) {
|
|
556
|
+
if (!encoded.path.startsWith("/")) throw new TypeError("encoded request path must start with /");
|
|
557
|
+
if (!/^[\x21-\x7e]+$/.test(encoded.path) || encoded.path.includes("#")) throw new TypeError("encoded request path must be printable ASCII without spaces or fragments");
|
|
558
|
+
const reserved = new Set([credentialHeaderName, "content-type", "content-length", "accept", "host"].map((value) => value.toLowerCase()));
|
|
559
|
+
for (const [name, value] of Object.entries(encoded.headers ?? {})) if (!headerName.test(name) || reserved.has(name.toLowerCase()) || !/^[\x20-\x7e]*$/.test(value)) throw new TypeError("encoded request header name or value is invalid");
|
|
560
|
+
}
|
|
561
|
+
function safeTokens(tokens, credential) {
|
|
562
|
+
return tokens.filter((token) => safeToken.test(token) && !revealsCredential(token, credential));
|
|
563
|
+
}
|
|
564
|
+
function revealsCredential(value, credential) {
|
|
565
|
+
return !!credential && value.includes(credential);
|
|
566
|
+
}
|
|
567
|
+
function boundedIdentifier(value) {
|
|
568
|
+
return value === "" || Buffer.byteLength(value) <= 256 && isHeaderSafeCredential(value);
|
|
569
|
+
}
|
|
570
|
+
function completeUsage(partial) {
|
|
571
|
+
const usage = { inputTokens: partial.inputTokens ?? 0, cachedInputTokens: partial.cachedInputTokens ?? 0, outputTokens: partial.outputTokens ?? 0, reasoningTokens: partial.reasoningTokens ?? 0, totalTokens: partial.totalTokens ?? 0 };
|
|
572
|
+
if (Object.values(usage).some((value) => !Number.isFinite(value) || value < 0)) return void 0;
|
|
573
|
+
if (usage.totalTokens === 0) usage.totalTokens = usage.inputTokens + usage.outputTokens;
|
|
574
|
+
return usage;
|
|
575
|
+
}
|
|
576
|
+
function makeReceipt(call, format, headers, credential) {
|
|
577
|
+
const receipt = { callId: call.id, provider: format.providerName, observedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
578
|
+
for (const name of format.requestIDHeaders ?? []) {
|
|
579
|
+
const value = String(headers[name.toLowerCase()] ?? "").trim();
|
|
580
|
+
if (boundedIdentifier(value) && value && !revealsCredential(value, credential)) {
|
|
581
|
+
receipt.providerRequestId = value;
|
|
582
|
+
break;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
for (const name of format.rateLimitHeaders ?? []) {
|
|
586
|
+
const value = String(headers[name.toLowerCase()] ?? "").trim();
|
|
587
|
+
if (value && value.length <= 256 && /^[\x20-\x7e]*$/.test(value) && !revealsCredential(value, credential)) {
|
|
588
|
+
(receipt.metadata ??= {})[name.toLowerCase()] = value;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
return receipt;
|
|
592
|
+
}
|
|
593
|
+
function classifyOutcome(outcome, message, delay, receipt, attempt) {
|
|
594
|
+
if (outcome.disposition === "retry") return attempt.retry(outcome.kind, message, Number.isFinite(delay) ? Math.floor(Math.min(36e5, Math.max(0, delay))) : 0);
|
|
595
|
+
if (outcome.disposition === "blocked") {
|
|
596
|
+
attempt.response.finishReason = "contentPolicy";
|
|
597
|
+
return attempt.branch(BlockedBranchID, outcome.kind, message, receipt);
|
|
598
|
+
}
|
|
599
|
+
return attempt.branch(outcome.disposition === "invalidResponse" ? InvalidResponseBranchID : ProviderRejectedBranchID, outcome.kind, message, receipt);
|
|
600
|
+
}
|
|
601
|
+
export {
|
|
602
|
+
BlockedBranchID,
|
|
603
|
+
DefectBranchID,
|
|
604
|
+
GeneratedBranchID,
|
|
605
|
+
InvalidResponseBranchID,
|
|
606
|
+
ProviderRejectedBranchID,
|
|
607
|
+
ProviderReportedError,
|
|
608
|
+
TextGenerationOperationID,
|
|
609
|
+
TextGenerationQuery,
|
|
610
|
+
TruncatedBranchID,
|
|
611
|
+
blockedOutcome,
|
|
612
|
+
classifyError,
|
|
613
|
+
extractStructuredAnswer,
|
|
614
|
+
invalidResponseOutcome,
|
|
615
|
+
newTextGenerationQuery,
|
|
616
|
+
providerRejectedOutcome,
|
|
617
|
+
quotaExhaustedOutcome,
|
|
618
|
+
resolveModel,
|
|
619
|
+
retryOutcome,
|
|
620
|
+
temperatureAccepted,
|
|
621
|
+
temperatureNotAccepted,
|
|
622
|
+
temperatureRange,
|
|
623
|
+
textGenerationBranchDefinitions,
|
|
624
|
+
validateModelID,
|
|
625
|
+
validatePortableRequest,
|
|
626
|
+
validateStructuredOutputRules,
|
|
627
|
+
validateWireFormat
|
|
628
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { o as TemperaturePolicy, R as ReasoningEffort, n as StructuredOutputRules, C as CredentialHeader, F as FinishReason, d as ErrorRule, W as WireFormat } from '../../wire_format-stEVruMs.js';
|
|
2
|
+
import '../../types-D6De3sry.js';
|
|
3
|
+
import 'zod';
|
|
4
|
+
|
|
5
|
+
/** Role used for instructions in Chat Completions. */ type InstructionsRole = 'system' | 'developer';
|
|
6
|
+
/** Token limit field accepted by this provider. */ type MaxTokensField = 'max_tokens' | 'max_completion_tokens';
|
|
7
|
+
/** Whether requests use Chat Completions event streams. */ type StreamingPolicy = 'never' | 'always';
|
|
8
|
+
/** First matching model rule overrides profile settings. */
|
|
9
|
+
interface ModelRule {
|
|
10
|
+
/** Exact leading model ID text. */ modelIDPrefix?: string;
|
|
11
|
+
/** Anchored model ID regular expression. */ modelIDPattern?: string;
|
|
12
|
+
/** Sampling override. */ temperature?: TemperaturePolicy;
|
|
13
|
+
/** Accepted effort override; empty rejects all. */ reasoningEfforts?: Partial<Record<ReasoningEffort, string>>;
|
|
14
|
+
/** Schema mode override. */ structuredOutput?: StructuredOutputRules;
|
|
15
|
+
/** Token field override. */ maxTokensField?: MaxTokensField;
|
|
16
|
+
/** Streaming override. */ streaming?: StreamingPolicy;
|
|
17
|
+
}
|
|
18
|
+
/** Declarative provider Chat Completions dialect. */
|
|
19
|
+
interface Profile {
|
|
20
|
+
/** Provider identity in failures. */ providerName: string;
|
|
21
|
+
/** Endpoint path; defaults to /chat/completions. */ chatCompletionsPath?: string;
|
|
22
|
+
/** Credential header; defaults to Authorization: Bearer. */ credentialHeader?: CredentialHeader;
|
|
23
|
+
/** Public fixed request headers. */ fixedHeaders?: Record<string, string>;
|
|
24
|
+
/** Instruction role; defaults to system. */ instructionsRole?: InstructionsRole;
|
|
25
|
+
/** Token field; defaults to max_tokens. */ maxTokensField?: MaxTokensField;
|
|
26
|
+
/** Accepted reasoning values. */ reasoningEfforts?: Partial<Record<ReasoningEffort, string>>;
|
|
27
|
+
/** Sampling policy; defaults to accepted. */ temperature?: TemperaturePolicy;
|
|
28
|
+
/** Structured output rules; defaults to none. */ structuredOutput?: StructuredOutputRules;
|
|
29
|
+
/** Send strict true in JSON schema mode. */ shouldSendStrictJSONSchema?: boolean;
|
|
30
|
+
/** Streaming policy; defaults to never. */ streaming?: StreamingPolicy;
|
|
31
|
+
/** Request usage with stream_options. */ shouldRequestStreamUsage?: boolean;
|
|
32
|
+
/** Optional allowlist of top-level body fields. */ allowedRequestFields?: string[];
|
|
33
|
+
/** Provider finish-token overrides. */ finishReasons?: Record<string, FinishReason>;
|
|
34
|
+
/** Error classification rules. */ errorRules?: ErrorRule[];
|
|
35
|
+
/** JSON pointers of safe error tokens. */ errorTokenPointers?: string[];
|
|
36
|
+
/** Response request-ID headers. */ requestIDHeaders?: string[];
|
|
37
|
+
/** Receipt metadata headers. */ rateLimitHeaders?: string[];
|
|
38
|
+
/** No-byte timeout in milliseconds. */ stallTimeoutMs?: number;
|
|
39
|
+
/** First matching model override wins. */ modelRules?: ModelRule[];
|
|
40
|
+
}
|
|
41
|
+
/** Validate a profile and return a provider-neutral textgen wire format. */
|
|
42
|
+
declare function newWireFormat(profile: Profile): WireFormat;
|
|
43
|
+
|
|
44
|
+
export { type InstructionsRole, type MaxTokensField, type ModelRule, type Profile, type StreamingPolicy, newWireFormat };
|