@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
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Connector TypeScript SDK
|
|
2
2
|
|
|
3
|
-
`@superdurable/connectors-sdk` is the provider-neutral TypeScript SDK for Dex connectors. It provides stable value contracts, typed Query and Mutation operations, Dex Step factories, credential refresh, OAuth token exchanges, HTTP safety helpers, project configuration loading, scripted mocks, and single-dispatch marker decisions. The package is at version `0.
|
|
3
|
+
`@superdurable/connectors-sdk` is the provider-neutral TypeScript SDK for Dex connectors. It provides stable value contracts, typed Query and Mutation operations, Dex Step factories, credential refresh, OAuth token exchanges, HTTP safety helpers, project configuration loading, scripted mocks, and single-dispatch marker decisions. The package is at version `0.3.0`, requires Node 22 or 24, and uses `@superdurable/dex` 1.28.0 or newer. It is ESM-only, matching Dex: use `import`; direct `require()` fails with `ERR_PACKAGE_PATH_NOT_EXPORTED`.
|
|
4
4
|
|
|
5
5
|
Install dependencies and verify the package from this directory:
|
|
6
6
|
|
|
@@ -17,14 +17,32 @@ node examples/refreshing_project.mjs
|
|
|
17
17
|
node examples/shared_project.mjs
|
|
18
18
|
node examples/connectormock.mjs
|
|
19
19
|
node examples/oauthtoken.mjs
|
|
20
|
+
node examples/manifest_refresh.mjs
|
|
20
21
|
node examples/singledispatch.mjs
|
|
21
22
|
node examples/fence.mjs
|
|
23
|
+
node examples/textgen.mjs
|
|
22
24
|
```
|
|
23
25
|
|
|
24
26
|
All durations in this package are milliseconds. Public JSON shapes use the Go SDK's field names. Credential values and project objects stay outside Flow state and cannot be serialized through their wrappers.
|
|
25
27
|
|
|
26
28
|
The [Dex integration harness](test/integ/step_flows.mjs) exercises the same Step factories through a real Worker and Client. Start `dexcli dev --open=false --dex-port 18891` with a local Dex server, set `DEX_SERVER_ADDRESS` when its gRPC address differs from `127.0.0.1:18891`, and run `npm run test:integration`. It verifies a happy path, optional branch, unwired optional branch failure, retry then success, retry exhaustion, uncertain mutation, a lost-response single-dispatch fence, repeated claim heartbeats, result Attribute persistence, progress and text Stream delivery, and scripted mock replay. It uses fixture operations and no provider credentials. CI runs the same harness in `sdkts-integration` using its once-per-run resolved Dex CLI.
|
|
27
29
|
|
|
30
|
+
## Portable text generation
|
|
31
|
+
|
|
32
|
+
The [runnable text generation example](examples/textgen.mjs) uses `newTextGenerationQuery` with the OpenAI-compatible Chat Completions adapter from `@superdurable/connectors-sdk/textgen/openaichat`. The `@superdurable/connectors-sdk/textgen` entrypoint also exports request and response contracts, model ID validation, portable schema checks, branch definitions, temperature policies, error rules, and the provider-neutral wire-format contract. The example exercises a local fixture provider; a consuming connector supplies a real connection, credential resolver, and Dex Step.
|
|
33
|
+
|
|
34
|
+
`generateText` requires sync Execute durability and a request timeout shorter than the Step Execute timeout. It writes streamed text deltas to a configured text Stream as bytes arrive; the Result text is authoritative after retries or a blocked response. Register that Stream in the Flow persistence schema. Its six branches are `generated`, `truncated`, `blocked`, `providerRejected`, `invalidResponse`, and `defect`; only `generated` is required. The query retries transport errors, stalled responses, interrupted streams, and safe HTTP failures. A credential is resolved once after local validation and is never stored in Flow state, results, failures, or receipts.
|
|
35
|
+
|
|
36
|
+
Each invocation resolves the trimmed request model or connection model, then validates messages, the token limit, the structured-output name and schema, declared features, temperature, and reasoning effort in that order. It applies model schema rules, resolves one credential, and encodes the request before dispatch. The first error in this order selects `defect` with zero provider requests. A nil heartbeat every five seconds keeps a silent call alive and clears any Step heartbeat checkpoint. Stream text may contain text from an attempt later retried or blocked, so consumers use the final Result for decisions. A caller may inject `httpClient` to own HTTP transport as in Go; the default transport permits private HTTPS endpoints and neither transport should replay redirects.
|
|
37
|
+
|
|
38
|
+
An injected HTTP client may return a complete SSE response body without calling `onResponseChunk`. If it calls `onResponseChunk`, it must feed every chunk in order before returning and stop when the callback returns `false`. A provider retry-delay reader that throws or returns a non-finite value is treated as absent, allowing the `Retry-After` header to supply the delay. Usage token counts are JavaScript numbers and require safe integers; unlike Go's `int64` counts, larger values are rejected because they cannot be represented exactly in the public TypeScript `Usage` contract.
|
|
39
|
+
|
|
40
|
+
The schema subset is the same as Go `sdkgo/textgen`: root object, closed objects with every property required, nullable optional values, arrays with `items`, and only the documented portable keywords. Structured output is checked locally even when the provider accepts a transformed schema. Model IDs are validated according to whether the adapter sends them in a JSON body or path segment. Optional request fields rejected by a wire format or selected model produce a `defect` branch before dispatch.
|
|
41
|
+
|
|
42
|
+
Exact structured-output numbers may use at most 256 literal characters and an exponent magnitude of at most 400. Date and time formats follow Go's parser, including calendar-valid dates, one-digit hours, comma fractions, and offsets through hour 24 and minute 60. The JSON-object instruction uses Go's sorted, HTML-safe JSON encoding so provider requests match the Go connector. The shared [string-format fixture](../sdkgo/textgen/textgentest/testdata/string_format_cases.json) runs in both Go and TypeScript tests.
|
|
43
|
+
|
|
44
|
+
The `@superdurable/connectors-sdk/textgen/textgentest` entrypoint ports Go's credential-safe fake provider and reusable exchange suite. [Its provider-dialect test](test/textgentest.test.ts) exercises model precedence, all common branches, error rules, complete and interrupted streams, usage, receipts, and credential redaction. The [Dex integration harness](test/integ/step_flows.mjs) calls `runTextGenerationDexScenarios` through a real Worker and Client to verify retry, heartbeat, text Stream persistence, and an unwired optional branch.
|
|
45
|
+
|
|
28
46
|
## Typed operations and credential resolution
|
|
29
47
|
|
|
30
48
|
The [runnable operation example](examples/operations.mjs) validates a Query definition, binds a live Step context to stable call identity, resolves credentials at invocation time, and completes a Go-compatible zero receipt. `Mutation` adds an idempotency-key method and may return `newMutationUncertain` only for a dispatched write that cannot be repeated or confirmed by read-back. `runQuery` and `runMutation` throw `RetryError` for a safe retry; a Step factory turns that into Dex retry behavior. The example uses a test context; application Steps receive `AsyncContext` from Dex.
|
|
@@ -101,6 +119,7 @@ if (result.kind !== 'branch' || retryPolicy.totalDurationMs !== 3600000 || crede
|
|
|
101
119
|
|
|
102
120
|
The [complete runnable HTTP example](examples/providerhttp.mjs) sends one bounded request to a local server. `sendProviderRequest` resolves every address before connecting, pins the chosen address for IP literals and hostnames, and returns redirects without following them. `ProviderTransportError.isProvablyNotSent` is true for DNS, TCP connect, or TLS handshake failures before a usable connection could send HTTP bytes. Failures after connection or request writing have an unknown dispatch outcome, matching the Go connectors' DNS, connect, TLS, and obtained-connection trace.
|
|
103
121
|
`BodyTooLargeError` preserves the response `status` and `headers`, so a caller can classify a 429 or 503 and its `Retry-After` even when the response body exceeds its limit.
|
|
122
|
+
`ProviderRequest.onBodyLimit` defaults to `destroy`, closing an oversized response immediately. Set it to `drain` for provider calls that continue or bill work after a client disconnect: the SDK releases buffered bytes, delivers callback bytes only through the limit, reads to EOF, then rejects with `BodyTooLargeError`. Drain requires at least one positive `timeoutMs`, positive `stallTimeoutMs`, or `signal`; otherwise `sendProviderRequest` throws `TypeError`. These bounds remain active while draining. For non-2xx responses, an explicit `maxErrorResponseBytes` takes precedence and truncates the body immediately instead of draining. Once drain begins, a later read failure still rejects with `BodyTooLargeError`, including when `ignoreErrorBodyReadFailure` is set. The runnable example exercises `drain` with a local server.
|
|
104
123
|
|
|
105
124
|
```js
|
|
106
125
|
import { createServer } from 'node:http';
|
|
@@ -212,7 +231,9 @@ mutation.assertNoFailures();
|
|
|
212
231
|
|
|
213
232
|
## OAuth token refresh
|
|
214
233
|
|
|
215
|
-
The [runnable OAuth exchange example](examples/oauthtoken.mjs) shows a provider-neutral refresh-token grant. The injected `fetch` returns a local fixture; a connector normally uses the provider's fixed HTTPS token endpoint. `TokenEndpoint` also supports client-credentials and JWT-bearer grants, optional Basic authentication, declared response fields, bounded responses, and terminal OAuth error-code classification.
|
|
234
|
+
The [runnable OAuth exchange example](examples/oauthtoken.mjs) shows a provider-neutral refresh-token grant. The injected `fetch` returns a local fixture; a connector normally uses the provider's fixed HTTPS token endpoint. `TokenEndpoint` also supports client-credentials and JWT-bearer grants, optional Basic authentication, declared response fields, bounded responses, and terminal OAuth error-code classification. The [manifest refresh example](examples/manifest_refresh.mjs) uses `ManifestCredentialRefreshDriver` with a generated authorization declaration and field map to exchange refresh-token, client-credentials, or JWT-bearer grants, preserve rotated material, check scopes, and replace only mapped fields. Constructor options also accept startup-pinned `settings`, a caller-owned `fetch`, and an injectable `now` clock. Credentials for a connector with several authorization methods use `authMethodId`; an empty value selects `defaultMethod`. Refresh validation uses `requiredScopes` when declared, and a grant can override `exactScopes` or `acceptsMissingTokenType`. `refresh.scopeSeparators` selects `whitespace` (the default) or `whitespaceAndComma` for both exact and all-scope checks. A blank client secret requires reauthorization unless `refresh.allowsPublicClient` is true. A response lacking `expires_in` is retryable unless the manifest declares `missingExpiry`. Only declared terminal OAuth errors below HTTP 500 require reauthorization; transport, server, and other token errors remain retryable.
|
|
235
|
+
|
|
236
|
+
An access token with no stored expiry remains usable; the current project provider requires authoritative expiry before forcing refresh after a provider rejection. A response without `expires_in` uses the manifest's `missingExpiry` lifetime or returns an expired result for `keep`. The current project provider rejects that result without changing stored material. Refreshing multiple grants follows the Go driver's current single-expiry behavior: active grants rotate together and the earliest expiry wins. A process failure between provider response and persistence, or failure of a later grant after an earlier rotation, can lose a one-use refresh token.
|
|
216
237
|
|
|
217
238
|
```js
|
|
218
239
|
import { SecretString } from '../dist/index.js';
|
|
@@ -260,6 +281,16 @@ await fence.recordProviderId(result.providerId);
|
|
|
260
281
|
|
|
261
282
|
## Release notes
|
|
262
283
|
|
|
284
|
+
### 0.3.0
|
|
285
|
+
|
|
286
|
+
Adds declaration-driven required and exact scope checks, per-grant token-type rules, Go-compatible duration validation, and default-method selection for manifest refresh drivers. Scope parsing now follows `scopeSeparators`: whitespace by default, or spaces and commas when declared. A blank client secret now requires reauthorization unless public-client refresh is declared. A missing `expires_in` is retryable unless the manifest declares `missingExpiry`. These changes alter the published 0.2.0 refresh driver's behavior.
|
|
287
|
+
|
|
288
|
+
Public HTTPS address checks now refuse the IPv4 broadcast address, matching Go's global-unicast rule. Adds opt-in draining of oversized Provider HTTP responses with bounded cancellation and byte-exact chunk callbacks.
|
|
289
|
+
|
|
290
|
+
### 0.2.0
|
|
291
|
+
|
|
292
|
+
Adds manifest-driven refresh for OAuth, client-credentials, and JWT bearer grants, including rotated refresh tokens, declared scope and token-type checks, and retryable token endpoint failures. Connector migration requires schema and codegen support for the new token-type declarations.
|
|
293
|
+
|
|
263
294
|
### 0.1.0
|
|
264
295
|
|
|
265
296
|
First public ESM-only TypeScript Connector SDK release: Go-compatible contracts and conformance fixtures, safe provider HTTP, project configuration with durable credential refresh and guarded local writeback, typed operations and Dex Step factories, OAuth exchanges, scripted mocks, and the live single-dispatch fence. No live provider authorization has been tested.
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
import {
|
|
2
|
+
FailureKindSchema
|
|
3
|
+
} from "./chunk-IHG6ADQG.js";
|
|
4
|
+
|
|
5
|
+
// src/textgen/structured_output.ts
|
|
6
|
+
var keywords = /* @__PURE__ */ new Set(["type", "properties", "required", "additionalProperties", "items", "enum", "const", "description", "title", "minimum", "maximum", "minLength", "maxLength", "minItems", "maxItems", "format"]);
|
|
7
|
+
var movableSchemaKeywords = Object.freeze(["minimum", "maximum", "minLength", "maxLength", "minItems", "maxItems", "format"]);
|
|
8
|
+
var removableSchemaKeywords = Object.freeze([...movableSchemaKeywords, "title", "additionalProperties"]);
|
|
9
|
+
var types = /* @__PURE__ */ new Set(["object", "array", "string", "number", "integer", "boolean", "null"]);
|
|
10
|
+
var formats = /* @__PURE__ */ new Set(["date-time", "date", "time", "uuid"]);
|
|
11
|
+
function jsonPointerError(pointer, reason) {
|
|
12
|
+
return new Error(`at ${pointer || "the root"}: ${reason}`);
|
|
13
|
+
}
|
|
14
|
+
function isJSONObject(value) {
|
|
15
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
16
|
+
}
|
|
17
|
+
function pointerSegment(value) {
|
|
18
|
+
return value.replaceAll("~", "~0").replaceAll("/", "~1");
|
|
19
|
+
}
|
|
20
|
+
function boundedNumber(value) {
|
|
21
|
+
return typeof value === "number" && Number.isFinite(value) && parseRational(String(value)) !== void 0;
|
|
22
|
+
}
|
|
23
|
+
function validateCountBounds(node, pointer, minimum, maximum) {
|
|
24
|
+
for (const keyword of [minimum, maximum]) if (keyword in node && (!Number.isSafeInteger(node[keyword]) || node[keyword] < 0)) throw jsonPointerError(pointer, `"${keyword}" must be a non-negative integer`);
|
|
25
|
+
if (minimum in node && maximum in node && node[minimum] > node[maximum]) throw jsonPointerError(pointer, `"${minimum}" exceeds "${maximum}"`);
|
|
26
|
+
}
|
|
27
|
+
function schemaType(node, pointer) {
|
|
28
|
+
const type = node.type;
|
|
29
|
+
if (typeof type === "string" && types.has(type)) return [type, false];
|
|
30
|
+
if (Array.isArray(type) && type.length === 2 && type.every((entry) => typeof entry === "string") && type.includes("null")) {
|
|
31
|
+
const other = type.find((entry) => entry !== "null");
|
|
32
|
+
if (other && types.has(other)) return [other, true];
|
|
33
|
+
}
|
|
34
|
+
throw jsonPointerError(pointer, '"type" must be one JSON type or a pair of one type and "null"');
|
|
35
|
+
}
|
|
36
|
+
function canonicalizePortableSchema(schema) {
|
|
37
|
+
function checkSerializable(value, seen) {
|
|
38
|
+
if (typeof value === "number" && !Number.isFinite(value)) throw new Error("schema must be JSON serializable");
|
|
39
|
+
if (typeof value === "undefined" || typeof value === "function" || typeof value === "symbol" || typeof value === "bigint") throw new Error("schema must be JSON serializable");
|
|
40
|
+
if (value && typeof value === "object") {
|
|
41
|
+
if ("toJSON" in value && typeof value.toJSON === "function") return;
|
|
42
|
+
if (seen.has(value)) throw new Error("schema must be JSON serializable");
|
|
43
|
+
seen.add(value);
|
|
44
|
+
for (const child of Object.values(value)) checkSerializable(child, seen);
|
|
45
|
+
seen.delete(value);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
checkSerializable(schema, /* @__PURE__ */ new Set());
|
|
49
|
+
let canonical;
|
|
50
|
+
try {
|
|
51
|
+
canonical = JSON.parse(JSON.stringify(schema));
|
|
52
|
+
} catch {
|
|
53
|
+
throw new Error("schema must be JSON serializable");
|
|
54
|
+
}
|
|
55
|
+
if (!isJSONObject(canonical)) throw new Error("schema must be a JSON object");
|
|
56
|
+
const [rootType, nullable] = schemaType(canonical, "");
|
|
57
|
+
if (rootType !== "object" || nullable) throw jsonPointerError("", 'the root must have type "object"');
|
|
58
|
+
checkNode(canonical, "", 1);
|
|
59
|
+
return canonical;
|
|
60
|
+
}
|
|
61
|
+
function checkNode(node, pointer, depth) {
|
|
62
|
+
if (depth > 10) throw jsonPointerError(pointer, "the schema is nested deeper than 10 levels");
|
|
63
|
+
for (const keyword of Object.keys(node).sort()) if (!keywords.has(keyword)) throw jsonPointerError(pointer, `keyword ${JSON.stringify(keyword.slice(0, 64))} is outside the portable subset`);
|
|
64
|
+
const [type] = schemaType(node, pointer);
|
|
65
|
+
const keywordRequiredType = { properties: "object", required: "object", additionalProperties: "object", items: "array", minItems: "array", maxItems: "array", minLength: "string", maxLength: "string", format: "string" };
|
|
66
|
+
for (const keyword of Object.keys(node).sort()) {
|
|
67
|
+
if (keywordRequiredType[keyword] && keywordRequiredType[keyword] !== type) throw jsonPointerError(pointer, `keyword "${keyword}" applies only to type "${keywordRequiredType[keyword]}"`);
|
|
68
|
+
if ((keyword === "minimum" || keyword === "maximum") && type !== "number" && type !== "integer") throw jsonPointerError(pointer, `keyword "${keyword}" applies only to number and integer types`);
|
|
69
|
+
}
|
|
70
|
+
for (const keyword of ["description", "title"]) if (keyword in node && typeof node[keyword] !== "string") throw jsonPointerError(pointer, `"${keyword}" must be a string`);
|
|
71
|
+
if ("enum" in node && (!Array.isArray(node.enum) || node.enum.length === 0)) throw jsonPointerError(pointer, '"enum" must be a non-empty array');
|
|
72
|
+
for (const value of node.enum ?? []) if (!scalar(value)) throw jsonPointerError(pointer, '"enum" values must be strings, numbers, booleans, or null');
|
|
73
|
+
if ("const" in node && !scalar(node.const)) throw jsonPointerError(pointer, '"const" must be a string, number, boolean, or null');
|
|
74
|
+
if (type === "object") {
|
|
75
|
+
if (!isJSONObject(node.properties)) throw jsonPointerError(pointer, 'an object requires a "properties" object');
|
|
76
|
+
if (node.additionalProperties !== false) throw jsonPointerError(pointer, 'an object must set "additionalProperties" to false');
|
|
77
|
+
if (!Array.isArray(node.required)) throw jsonPointerError(pointer, 'an object must list every property in "required"');
|
|
78
|
+
const required = node.required;
|
|
79
|
+
if (required.some((name) => typeof name !== "string") || new Set(required).size !== required.length) throw jsonPointerError(pointer, '"required" must list property names once each');
|
|
80
|
+
for (const name of required) if (!Object.hasOwn(node.properties, name)) throw jsonPointerError(pointer, '"required" lists a property that "properties" does not declare');
|
|
81
|
+
if (required.length !== Object.keys(node.properties).length) throw jsonPointerError(pointer, 'an object must list every property in "required"; use type [T, "null"] for an optional value');
|
|
82
|
+
for (const [name, child] of Object.entries(node.properties).sort(([a], [b]) => a.localeCompare(b))) {
|
|
83
|
+
const childPointer = `${pointer}/properties/${pointerSegment(name)}`;
|
|
84
|
+
if (!isJSONObject(child)) throw jsonPointerError(childPointer, "a property schema must be an object");
|
|
85
|
+
checkNode(child, childPointer, depth + 1);
|
|
86
|
+
}
|
|
87
|
+
} else if (type === "array") {
|
|
88
|
+
validateCountBounds(node, pointer, "minItems", "maxItems");
|
|
89
|
+
if (!isJSONObject(node.items)) throw jsonPointerError(pointer, 'an array requires an "items" schema object');
|
|
90
|
+
checkNode(node.items, `${pointer}/items`, depth + 1);
|
|
91
|
+
} else if (type === "string") {
|
|
92
|
+
if ("format" in node && !formats.has(node.format)) throw jsonPointerError(pointer, '"format" must be date-time, date, time, or uuid');
|
|
93
|
+
validateCountBounds(node, pointer, "minLength", "maxLength");
|
|
94
|
+
} else if (type === "number" || type === "integer") {
|
|
95
|
+
for (const keyword of ["minimum", "maximum"]) if (keyword in node && !boundedNumber(node[keyword])) throw jsonPointerError(pointer, `"${keyword}" must be a number`);
|
|
96
|
+
if (node.minimum !== void 0 && node.maximum !== void 0 && node.minimum > node.maximum) throw jsonPointerError(pointer, '"minimum" exceeds "maximum"');
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
function scalar(value) {
|
|
100
|
+
return value === null || typeof value === "string" || typeof value === "boolean" || boundedNumber(value);
|
|
101
|
+
}
|
|
102
|
+
function transformSchemaForProvider(schema, rules) {
|
|
103
|
+
const copy = JSON.parse(JSON.stringify(schema));
|
|
104
|
+
function visit(node) {
|
|
105
|
+
const constraints = [];
|
|
106
|
+
for (const keyword of [...rules.keywordsMovedToDescription ?? []].sort()) if (Object.hasOwn(node, keyword)) {
|
|
107
|
+
constraints.push(`${keyword}: ${marshalGoJSON(node[keyword])}`);
|
|
108
|
+
delete node[keyword];
|
|
109
|
+
}
|
|
110
|
+
if (constraints.length) node.description = `${typeof node.description === "string" && node.description ? `${node.description} ` : ""}(${constraints.join(", ")})`;
|
|
111
|
+
for (const keyword of rules.keywordsRemoved ?? []) delete node[keyword];
|
|
112
|
+
if (isJSONObject(node.properties)) {
|
|
113
|
+
for (const child of Object.values(node.properties)) if (isJSONObject(child)) visit(child);
|
|
114
|
+
}
|
|
115
|
+
if (isJSONObject(node.items)) visit(node.items);
|
|
116
|
+
}
|
|
117
|
+
visit(copy);
|
|
118
|
+
return copy;
|
|
119
|
+
}
|
|
120
|
+
function jsonObjectInstruction(name, description, schema) {
|
|
121
|
+
return `Respond with only one JSON object, without Markdown code fences, that matches the JSON Schema named ${JSON.stringify(name)}${description ? ` (${description})` : ""}:
|
|
122
|
+
${marshalGoJSON(schema)}`;
|
|
123
|
+
}
|
|
124
|
+
function marshalGoJSON(value) {
|
|
125
|
+
if (Array.isArray(value)) return `[${value.map(marshalGoJSON).join(",")}]`;
|
|
126
|
+
if (isJSONObject(value)) {
|
|
127
|
+
const keys = Object.keys(value).sort((left, right) => Buffer.compare(Buffer.from(left), Buffer.from(right)));
|
|
128
|
+
return `{${keys.map((key) => `${marshalGoJSON(key)}:${marshalGoJSON(value[key])}`).join(",")}}`;
|
|
129
|
+
}
|
|
130
|
+
const encoded = JSON.stringify(value);
|
|
131
|
+
if (encoded === void 0) throw new Error("schema must be JSON serializable");
|
|
132
|
+
return encoded.replace(/[<>&\u2028\u2029]/g, (character) => `\\u${character.codePointAt(0).toString(16).padStart(4, "0")}`);
|
|
133
|
+
}
|
|
134
|
+
function extractStructuredAnswer(request, generatedText) {
|
|
135
|
+
if (!request.structuredOutput) return generatedText;
|
|
136
|
+
const schema = canonicalizePortableSchema(request.structuredOutput.schema);
|
|
137
|
+
return extractStructuredAnswerFromSchema(schema, generatedText);
|
|
138
|
+
}
|
|
139
|
+
function extractStructuredAnswerFromSchema(schema, generatedText) {
|
|
140
|
+
let text = generatedText.trim();
|
|
141
|
+
const fence = /^```([^\n]*)\n([\s\S]*)```$/.exec(text);
|
|
142
|
+
if (fence && (!fence[1]?.trim() || fence[1].trim().toLowerCase() === "json")) text = (fence[2] ?? "").trim();
|
|
143
|
+
let value;
|
|
144
|
+
const sourceByHolder = /* @__PURE__ */ new WeakMap();
|
|
145
|
+
try {
|
|
146
|
+
const sourceReviver = function(key, child, context) {
|
|
147
|
+
if (typeof child === "number" && context.source !== void 0) {
|
|
148
|
+
let sources = sourceByHolder.get(this);
|
|
149
|
+
if (!sources) {
|
|
150
|
+
sources = /* @__PURE__ */ new Map();
|
|
151
|
+
sourceByHolder.set(this, sources);
|
|
152
|
+
}
|
|
153
|
+
sources.set(key, context.source);
|
|
154
|
+
}
|
|
155
|
+
return child;
|
|
156
|
+
};
|
|
157
|
+
value = JSON.parse(text, sourceReviver);
|
|
158
|
+
} catch {
|
|
159
|
+
throw jsonPointerError("", "the text is not one JSON value");
|
|
160
|
+
}
|
|
161
|
+
const numbers = /* @__PURE__ */ new Map();
|
|
162
|
+
function assign(current, pointer) {
|
|
163
|
+
if (current === null || typeof current !== "object") return;
|
|
164
|
+
for (const [name, child] of Object.entries(current)) {
|
|
165
|
+
const childPointer = `${pointer}/${pointerSegment(name)}`;
|
|
166
|
+
if (typeof child === "number") numbers.set(childPointer, sourceByHolder.get(current)?.get(name) ?? "");
|
|
167
|
+
else assign(child, childPointer);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
assign(value, "");
|
|
171
|
+
validateInstance(value, schema, "", numbers);
|
|
172
|
+
return text;
|
|
173
|
+
}
|
|
174
|
+
function validateInstance(value, schema, pointer, numbers) {
|
|
175
|
+
const [type, nullable] = schemaType(schema, pointer);
|
|
176
|
+
const number = typeof value === "number" ? parseRational(numbers.get(pointer) ?? "") : void 0;
|
|
177
|
+
if (typeof value === "number" && !number) throw jsonPointerError(pointer, "the number is out of range");
|
|
178
|
+
if (value === null) {
|
|
179
|
+
if (!nullable && type !== "null") throw jsonPointerError(pointer, `expected type ${type}, found null`);
|
|
180
|
+
} else {
|
|
181
|
+
const actualType = Array.isArray(value) ? "array" : isJSONObject(value) ? "object" : typeof value;
|
|
182
|
+
if (!(type === actualType || type === "integer" && number && number.numerator % number.denominator === 0n || type === "number" && number)) throw jsonPointerError(pointer, `expected type ${type}`);
|
|
183
|
+
}
|
|
184
|
+
const equal = (expected) => {
|
|
185
|
+
if (typeof expected === "number" && number) {
|
|
186
|
+
const wanted = parseRational(String(expected));
|
|
187
|
+
return wanted !== void 0 && compareRational(wanted, number) === 0;
|
|
188
|
+
}
|
|
189
|
+
return Object.is(expected, value);
|
|
190
|
+
};
|
|
191
|
+
if (Array.isArray(schema.enum) && !schema.enum.some(equal)) throw jsonPointerError(pointer, "the value is not one of the enum values");
|
|
192
|
+
if ("const" in schema && !equal(schema.const)) throw jsonPointerError(pointer, "the value does not equal const");
|
|
193
|
+
if (value === null) return;
|
|
194
|
+
if (type === "object" && isJSONObject(value) && isJSONObject(schema.properties)) {
|
|
195
|
+
for (const name of Object.keys(value).sort()) if (!Object.hasOwn(schema.properties, name)) throw jsonPointerError(pointer, "the object has a property the schema does not declare");
|
|
196
|
+
for (const [name, child] of Object.entries(schema.properties).sort(([a], [b]) => a.localeCompare(b))) {
|
|
197
|
+
const childPointer = `${pointer}/${pointerSegment(name)}`;
|
|
198
|
+
if (!Object.hasOwn(value, name)) throw jsonPointerError(childPointer, "a required property is missing");
|
|
199
|
+
if (isJSONObject(child)) validateInstance(value[name], child, childPointer, numbers);
|
|
200
|
+
}
|
|
201
|
+
} else if (type === "array" && Array.isArray(value) && isJSONObject(schema.items)) {
|
|
202
|
+
checkInstanceCount(value.length, schema, pointer, "minItems", "maxItems", "items");
|
|
203
|
+
value.forEach((item, index) => validateInstance(item, schema.items, `${pointer}/${index}`, numbers));
|
|
204
|
+
} else if (type === "string" && typeof value === "string") {
|
|
205
|
+
checkInstanceCount([...value].length, schema, pointer, "minLength", "maxLength", "characters");
|
|
206
|
+
if (typeof schema.format === "string" && !matchesFormat(value, schema.format)) throw jsonPointerError(pointer, `the string does not match format ${schema.format}`);
|
|
207
|
+
} else if ((type === "number" || type === "integer") && typeof value === "number") {
|
|
208
|
+
if (number && typeof schema.minimum === "number" && compareRational(number, parseRational(String(schema.minimum))) < 0) throw jsonPointerError(pointer, "the value is below minimum");
|
|
209
|
+
if (number && typeof schema.maximum === "number" && compareRational(number, parseRational(String(schema.maximum))) > 0) throw jsonPointerError(pointer, "the value is above maximum");
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
function checkInstanceCount(value, schema, pointer, minimum, maximum, unit) {
|
|
213
|
+
if (typeof schema[minimum] === "number" && value < schema[minimum]) throw jsonPointerError(pointer, `the value has fewer ${unit} than ${minimum}`);
|
|
214
|
+
if (typeof schema[maximum] === "number" && value > schema[maximum]) throw jsonPointerError(pointer, `the value has more ${unit} than ${maximum}`);
|
|
215
|
+
}
|
|
216
|
+
function matchesFormat(value, format) {
|
|
217
|
+
if (format === "uuid") return /^[0-9A-Fa-f]{8}-(?:[0-9A-Fa-f]{4}-){3}[0-9A-Fa-f]{12}$/.test(value);
|
|
218
|
+
if (format === "date") {
|
|
219
|
+
const match2 = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value);
|
|
220
|
+
return !!match2 && isValidCalendarDate(Number(match2[1]), Number(match2[2]), Number(match2[3]));
|
|
221
|
+
}
|
|
222
|
+
if (format === "time") return matchesGoClock(value);
|
|
223
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})T(.+)$/.exec(value);
|
|
224
|
+
if (!match || !matchesGoClock(match[4] ?? "")) return false;
|
|
225
|
+
return isValidCalendarDate(Number(match[1]), Number(match[2]), Number(match[3]));
|
|
226
|
+
}
|
|
227
|
+
function isValidCalendarDate(year, month, day) {
|
|
228
|
+
const daysInMonth = [31, year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month - 1] ?? 0;
|
|
229
|
+
return month >= 1 && month <= 12 && day >= 1 && day <= daysInMonth;
|
|
230
|
+
}
|
|
231
|
+
function matchesGoClock(value) {
|
|
232
|
+
const match = /^(\d{1,2}):(\d{2}):(\d{2})(?:[.,]\d+)?(?:Z|[+-](\d{2}):(\d{2}))$/.exec(value);
|
|
233
|
+
return !!match && Number(match[1]) < 24 && Number(match[2]) < 60 && Number(match[3]) < 60 && Number(match[4] ?? 0) <= 24 && Number(match[5] ?? 0) <= 60;
|
|
234
|
+
}
|
|
235
|
+
function parseRational(literal) {
|
|
236
|
+
if (!literal || literal.length > 256) return void 0;
|
|
237
|
+
const match = /^(-?)(0|[1-9]\d*)(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/.exec(literal);
|
|
238
|
+
if (!match) return void 0;
|
|
239
|
+
const exponent = Number(match[4] ?? 0);
|
|
240
|
+
if (!Number.isInteger(exponent) || Math.abs(exponent) > 400) return void 0;
|
|
241
|
+
const digits = (match[2] ?? "") + (match[3] ?? "");
|
|
242
|
+
const power = exponent - (match[3]?.length ?? 0);
|
|
243
|
+
const signed = BigInt(digits) * (match[1] === "-" ? -1n : 1n);
|
|
244
|
+
return power >= 0 ? { numerator: signed * 10n ** BigInt(power), denominator: 1n } : { numerator: signed, denominator: 10n ** BigInt(-power) };
|
|
245
|
+
}
|
|
246
|
+
function compareRational(left, right) {
|
|
247
|
+
const difference = left.numerator * right.denominator - right.numerator * left.denominator;
|
|
248
|
+
return difference < 0n ? -1 : difference > 0n ? 1 : 0;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// src/textgen/wire_format.ts
|
|
252
|
+
function temperatureAccepted() {
|
|
253
|
+
return { kind: "accepted" };
|
|
254
|
+
}
|
|
255
|
+
function temperatureRange(minimum, maximum) {
|
|
256
|
+
return { kind: "range", minimum, maximum };
|
|
257
|
+
}
|
|
258
|
+
function temperatureNotAccepted() {
|
|
259
|
+
return { kind: "notAccepted" };
|
|
260
|
+
}
|
|
261
|
+
function validateStructuredOutputRules(rules) {
|
|
262
|
+
if (!["none", "jsonSchema", "jsonObjectWithInstruction"].includes(rules.mode)) throw new TypeError(`structured output mode ${JSON.stringify(rules.mode)} is invalid`);
|
|
263
|
+
for (const keyword of rules.keywordsMovedToDescription ?? []) if (!movableSchemaKeywords.includes(keyword)) throw new TypeError(`schema keyword ${JSON.stringify(keyword)} cannot be moved to a description`);
|
|
264
|
+
for (const keyword of rules.keywordsRemoved ?? []) if (!removableSchemaKeywords.includes(keyword)) throw new TypeError(`schema keyword ${JSON.stringify(keyword)} cannot be removed`);
|
|
265
|
+
}
|
|
266
|
+
function retryOutcome(kind) {
|
|
267
|
+
return { disposition: "retry", kind };
|
|
268
|
+
}
|
|
269
|
+
function providerRejectedOutcome(kind) {
|
|
270
|
+
return { disposition: "providerRejected", kind };
|
|
271
|
+
}
|
|
272
|
+
function quotaExhaustedOutcome() {
|
|
273
|
+
return providerRejectedOutcome("QUOTA_EXHAUSTED");
|
|
274
|
+
}
|
|
275
|
+
function blockedOutcome() {
|
|
276
|
+
return { disposition: "blocked", kind: "PROVIDER_REJECTION" };
|
|
277
|
+
}
|
|
278
|
+
function invalidResponseOutcome() {
|
|
279
|
+
return { disposition: "invalidResponse", kind: "PROTOCOL" };
|
|
280
|
+
}
|
|
281
|
+
var ProviderReportedError = class extends Error {
|
|
282
|
+
/** Bounded machine-readable tokens. */
|
|
283
|
+
errorTokens;
|
|
284
|
+
/** Construct without exposing provider message text. */
|
|
285
|
+
constructor(errorTokens) {
|
|
286
|
+
super("provider reported an error inside a 2xx response");
|
|
287
|
+
this.name = "ProviderReportedError";
|
|
288
|
+
this.errorTokens = errorTokens;
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
function classifyError(rules, statusCode, tokens, isReported = false) {
|
|
292
|
+
for (const rule of rules) if ((rule.statusCode === 0 || rule.statusCode === statusCode) && (!isReported || rule.statusCode === 0) && (!rule.errorToken || tokens.includes(rule.errorToken))) return rule.outcome;
|
|
293
|
+
if (isReported) return invalidResponseOutcome();
|
|
294
|
+
if (statusCode === 429) return retryOutcome("RATE_LIMIT");
|
|
295
|
+
if (statusCode === 408 || statusCode >= 500 && statusCode !== 501) return retryOutcome("AVAILABILITY");
|
|
296
|
+
const kinds = { 401: "AUTHENTICATION", 402: "QUOTA_EXHAUSTED", 403: "AUTHORIZATION", 404: "NOT_FOUND", 409: "CONFLICT" };
|
|
297
|
+
if (statusCode >= 300 && statusCode < 400) return invalidResponseOutcome();
|
|
298
|
+
return providerRejectedOutcome(kinds[statusCode] ?? "PROVIDER_REJECTION");
|
|
299
|
+
}
|
|
300
|
+
function validateWireFormat(format) {
|
|
301
|
+
if (!format.providerName) throw new TypeError("wire format provider name is required");
|
|
302
|
+
if (format.modelIDRule !== "body" && format.modelIDRule !== "pathSegment") throw new TypeError("wire format model ID rule is invalid");
|
|
303
|
+
if (typeof format.rulesForModel !== "function" || typeof format.encodeRequest !== "function") throw new TypeError("wire format rulesForModel and encodeRequest are required");
|
|
304
|
+
if (!format.decodeResponse && !format.decodeStreamEvent) throw new TypeError("wire format requires decodeResponse, decodeStreamEvent, or both");
|
|
305
|
+
if (typeof format.credentialHeader?.name !== "string" || !/^[!#$%&'*+.^_`|~0-9A-Za-z-]{1,128}$/.test(format.credentialHeader.name)) throw new TypeError("wire format credential header name is invalid");
|
|
306
|
+
if (typeof format.credentialHeader.prefix !== "string" || !/^[\x20-\x7e]*$/.test(format.credentialHeader.prefix)) throw new TypeError("wire format credential header prefix must be printable ASCII");
|
|
307
|
+
for (const [token, reason] of Object.entries(format.finishReasons)) if (!/^[A-Za-z0-9_.:-]{1,64}$/.test(token) || !["stop", "length", "contentPolicy", "refusal"].includes(reason)) throw new TypeError("wire format finish token is invalid");
|
|
308
|
+
for (const [index, rule] of (format.errorRules ?? []).entries()) {
|
|
309
|
+
if (!Number.isInteger(rule.statusCode) || rule.statusCode !== 0 && (rule.statusCode < 300 || rule.statusCode > 599)) throw new TypeError(`error rule ${index} status is invalid`);
|
|
310
|
+
if (rule.errorToken && !/^[A-Za-z0-9_.:-]{1,64}$/.test(rule.errorToken)) throw new TypeError(`error rule ${index} token is invalid`);
|
|
311
|
+
if (!rule.outcome || !["retry", "providerRejected", "invalidResponse", "blocked"].includes(rule.outcome.disposition) || !FailureKindSchema.safeParse(rule.outcome.kind).success) throw new TypeError(`error rule ${index} outcome is invalid`);
|
|
312
|
+
}
|
|
313
|
+
for (const pointer of format.errorTokenPointers ?? []) if (pointer !== "" && !pointer.startsWith("/")) throw new TypeError("wire format error-token pointer must start with /");
|
|
314
|
+
for (const name of [...format.requestIDHeaders ?? [], ...format.rateLimitHeaders ?? []]) if (!/^[!#$%&'*+.^_`|~0-9A-Za-z-]{1,128}$/.test(name)) throw new TypeError("wire format response header name is invalid");
|
|
315
|
+
if (!Number.isSafeInteger(format.stallTimeoutMs ?? 0) || (format.stallTimeoutMs ?? 0) < 0) throw new TypeError("wire format stall timeout cannot be negative");
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
export {
|
|
319
|
+
canonicalizePortableSchema,
|
|
320
|
+
transformSchemaForProvider,
|
|
321
|
+
jsonObjectInstruction,
|
|
322
|
+
extractStructuredAnswer,
|
|
323
|
+
extractStructuredAnswerFromSchema,
|
|
324
|
+
temperatureAccepted,
|
|
325
|
+
temperatureRange,
|
|
326
|
+
temperatureNotAccepted,
|
|
327
|
+
validateStructuredOutputRules,
|
|
328
|
+
retryOutcome,
|
|
329
|
+
providerRejectedOutcome,
|
|
330
|
+
quotaExhaustedOutcome,
|
|
331
|
+
blockedOutcome,
|
|
332
|
+
invalidResponseOutcome,
|
|
333
|
+
ProviderReportedError,
|
|
334
|
+
classifyError,
|
|
335
|
+
validateWireFormat
|
|
336
|
+
};
|
|
@@ -1,48 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
"VALIDATION",
|
|
5
|
-
"AUTHENTICATION",
|
|
6
|
-
"AUTHORIZATION",
|
|
7
|
-
"NOT_FOUND",
|
|
8
|
-
"CONFLICT",
|
|
9
|
-
"RATE_LIMIT",
|
|
10
|
-
"QUOTA_EXHAUSTED",
|
|
11
|
-
"AVAILABILITY",
|
|
12
|
-
"PROVIDER_REJECTION",
|
|
13
|
-
"TRANSPORT",
|
|
14
|
-
"RESPONSE_TOO_LARGE",
|
|
15
|
-
"PROTOCOL",
|
|
16
|
-
"LOCAL_DEFECT"
|
|
17
|
-
]);
|
|
18
|
-
var FailureSchema = z.strictObject({ kind: FailureKindSchema, provider: z.string().min(1), operation: z.string().min(1), message: z.string().min(1) });
|
|
19
|
-
var RetryError = class extends Error {
|
|
20
|
-
/** Validated provider fact behind the retry request. */
|
|
21
|
-
failure;
|
|
22
|
-
/** Provider-requested retry delay in milliseconds; zero uses the Step policy. */
|
|
23
|
-
retryAfterMs;
|
|
24
|
-
constructor(failure, retryAfterMs = 0) {
|
|
25
|
-
const validated = FailureSchema.parse(failure);
|
|
26
|
-
super(`connector ${validated.provider} ${validated.operation} retry (${validated.kind}): ${validated.message}`);
|
|
27
|
-
this.name = "RetryError";
|
|
28
|
-
this.failure = validated;
|
|
29
|
-
this.retryAfterMs = retryAfterMs;
|
|
30
|
-
}
|
|
31
|
-
};
|
|
32
|
-
var ReauthorizationRequiredError = class extends Error {
|
|
33
|
-
constructor() {
|
|
34
|
-
super("connector reauthorization is required");
|
|
35
|
-
this.name = "ReauthorizationRequiredError";
|
|
36
|
-
}
|
|
37
|
-
};
|
|
1
|
+
import {
|
|
2
|
+
FailureSchema
|
|
3
|
+
} from "./chunk-IHG6ADQG.js";
|
|
38
4
|
|
|
39
5
|
// src/types.ts
|
|
40
|
-
import { z
|
|
6
|
+
import { z } from "zod";
|
|
41
7
|
import { createHash } from "crypto";
|
|
42
|
-
var CallIDSchema =
|
|
8
|
+
var CallIDSchema = z.uuid();
|
|
43
9
|
var CallIDNamespace = "63281a42-70ad-5bc3-a289-728819af723f";
|
|
44
|
-
var ConnectionRefSchema =
|
|
45
|
-
var OperationRefSchema =
|
|
10
|
+
var ConnectionRefSchema = z.strictObject({ provider: z.string().refine((value) => value.trim().length > 0), name: z.string().refine((value) => value.trim().length > 0) });
|
|
11
|
+
var OperationRefSchema = z.strictObject({ connectorId: z.string().regex(/^[a-z][a-z0-9-]{1,62}$/), operationId: z.string().regex(/^[a-z][A-Za-z0-9]+$/) });
|
|
46
12
|
function deriveCallID(flowId, stepExecutionId, operation) {
|
|
47
13
|
OperationRefSchema.parse(operation);
|
|
48
14
|
const namespace = CallIDNamespace;
|
|
@@ -68,28 +34,28 @@ function uuidSHA1(namespace, name) {
|
|
|
68
34
|
}
|
|
69
35
|
var DefectBranchID = "defect";
|
|
70
36
|
var UncertainBranchID = "uncertain";
|
|
71
|
-
var BranchDefinitionSchema =
|
|
72
|
-
var StepDefaultsWireSchema =
|
|
73
|
-
executeMethodTimeout:
|
|
74
|
-
heartbeatTimeout:
|
|
75
|
-
executeRetry:
|
|
76
|
-
executeDurability:
|
|
37
|
+
var BranchDefinitionSchema = z.strictObject({ id: z.string().min(1), description: z.string(), optional: z.boolean().optional() });
|
|
38
|
+
var StepDefaultsWireSchema = z.strictObject({
|
|
39
|
+
executeMethodTimeout: z.number().int().nonnegative(),
|
|
40
|
+
heartbeatTimeout: z.number().int().nonnegative().optional(),
|
|
41
|
+
executeRetry: z.strictObject({ InitialInterval: z.number().int().nonnegative(), BackoffCoefficient: z.number().positive(), MaximumInterval: z.number().int().nonnegative(), MaximumAttempts: z.number().int().nonnegative(), TotalDuration: z.number().int().nonnegative() }).optional(),
|
|
42
|
+
executeDurability: z.number().int()
|
|
77
43
|
});
|
|
78
|
-
var QueryDefinitionWireSchema =
|
|
44
|
+
var QueryDefinitionWireSchema = z.strictObject({ operation: OperationRefSchema, branches: z.array(BranchDefinitionSchema).nullable(), stepDefaults: StepDefaultsWireSchema });
|
|
79
45
|
var MutationDefinitionWireSchema = QueryDefinitionWireSchema;
|
|
80
|
-
var ReceiptSchema =
|
|
46
|
+
var ReceiptSchema = z.strictObject({
|
|
81
47
|
callId: CallIDSchema,
|
|
82
|
-
idempotencyKey:
|
|
83
|
-
provider:
|
|
84
|
-
providerObjectId:
|
|
85
|
-
providerRequestId:
|
|
86
|
-
observedAt:
|
|
87
|
-
metadata:
|
|
48
|
+
idempotencyKey: z.string().optional(),
|
|
49
|
+
provider: z.string().min(1),
|
|
50
|
+
providerObjectId: z.string().optional(),
|
|
51
|
+
providerRequestId: z.string().optional(),
|
|
52
|
+
observedAt: z.iso.datetime({ offset: true }),
|
|
53
|
+
metadata: z.record(z.string(), z.string()).optional()
|
|
88
54
|
});
|
|
89
55
|
function zeroReceipt() {
|
|
90
56
|
return { callId: "", provider: "", observedAt: "0001-01-01T00:00:00Z" };
|
|
91
57
|
}
|
|
92
|
-
var QueryResultSchema = (valueSchema) =>
|
|
58
|
+
var QueryResultSchema = (valueSchema) => z.strictObject({ branch: z.string().min(1), value: valueSchema, receipt: ReceiptSchema, failure: FailureSchema.optional() });
|
|
93
59
|
var MutationResultSchema = QueryResultSchema;
|
|
94
60
|
function newQueryBranch(branch, value, receipt = {}, failure) {
|
|
95
61
|
return failure === void 0 ? { kind: "branch", branch, value, receipt } : { kind: "branch", branch, value, receipt, failure };
|
|
@@ -119,36 +85,32 @@ function retryPolicyToGoWire(policy) {
|
|
|
119
85
|
TotalDuration: policy.totalDurationMs * 1e6
|
|
120
86
|
};
|
|
121
87
|
}
|
|
122
|
-
var ConnectorUIBindingSchema =
|
|
123
|
-
var ConnectorUIUnitSchema =
|
|
124
|
-
id:
|
|
125
|
-
unitId:
|
|
126
|
-
label:
|
|
127
|
-
description:
|
|
128
|
-
required:
|
|
129
|
-
bindings:
|
|
88
|
+
var ConnectorUIBindingSchema = z.strictObject({ port: z.string().regex(/^[a-z][A-Za-z0-9]*$/), jsonPointer: z.string().regex(/^\/(?:[^~]|~[01])*$/) });
|
|
89
|
+
var ConnectorUIUnitSchema = z.strictObject({
|
|
90
|
+
id: z.string().regex(/^[a-z][A-Za-z0-9]*$/),
|
|
91
|
+
unitId: z.string().regex(/^[a-z][A-Za-z0-9]*$/),
|
|
92
|
+
label: z.string().refine((value) => value.trim().length > 0),
|
|
93
|
+
description: z.string().refine((value) => value.trim().length > 0),
|
|
94
|
+
required: z.boolean(),
|
|
95
|
+
bindings: z.array(ConnectorUIBindingSchema).min(1)
|
|
130
96
|
}).superRefine((unit, context) => {
|
|
131
97
|
if (new Set(unit.bindings.map((binding) => binding.port)).size !== unit.bindings.length) context.addIssue({ code: "custom", message: "duplicate UI port" });
|
|
132
98
|
});
|
|
133
|
-
var ConnectorConfigurationUISchema =
|
|
99
|
+
var ConnectorConfigurationUISchema = z.strictObject({ units: z.array(ConnectorUIUnitSchema) }).superRefine((ui, context) => {
|
|
134
100
|
if (new Set(ui.units.map((unit) => unit.id)).size !== ui.units.length) context.addIssue({ code: "custom", message: "duplicate UI unit ID" });
|
|
135
101
|
});
|
|
136
|
-
var ConnectorConfigurationRefSchema =
|
|
102
|
+
var ConnectorConfigurationRefSchema = z.strictObject({
|
|
137
103
|
connectorId: OperationRefSchema.shape.connectorId,
|
|
138
|
-
connectionName:
|
|
104
|
+
connectionName: z.string().refine((value) => value.trim().length > 0),
|
|
139
105
|
operationId: OperationRefSchema.shape.operationId,
|
|
140
|
-
flowType:
|
|
141
|
-
stepType:
|
|
106
|
+
flowType: z.string().refine((value) => value.trim().length > 0),
|
|
107
|
+
stepType: z.string().refine((value) => value.trim().length > 0)
|
|
142
108
|
});
|
|
143
109
|
function validateRetryDelay(delayMs) {
|
|
144
110
|
if (!Number.isSafeInteger(delayMs) || delayMs < 0) throw new RangeError("retry delay must be a non-negative integer in milliseconds");
|
|
145
111
|
}
|
|
146
112
|
|
|
147
113
|
export {
|
|
148
|
-
FailureKindSchema,
|
|
149
|
-
FailureSchema,
|
|
150
|
-
RetryError,
|
|
151
|
-
ReauthorizationRequiredError,
|
|
152
114
|
CallIDSchema,
|
|
153
115
|
CallIDNamespace,
|
|
154
116
|
ConnectionRefSchema,
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
var FailureKindSchema = z.enum([
|
|
4
|
+
"VALIDATION",
|
|
5
|
+
"AUTHENTICATION",
|
|
6
|
+
"AUTHORIZATION",
|
|
7
|
+
"NOT_FOUND",
|
|
8
|
+
"CONFLICT",
|
|
9
|
+
"RATE_LIMIT",
|
|
10
|
+
"QUOTA_EXHAUSTED",
|
|
11
|
+
"AVAILABILITY",
|
|
12
|
+
"PROVIDER_REJECTION",
|
|
13
|
+
"TRANSPORT",
|
|
14
|
+
"RESPONSE_TOO_LARGE",
|
|
15
|
+
"PROTOCOL",
|
|
16
|
+
"LOCAL_DEFECT"
|
|
17
|
+
]);
|
|
18
|
+
var FailureSchema = z.strictObject({ kind: FailureKindSchema, provider: z.string().min(1), operation: z.string().min(1), message: z.string().min(1) });
|
|
19
|
+
var RetryError = class extends Error {
|
|
20
|
+
/** Validated provider fact behind the retry request. */
|
|
21
|
+
failure;
|
|
22
|
+
/** Provider-requested retry delay in milliseconds; zero uses the Step policy. */
|
|
23
|
+
retryAfterMs;
|
|
24
|
+
constructor(failure, retryAfterMs = 0) {
|
|
25
|
+
const validated = FailureSchema.parse(failure);
|
|
26
|
+
super(`connector ${validated.provider} ${validated.operation} retry (${validated.kind}): ${validated.message}`);
|
|
27
|
+
this.name = "RetryError";
|
|
28
|
+
this.failure = validated;
|
|
29
|
+
this.retryAfterMs = retryAfterMs;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
var ReauthorizationRequiredError = class extends Error {
|
|
33
|
+
constructor() {
|
|
34
|
+
super("connector reauthorization is required");
|
|
35
|
+
this.name = "ReauthorizationRequiredError";
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export {
|
|
40
|
+
FailureKindSchema,
|
|
41
|
+
FailureSchema,
|
|
42
|
+
RetryError,
|
|
43
|
+
ReauthorizationRequiredError
|
|
44
|
+
};
|