@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.
@@ -1,14 +1,316 @@
1
1
  import {
2
2
  SecretString
3
- } from "../chunk-KD5DYYSU.js";
3
+ } from "../chunk-QDMBAWXD.js";
4
+ import "../chunk-BQGYGCOQ.js";
4
5
  import {
5
6
  ReauthorizationRequiredError
6
- } from "../chunk-SOXYPIMO.js";
7
+ } from "../chunk-IHG6ADQG.js";
7
8
 
8
9
  // src/oauthtoken/index.ts
9
10
  import { createPrivateKey, sign } from "crypto";
11
+
12
+ // src/oauthtoken/manifest_refresh.ts
13
+ var ManifestCredentialRefreshDriver = class {
14
+ #providerName;
15
+ #auth;
16
+ #credentialFields;
17
+ #settings;
18
+ #fetch;
19
+ #now;
20
+ /** Bind validated auth data, pinned settings, a caller-owned transport, and a clock. */
21
+ constructor(options) {
22
+ if (!options?.providerName || !options.auth || !options.credentialFields) throw new TypeError("manifest refresh configuration is required");
23
+ this.#providerName = options.providerName;
24
+ this.#auth = options.auth;
25
+ this.#credentialFields = options.credentialFields;
26
+ this.#settings = options.settings ?? {};
27
+ this.#fetch = options.fetch ?? fetch;
28
+ this.#now = options.now ?? (() => /* @__PURE__ */ new Date());
29
+ }
30
+ /** Check declared access fields against the host's single credential expiry. */
31
+ refreshRequired(state) {
32
+ const grants = this.#grants(this.#selectedMethod(state.credentials));
33
+ if (!grants.length) return false;
34
+ const accessFields = grants.flatMap((grant) => grant.mappings.filter((mapping) => mapping.source === "access_token").map((mapping) => manifestText(mapping.credential)));
35
+ const hasAccessToken = accessFields.length > 0 && accessFields.every((name) => Boolean(this.#credentialValue(state.credentials, name)));
36
+ return isRefreshRequired(hasAccessToken, state.expiresAt, state.now, "keep");
37
+ }
38
+ /** Send each active grant and return one complete credential with the earliest expiry. */
39
+ async refresh(state, signal) {
40
+ let credentials = state.credentials;
41
+ const method = this.#selectedMethod(credentials);
42
+ const grants = this.#grants(method);
43
+ if (!grants.length) throw new ReauthorizationRequiredError();
44
+ for (const grant of grants) this.#validateGrant(grant);
45
+ const expiries = [];
46
+ for (const grant of grants) {
47
+ if (grant.refreshField && !this.#credentialValue(credentials, grant.refreshField)) continue;
48
+ const token = await this.#exchange(grant, credentials, method, signal);
49
+ const requiredScopes = this.#scopes(grant.scopes).filter((scope) => grant.kind !== "refresh" || scope !== "offline_access");
50
+ if (grants.length > 1 ? !hasExactScopes(token.scope, requiredScopes) : token.scope !== "" && !hasAllScopes(token.scope, requiredScopes)) throw new ReauthorizationRequiredError();
51
+ const updates = {};
52
+ for (const mapping of grant.mappings) {
53
+ const destination = manifestText(mapping.credential);
54
+ const value = token.mappedCredentialValue(manifestText(mapping.source));
55
+ if (value === void 0 || value === "" && destination === grant.refreshField) continue;
56
+ const property = this.#credentialFields[destination];
57
+ if (!property) throw new TypeError("manifest credential field is not mapped");
58
+ updates[property] = this.#isSecret(method, destination) ? new SecretString(value) : value;
59
+ }
60
+ for (const copy of grant.copies) {
61
+ const destination = manifestText(copy.credential);
62
+ const property = this.#credentialFields[destination];
63
+ if (!property) throw new TypeError("manifest credential field is not mapped");
64
+ const value = this.#credentialValue(credentials, manifestText(copy.fromCredential));
65
+ updates[property] = this.#isSecret(method, destination) ? new SecretString(value) : value;
66
+ }
67
+ credentials = { ...credentials, ...updates };
68
+ const lifetime = token.expiresInMs > 0 ? token.expiresInMs : parseDurationMs(grant.missingExpiry);
69
+ expiries.push(lifetime > 0 ? this.#now().getTime() + lifetime : 0);
70
+ }
71
+ if (!expiries.length) throw new ReauthorizationRequiredError();
72
+ return { credentials, expiresAt: new Date(Math.min(...expiries)) };
73
+ }
74
+ #selectedMethod(credentials) {
75
+ const methods = manifestList(this.#auth.methods);
76
+ if (!methods.length) return this.#auth;
77
+ const selected = manifestText(credentials.authMethodId);
78
+ if (!selected && methods.length > 1) throw new ReauthorizationRequiredError();
79
+ const method = methods.map(manifestObject).find((item) => item.id === (selected || manifestText(this.#auth.defaultMethod)));
80
+ if (!method) throw new ReauthorizationRequiredError();
81
+ return method;
82
+ }
83
+ #grants(method) {
84
+ if (method.oauth2) {
85
+ const oauth = manifestObject(method.oauth2);
86
+ const refresh = method.oauth2 && manifestObject(oauth.refresh);
87
+ let declared = manifestList(refresh?.grants).map(manifestObject);
88
+ if (!declared.length) {
89
+ const mapped = manifestList(oauth.credentialMappings).map(manifestObject).find((mapping) => mapping.source === "refresh_token");
90
+ if (!mapped) return [];
91
+ declared = [{ refreshTokenCredential: mapped.credential, credentialMappings: oauth.credentialMappings }];
92
+ }
93
+ return declared.map((declaration) => ({
94
+ kind: "refresh",
95
+ endpoint: oauth.tokenEndpoint,
96
+ authentication: oauth.tokenEndpointAuthMethod ?? "client_secret_post",
97
+ encoding: manifestText(oauth.tokenRequestEncoding) || "form",
98
+ clientID: manifestText(oauth.clientIDCredential),
99
+ clientSecret: manifestText(oauth.clientSecretCredential),
100
+ refreshField: manifestText(declaration.refreshTokenCredential),
101
+ keyCredential: "",
102
+ assertion: {},
103
+ mappings: manifestList(declaration.credentialMappings).map(manifestObject),
104
+ copies: [],
105
+ scopes: declaration.grantedScopes === "userScopes" ? oauth.userScopes : oauth.scopes,
106
+ requestScopes: [],
107
+ terminalCodes: strings(refresh?.terminalErrorCodes, ["invalid_grant"]),
108
+ acceptedTokenTypes: strings(declaration.acceptedTokenTypes ?? refresh?.acceptedTokenTypes, ["Bearer"]),
109
+ acceptsMissingTokenType: refresh?.acceptsMissingTokenType === true,
110
+ missingExpiry: missingExpiryLifetime(refresh?.missingExpiry),
111
+ additionalParameters: stringRecord(refresh?.additionalParameters)
112
+ }));
113
+ }
114
+ if (method.clientCredentials) {
115
+ const client = manifestObject(method.clientCredentials);
116
+ return [{
117
+ kind: "client_credentials",
118
+ endpoint: client.tokenEndpoint,
119
+ authentication: client.tokenEndpointAuthMethod ?? "client_secret_post",
120
+ encoding: manifestText(client.tokenRequestEncoding) || "form",
121
+ clientID: manifestText(client.clientIDCredential),
122
+ clientSecret: manifestText(client.clientSecretCredential),
123
+ refreshField: "",
124
+ keyCredential: "",
125
+ assertion: {},
126
+ mappings: manifestList(client.credentialMappings).map(manifestObject),
127
+ copies: [],
128
+ scopes: client.scopes,
129
+ requestScopes: client.scopes,
130
+ terminalCodes: strings(client.terminalErrorCodes, ["invalid_client"]),
131
+ acceptedTokenTypes: strings(client.acceptedTokenTypes, ["Bearer"]),
132
+ acceptsMissingTokenType: client.acceptsMissingTokenType === true,
133
+ missingExpiry: missingExpiryLifetime(client.missingExpiry),
134
+ additionalParameters: stringRecord(client.additionalParameters)
135
+ }];
136
+ }
137
+ const serviceAccount = manifestObject(method.serviceAccount);
138
+ if (!serviceAccount.jwtBearer) return [];
139
+ const bearer = manifestObject(serviceAccount.jwtBearer);
140
+ return [{
141
+ kind: "jwt_bearer",
142
+ endpoint: bearer.tokenEndpoint,
143
+ authentication: "",
144
+ encoding: "form",
145
+ clientID: "",
146
+ clientSecret: "",
147
+ refreshField: "",
148
+ keyCredential: manifestText(bearer.keyCredential),
149
+ assertion: bearer,
150
+ mappings: manifestList(bearer.credentialMappings).map(manifestObject),
151
+ copies: manifestList(bearer.credentialCopies).map(manifestObject),
152
+ scopes: bearer.scopes,
153
+ requestScopes: [],
154
+ terminalCodes: strings(bearer.terminalErrorCodes, ["invalid_grant"]),
155
+ acceptedTokenTypes: strings(bearer.acceptedTokenTypes, ["Bearer"]),
156
+ acceptsMissingTokenType: bearer.acceptsMissingTokenType === true,
157
+ missingExpiry: missingExpiryLifetime(bearer.missingExpiry),
158
+ additionalParameters: {}
159
+ }];
160
+ }
161
+ async #exchange(grant, credentials, method, signal) {
162
+ let key = {};
163
+ if (grant.kind === "jwt_bearer") {
164
+ try {
165
+ key = manifestObject(JSON.parse(this.#credentialValue(credentials, grant.keyCredential)));
166
+ } catch {
167
+ throw new ReauthorizationRequiredError();
168
+ }
169
+ }
170
+ const endpoint = new TokenEndpoint({
171
+ providerName: this.#providerName,
172
+ url: this.#resolveSource(grant.endpoint, credentials, key),
173
+ terminalErrorCodes: grant.terminalCodes,
174
+ acceptedTokenTypes: grant.acceptedTokenTypes,
175
+ acceptsMissingTokenType: grant.acceptsMissingTokenType,
176
+ acceptsMissingExpiresIn: true,
177
+ usesJSONRequestBody: grant.encoding === "json",
178
+ additionalRequestParameters: grant.additionalParameters,
179
+ credentialMappingPaths: grant.mappings.map((mapping) => manifestText(mapping.source)),
180
+ fetch: this.#fetch
181
+ });
182
+ if (grant.kind === "jwt_bearer") {
183
+ const assertion = grant.assertion;
184
+ const lifetimeMs = parseDurationMs(manifestText(assertion.lifetime));
185
+ let signed;
186
+ try {
187
+ signed = signJWTBearerAssertion({
188
+ issuer: this.#resolveSource(assertion.issuer, credentials, key),
189
+ audience: this.#resolveSource(assertion.audience, credentials, key),
190
+ ...assertion.subject ? { subject: this.#resolveSource(assertion.subject, credentials, key) } : {},
191
+ scope: this.#scopes(assertion.scopes).join(" "),
192
+ issuedAt: this.#now(),
193
+ lifetimeMs
194
+ }, parseRSAPrivateKeyPEM(this.#resolveSource(assertion.privateKey, credentials, key)));
195
+ } catch {
196
+ throw new ReauthorizationRequiredError();
197
+ }
198
+ return await endpoint.exchangeJWTBearerAssertion(signed, signal);
199
+ }
200
+ const secret = this.#credentialValue(credentials, grant.clientSecret);
201
+ if (!secret && this.#isRequired(method, grant.clientSecret)) throw new ReauthorizationRequiredError();
202
+ const authenticationMethod = secret || grant.kind === "client_credentials" ? this.#resolveSource(grant.authentication, credentials, key) : "none";
203
+ const client = { id: this.#credentialValue(credentials, grant.clientID), secret: new SecretString(secret), authenticationMethod };
204
+ if (grant.kind === "refresh") {
205
+ return endpoint.exchangeRefreshToken(client, new SecretString(this.#credentialValue(credentials, grant.refreshField)), signal);
206
+ }
207
+ return endpoint.exchangeClientCredentials(client, this.#scopes(grant.requestScopes), signal);
208
+ }
209
+ #credentialValue(credentials, name) {
210
+ const property = this.#credentialFields[name];
211
+ if (!property) return "";
212
+ const value = credentials[property];
213
+ return value instanceof SecretString ? value.reveal() : typeof value === "string" ? value : "";
214
+ }
215
+ #isSecret(method, name) {
216
+ return manifestList(method.fields).map(manifestObject).some((field) => field.name === name && field.type === "secretString");
217
+ }
218
+ #isRequired(method, name) {
219
+ const field = manifestList(method.fields).map(manifestObject).find((candidate) => candidate.name === name);
220
+ return field ? field.required === true : true;
221
+ }
222
+ #validateGrant(grant) {
223
+ for (const mapping of grant.mappings) {
224
+ if (!manifestText(mapping.source) || !this.#credentialFields[manifestText(mapping.credential)])
225
+ throw new TypeError("manifest credential field is not mapped");
226
+ }
227
+ for (const copy of grant.copies) {
228
+ if (!this.#credentialFields[manifestText(copy.credential)] || !this.#credentialFields[manifestText(copy.fromCredential)])
229
+ throw new TypeError("manifest credential field is not mapped");
230
+ }
231
+ parseDurationMs(grant.missingExpiry);
232
+ if (grant.kind === "jwt_bearer") parseDurationMs(manifestText(grant.assertion.lifetime));
233
+ }
234
+ #scopes(source) {
235
+ if (Array.isArray(source)) return source.filter((value) => typeof value === "string");
236
+ const fromSetting = manifestObject(source).fromSetting;
237
+ return typeof fromSetting === "string" ? (this.#settings[fromSetting] ?? "").split(/\s+/).filter(Boolean) : [];
238
+ }
239
+ #resolveSource(source, credentials, key) {
240
+ if (typeof source === "string") return source;
241
+ const valueSource = manifestObject(source);
242
+ if (typeof valueSource.value === "string") return valueSource.value;
243
+ if (typeof valueSource.template === "string") return valueSource.template.replace(/\{([^{}]+)\}/g, (_, name) => pathEscape(this.#credentialFields[name] ? this.#credentialValue(credentials, name) : this.#settings[name] ?? ""));
244
+ let value = "";
245
+ if (typeof valueSource.fromCredential === "string") value = this.#credentialValue(credentials, valueSource.fromCredential);
246
+ else if (typeof valueSource.fromSetting === "string") value = this.#settings[valueSource.fromSetting] ?? "";
247
+ else if (typeof valueSource.fromKey === "string") value = manifestText(key[valueSource.fromKey]);
248
+ else throw new ReauthorizationRequiredError();
249
+ if (valueSource.values) {
250
+ const translated = manifestObject(valueSource.values)[value];
251
+ if (typeof translated !== "string") throw new ReauthorizationRequiredError();
252
+ return translated;
253
+ }
254
+ if (!value) throw new ReauthorizationRequiredError();
255
+ return value;
256
+ }
257
+ };
258
+ function manifestObject(value) {
259
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
260
+ }
261
+ function manifestList(value) {
262
+ return Array.isArray(value) ? value : [];
263
+ }
264
+ function manifestText(value) {
265
+ return typeof value === "string" ? value : "";
266
+ }
267
+ function strings(value, fallback) {
268
+ const found = manifestList(value).filter((item) => typeof item === "string");
269
+ return found.length ? found : fallback;
270
+ }
271
+ function stringRecord(value) {
272
+ return Object.fromEntries(Object.entries(manifestObject(value)).filter((entry) => typeof entry[1] === "string"));
273
+ }
274
+ function missingExpiryLifetime(value) {
275
+ return manifestText(manifestObject(value).lifetime);
276
+ }
277
+ function pathEscape(value) {
278
+ return encodeURIComponent(value).replace(/[!'()*]/g, (character) => `%${character.charCodeAt(0).toString(16).toUpperCase()}`).replace(/%(?:2B|26|3D|3A|40|24)/g, (escaped) => ({ "%2B": "+", "%26": "&", "%3D": "=", "%3A": ":", "%40": "@", "%24": "$" })[escaped] ?? escaped);
279
+ }
280
+ function hasExactScopes(returnedScopes, requiredScopes) {
281
+ const granted = new Set(returnedScopes.split(/[\s,]+/).filter(Boolean));
282
+ const required = new Set(requiredScopes);
283
+ return granted.size === required.size && [...required].every((scope) => granted.has(scope));
284
+ }
285
+ function parseDurationMs(value) {
286
+ if (!value) return 0;
287
+ const parts = value.matchAll(/(\d+(?:\.\d+)?)(ns|us|µs|μs|ms|s|m|h)/g);
288
+ const milliseconds = {
289
+ ns: 1e-6,
290
+ us: 1e-3,
291
+ "\xB5s": 1e-3,
292
+ "\u03BCs": 1e-3,
293
+ ms: 1,
294
+ s: 1e3,
295
+ m: 6e4,
296
+ h: 36e5
297
+ };
298
+ let duration = 0;
299
+ let position = 0;
300
+ for (const part of parts) {
301
+ if (part.index !== position) throw new TypeError("manifest grant duration is invalid");
302
+ duration += Number(part[1]) * milliseconds[part[2]];
303
+ position += part[0].length;
304
+ }
305
+ if (position !== value.length || duration <= 0 || !Number.isFinite(duration) || duration > Number.MAX_SAFE_INTEGER)
306
+ throw new TypeError("manifest grant duration is invalid");
307
+ return duration;
308
+ }
309
+
310
+ // src/oauthtoken/index.ts
10
311
  var RefreshSkewMs = 3e5;
11
312
  var TokenResponse = class {
313
+ #mappedCredentialFields;
12
314
  /** Replacement access token. */
13
315
  accessToken;
14
316
  /** Rotated refresh token, empty when the provider retained it. */
@@ -32,11 +334,16 @@ var TokenResponse = class {
32
334
  this.scope = fields.scope;
33
335
  this.tokenType = fields.tokenType;
34
336
  this.retainedFields = fields.retainedFields;
337
+ this.#mappedCredentialFields = fields.mappedCredentialFields ?? {};
35
338
  }
36
339
  /** Keep the previous refresh token when no rotated value was returned. */
37
340
  nextRefreshToken(prior) {
38
341
  return this.refreshToken.reveal() ? this.refreshToken : prior;
39
342
  }
343
+ /** Reveal only one declared credential mapping at the provider boundary. */
344
+ mappedCredentialValue(path) {
345
+ return this.#mappedCredentialFields[path]?.reveal();
346
+ }
40
347
  };
41
348
  var TokenEndpoint = class {
42
349
  #options;
@@ -136,13 +443,26 @@ var TokenEndpoint = class {
136
443
  if (typeof accessToken !== "string" || !accessToken || !hasAcceptedExpiry || !hasAcceptedType) throw new Error(`${options.providerName} token response omitted required fields`);
137
444
  const retainedFields = {};
138
445
  for (const field of options.retainedResponseFields ?? []) if (Object.hasOwn(decoded, field)) retainedFields[field] = decoded[field];
446
+ const mappedCredentialFields = {};
447
+ for (const path of options.credentialMappingPaths ?? []) {
448
+ const value = responsePathValue(decoded, path);
449
+ if (value !== void 0) mappedCredentialFields[path] = new SecretString(value);
450
+ }
139
451
  const refreshToken = typeof decoded.refresh_token === "string" ? decoded.refresh_token : "";
140
452
  const refreshTokenExpiry = typeof decoded.refresh_token_expires_in === "number" && decoded.refresh_token_expires_in > 0 ? decoded.refresh_token_expires_in : 0;
141
- return new TokenResponse({ accessToken: new SecretString(accessToken), refreshToken: new SecretString(refreshToken), expiresInMs: (expiresIn ?? 0) * 1e3, refreshTokenExpiresInMs: refreshTokenExpiry * 1e3, scope: typeof decoded.scope === "string" ? decoded.scope : "", tokenType: typeof tokenType === "string" ? tokenType : "", retainedFields });
453
+ return new TokenResponse({ accessToken: new SecretString(accessToken), refreshToken: new SecretString(refreshToken), expiresInMs: (expiresIn ?? 0) * 1e3, refreshTokenExpiresInMs: refreshTokenExpiry * 1e3, scope: typeof decoded.scope === "string" ? decoded.scope : "", tokenType: typeof tokenType === "string" ? tokenType : "", retainedFields, mappedCredentialFields });
142
454
  }
143
455
  };
144
456
  var grantParameters = /* @__PURE__ */ new Set(["grant_type", "refresh_token", "client_id", "client_secret", "assertion", "client_assertion", "client_assertion_type"]);
145
457
  var standardResponseFields = /* @__PURE__ */ new Set(["access_token", "refresh_token", "expires_in", "refresh_token_expires_in", "scope", "token_type", "error", "error_description", "error_uri", "id_token"]);
458
+ function responsePathValue(response, path) {
459
+ let value = response;
460
+ for (const member of path.split(".")) {
461
+ if (!value || typeof value !== "object" || Array.isArray(value) || !Object.hasOwn(value, member)) return void 0;
462
+ value = value[member];
463
+ }
464
+ return typeof value === "string" ? value : void 0;
465
+ }
146
466
  function validateEndpointURL(value, allowsPlainHTTP, providerName) {
147
467
  let parsed;
148
468
  try {
@@ -194,7 +514,7 @@ function hasAllScopes(returnedScope, requiredScopes) {
194
514
  const granted = new Set(returnedScope.split(/[ ,]+/).filter(Boolean));
195
515
  return requiredScopes.every((scope) => granted.has(scope));
196
516
  }
197
- function hasExactScopes(returnedScope, requiredScopes) {
517
+ function hasExactScopes2(returnedScope, requiredScopes) {
198
518
  const granted = new Set(returnedScope.split(/[ ,]+/).filter(Boolean));
199
519
  return granted.size === new Set(requiredScopes).size && hasAllScopes(returnedScope, requiredScopes);
200
520
  }
@@ -222,12 +542,13 @@ function signJWTBearerAssertion(assertion, privateKey) {
222
542
  return new SecretString(`${unsigned}.${signature}`);
223
543
  }
224
544
  export {
545
+ ManifestCredentialRefreshDriver,
225
546
  RefreshSkewMs,
226
547
  TokenEndpoint,
227
548
  TokenResponse,
228
549
  forcedRefreshFloorMs,
229
550
  hasAllScopes,
230
- hasExactScopes,
551
+ hasExactScopes2 as hasExactScopes,
231
552
  isRefreshRequired,
232
553
  isRefreshRequiredWithIssueTime,
233
554
  parseRSAPrivateKeyPEM,
@@ -1,4 +1,5 @@
1
- import { O as OperationRef, f as BranchDefinition, Z as RetryPolicy, C as Call, M as MutationAttempt, Q as QueryAttempt } from './credentials-vrvW0d4j.js';
1
+ import { C as Call } from './credentials-JqG97kJ5.js';
2
+ import { O as OperationRef, e as BranchDefinition, J as RetryPolicy, M as MutationAttempt, Q as QueryAttempt } from './types-D6De3sry.js';
2
3
 
3
4
  /** Execute options owned by one operation; durations are milliseconds. */
4
5
  interface StepDefaults {
@@ -1,7 +1,8 @@
1
1
  import { z } from 'zod';
2
2
  import { inspect } from 'node:util';
3
3
  import { S3Client } from '@aws-sdk/client-s3';
4
- import { t as CredentialProvider$1, C as Call, W as RefreshingCredentialProvider$1, X as RejectedCredentialRefreshingProvider, u as CredentialRefreshDriver } from '../credentials-vrvW0d4j.js';
4
+ import { c as CredentialProvider$1, C as Call, R as RefreshingCredentialProvider$1, h as RejectedCredentialRefreshingProvider, d as CredentialRefreshDriver } from '../credentials-JqG97kJ5.js';
5
+ import '../types-D6De3sry.js';
5
6
  import '@superdurable/dex';
6
7
 
7
8
  /** Current secret-free project configuration schema shared with Dex Web. */
@@ -1,13 +1,15 @@
1
+ import {
2
+ OperationRefSchema
3
+ } from "../chunk-BQGYGCOQ.js";
1
4
  import {
2
5
  BodyTooLargeError,
3
6
  isLocalStorageHost,
4
7
  isPrivateBrokerHost,
5
8
  readBoundedBody
6
- } from "../chunk-IVJLNZVL.js";
9
+ } from "../chunk-BG65RPGH.js";
7
10
  import {
8
- OperationRefSchema,
9
11
  ReauthorizationRequiredError
10
- } from "../chunk-SOXYPIMO.js";
12
+ } from "../chunk-IHG6ADQG.js";
11
13
 
12
14
  // src/projectconfig/model.ts
13
15
  import { z } from "zod";
@@ -19,11 +19,24 @@ declare class AddressRefusal extends Error {
19
19
  /** Sanitized transport failure with positive evidence about dispatch. */
20
20
  declare class ProviderTransportError extends Error {
21
21
  readonly isProvablyNotSent: boolean;
22
- /** True only when no socket was assigned to the request. */
22
+ /** True only when the request failed before a usable connection could send HTTP bytes. */
23
23
  constructor(isProvablyNotSent: boolean);
24
24
  }
25
- /** Public-only HTTPS or explicitly owned private-network HTTP. */
26
- type AddressPolicy = 'publicHTTPS' | 'privateNetworkHTTP';
25
+ /** Provider emitted no response byte within the configured stall timeout. */
26
+ declare class ProviderStalledError extends Error {
27
+ /** Construct a sanitized no-byte timeout failure. */
28
+ constructor();
29
+ }
30
+ /** Public HTTPS, owned private HTTP, or caller-owned unrestricted transport. */
31
+ type AddressPolicy = 'publicHTTPS' | 'privateNetworkHTTP' | 'unrestricted';
32
+ /** Caller-owned transport for a provider exchange; implementations must not replay redirects.
33
+ * A client may return a complete body without calling onResponseChunk. If it calls the callback,
34
+ * it must feed every received chunk in order before returning and honor false by stopping the response.
35
+ */
36
+ interface ProviderHTTPClient {
37
+ /** Send one request and return its bounded response. */
38
+ send(request: ProviderRequest): Promise<ProviderResponse>;
39
+ }
27
40
  /** One bounded provider request; response bodies and URL values never enter errors. */
28
41
  interface ProviderRequest {
29
42
  /** Absolute provider URL. */ url: string;
@@ -31,9 +44,13 @@ interface ProviderRequest {
31
44
  /** Request headers, including provider credentials when needed. */ headers?: Record<string, string>;
32
45
  /** Caller-encoded request body. */ body?: Uint8Array;
33
46
  /** Maximum response body size in bytes. */ maxResponseBytes: number;
47
+ /** Optional separate prefix limit for non-2xx error bodies. */ maxErrorResponseBytes?: number;
48
+ /** Classify a non-2xx status even when its diagnostic body fails to read. */ ignoreErrorBodyReadFailure?: boolean;
34
49
  /** Network policy checked against all resolved addresses. */ addressPolicy: AddressPolicy;
35
50
  /** Request deadline in milliseconds. */ timeoutMs?: number;
36
51
  /** Caller cancellation. */ signal?: AbortSignal;
52
+ /** Observe chunks in arrival order; returning false stops a completed event stream. */ onResponseChunk?: (chunk: Uint8Array, status: number, headers: http.IncomingHttpHeaders) => boolean | void;
53
+ /** Abort a response when no byte arrives for this many milliseconds. */ stallTimeoutMs?: number;
37
54
  }
38
55
  /** Bounded response; redirects remain visible as 3xx responses. */
39
56
  interface ProviderResponse {
@@ -54,4 +71,4 @@ declare function readErrorTokens(body: Uint8Array, pointers: readonly string[]):
54
71
  /** Send once using pinned, policy-checked DNS results and no redirect replay. */
55
72
  declare function sendProviderRequest(request: ProviderRequest): Promise<ProviderResponse>;
56
73
 
57
- export { type AddressPolicy, AddressRefusal, BodyTooLargeError, MaxErrorBodyBytes, MaxRetryAfterDelayMs, type ProviderRequest, type ProviderResponse, ProviderTransportError, isHeaderSafeCredential, parseRetryAfter, readBoundedBody, readErrorTokens, sendProviderRequest, validateBaseURL };
74
+ export { type AddressPolicy, AddressRefusal, BodyTooLargeError, MaxErrorBodyBytes, MaxRetryAfterDelayMs, type ProviderHTTPClient, type ProviderRequest, type ProviderResponse, ProviderStalledError, ProviderTransportError, isHeaderSafeCredential, parseRetryAfter, readBoundedBody, readErrorTokens, sendProviderRequest, validateBaseURL };
@@ -3,6 +3,7 @@ import {
3
3
  BodyTooLargeError,
4
4
  MaxErrorBodyBytes,
5
5
  MaxRetryAfterDelayMs,
6
+ ProviderStalledError,
6
7
  ProviderTransportError,
7
8
  isHeaderSafeCredential,
8
9
  parseRetryAfter,
@@ -10,12 +11,13 @@ import {
10
11
  readErrorTokens,
11
12
  sendProviderRequest,
12
13
  validateBaseURL
13
- } from "../chunk-IVJLNZVL.js";
14
+ } from "../chunk-BG65RPGH.js";
14
15
  export {
15
16
  AddressRefusal,
16
17
  BodyTooLargeError,
17
18
  MaxErrorBodyBytes,
18
19
  MaxRetryAfterDelayMs,
20
+ ProviderStalledError,
19
21
  ProviderTransportError,
20
22
  isHeaderSafeCredential,
21
23
  parseRetryAfter,
@@ -0,0 +1,42 @@
1
+ import { C as Call, S as SecretString } from './credentials-JqG97kJ5.js';
2
+ import { ProviderHTTPClient } from './providerhttp/index.js';
3
+ import { Q as QueryAttempt } from './types-D6De3sry.js';
4
+ import { b as Query, Q as QueryDefinition } from './operation-CASLOg86.js';
5
+ import { T as TextGenerationRequest, q as TextGenerationResponse, W as WireFormat } from './wire_format-stEVruMs.js';
6
+
7
+ /** Configuration for one generateText Query; durations are milliseconds. */
8
+ interface TextGenerationQueryConfig {
9
+ /** Generated connector operation definition and sync Step defaults. */ definition: QueryDefinition;
10
+ /** Provider-specific adapter. */ wireFormat: WireFormat;
11
+ /** Validated provider base URL. */ baseURL: string;
12
+ /** Default model; blank requires a request model. */ connectionModel?: string;
13
+ /** HTTP exchange deadline. */ requestTimeoutMs: number;
14
+ /** Optional caller-owned HTTP transport, as with Go's HTTPClient. It must not replay redirects. */ httpClient?: ProviderHTTPClient;
15
+ /** Resolve private credential once per validated attempt. */ resolveCredential: (call: Call) => string | SecretString | Promise<string | SecretString>;
16
+ /** Maximum complete body or event stream bytes. */ maxResponseBytes: number;
17
+ /** Maximum one SSE event bytes; required for streaming. */ maxStreamEventBytes?: number;
18
+ }
19
+ /** Shared provider-neutral text generation Query; concurrent invocations own isolated state.
20
+ *
21
+ * Each invocation resolves the model, validates portable fields in order, applies model rules,
22
+ * resolves one credential, encodes the request, dispatches once, and classifies the response.
23
+ * Validation or configuration failure in the first five stages selects defect with zero requests.
24
+ * A silent request records a nil Dex heartbeat every five seconds, clearing a caller checkpoint.
25
+ * Stream text can include retried, blocked, or invalid attempts; only the final Result is authoritative.
26
+ * Sync Execute durability prevents a long local attempt from dispatching the billed call again.
27
+ * Structured numbers are bounded to 256 literal characters and exponent magnitude 400.
28
+ */
29
+ declare class TextGenerationQuery implements Query<TextGenerationRequest, TextGenerationResponse> {
30
+ #private;
31
+ /** Validate configuration before a provider request can be made. */
32
+ constructor(config: TextGenerationQueryConfig);
33
+ /** Return the connector's generateText definition. */ definition(): QueryDefinition;
34
+ /** Return optional request features declared by the provider. */ requestFeatures(): Readonly<WireFormat['features']>;
35
+ /** Return the provider model ID rule. */ modelIDRule(): WireFormat['modelIDRule'];
36
+ /** Validate in fixed order, dispatch once, and classify a branch or safe retry without exposing secrets. */
37
+ invoke(call: Call, request: TextGenerationRequest): Promise<QueryAttempt<TextGenerationResponse>>;
38
+ }
39
+ /** Construct a generateText Query, rejecting invalid definitions, wire formats, models, timeouts, and size limits before dispatch. */
40
+ declare function newTextGenerationQuery(config: TextGenerationQueryConfig): TextGenerationQuery;
41
+
42
+ export { TextGenerationQuery as T, type TextGenerationQueryConfig as a, newTextGenerationQuery as n };
@@ -1,5 +1,6 @@
1
1
  import { z } from 'zod';
2
- import { C as Call, a as CallID } from '../credentials-vrvW0d4j.js';
2
+ import { C as CallID } from '../types-D6De3sry.js';
3
+ import { C as Call } from '../credentials-JqG97kJ5.js';
3
4
  import '@superdurable/dex';
4
5
 
5
6
  /** The current attempt has no permission to send another provider write. */
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  CallIDSchema
3
- } from "../chunk-SOXYPIMO.js";
3
+ } from "../chunk-BQGYGCOQ.js";
4
+ import "../chunk-IHG6ADQG.js";
4
5
 
5
6
  // src/singledispatch/index.ts
6
7
  import { z } from "zod";
@@ -0,0 +1,15 @@
1
+ import { T as TextGenerationRequest } from '../wire_format-stEVruMs.js';
2
+ export { B as BlockedBranchID, C as CredentialHeader, D as DecodedResponse, a as DefectBranchID, E as EncodeRequestInput, b as EncodedRequest, c as ErrorOutcome, d as ErrorRule, F as FinishReason, G as GeneratedBranchID, I as InvalidResponseBranchID, M as Message, e as MessageRole, f as ModelIDRule, g as ModelRequestRules, P as ProviderRejectedBranchID, h as ProviderReportedError, R as ReasoningEffort, i as RequestFeatures, j as ResponsePart, S as ServerSentEvent, k as StreamDecodeState, l as StructuredOutput, m as StructuredOutputMode, n as StructuredOutputRules, o as TemperaturePolicy, p as TextGenerationOperationID, q as TextGenerationResponse, r as TruncatedBranchID, U as Usage, W as WireFormat, s as blockedOutcome, t as classifyError, u as invalidResponseOutcome, v as providerRejectedOutcome, w as quotaExhaustedOutcome, x as resolveModel, y as retryOutcome, z as temperatureAccepted, A as temperatureNotAccepted, H as temperatureRange, J as textGenerationBranchDefinitions, K as validateModelID, L as validatePortableRequest, N as validateStructuredOutputRules, O as validateWireFormat } from '../wire_format-stEVruMs.js';
3
+ export { T as TextGenerationQuery, a as TextGenerationQueryConfig, n as newTextGenerationQuery } from '../query-CeUaW9xU.js';
4
+ import '../types-D6De3sry.js';
5
+ import 'zod';
6
+ import '../credentials-JqG97kJ5.js';
7
+ import '@superdurable/dex';
8
+ import '../providerhttp/index.js';
9
+ import 'node:http';
10
+ import '../operation-CASLOg86.js';
11
+
12
+ /** Remove whitespace and one JSON Markdown fence, then validate the original schema. */
13
+ declare function extractStructuredAnswer(request: TextGenerationRequest, generatedText: string): string;
14
+
15
+ export { TextGenerationRequest, extractStructuredAnswer };