@keemakr/operator-sdk 0.4.0 → 0.6.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/package.json +1 -1
- package/src/index.ts +169 -2
- package/src/manifest.ts +110 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@keemakr/operator-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "The keemakr connector contract: defineProvider + ConnectionProvider/ConnectionOperation types + OperationContext (governed fetch) + the connector manifest schema, shared by keemakr-core's built-in adapters and external connector repos. One shape, two consumers.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
package/src/index.ts
CHANGED
|
@@ -52,6 +52,60 @@ export interface TestResult {
|
|
|
52
52
|
detail?: string;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
// ─── Who a call acts for ─────────────────────────────────────────────────────
|
|
56
|
+
//
|
|
57
|
+
// The VERIFIED answer to "which person is behind this call", handed to an operation on
|
|
58
|
+
// `ctx.actor`. Before this existed a connector could see only `connector:<keyId>` — the
|
|
59
|
+
// install, not the human — so a connector that had to record who resolved an identity or
|
|
60
|
+
// recorded an opt-out took the actor as a TOOL ARGUMENT, a value the model fills. Kee CRM
|
|
61
|
+
// stored exactly that and had to mark it unverified (COS-207).
|
|
62
|
+
//
|
|
63
|
+
// Core fills this from something it authenticated — claims it SIGNED at mint time
|
|
64
|
+
// (`on_behalf_of`, `actor_role`), a verified member session, or the recorded approver of a held
|
|
65
|
+
// write — never from the operation's args. It is a PRINCIPAL, not an authorization: it says
|
|
66
|
+
// who, never what they may do — the grant's scopes and the operation's access class decide
|
|
67
|
+
// that, before your code runs.
|
|
68
|
+
export interface OperationActor {
|
|
69
|
+
/** The keemakr org this call is scoped to. */
|
|
70
|
+
tenantId: string;
|
|
71
|
+
/**
|
|
72
|
+
* The human this call acts for, or null when nobody does.
|
|
73
|
+
*
|
|
74
|
+
* NULL IS MEANINGFUL, not missing: an unattended machine grant has no honest answer, and a
|
|
75
|
+
* connector that must attribute an action should refuse rather than invent one.
|
|
76
|
+
*
|
|
77
|
+
* NON-NULL DOES NOT MEAN PRESENT. A scheduled run carries the schedule's OWNER here (`via:
|
|
78
|
+
* 'schedule'`) — a real, named, accountable person who is not at the keyboard. Check `attended`
|
|
79
|
+
* for "is a person behind this call right now", never this field's truthiness.
|
|
80
|
+
*
|
|
81
|
+
* On `via: 'approval'` this is the person who RELEASED the write, never merely the one who
|
|
82
|
+
* asked for it. Where core cannot establish who approved, it is null.
|
|
83
|
+
*/
|
|
84
|
+
userId: string | null;
|
|
85
|
+
/**
|
|
86
|
+
* That person's org role, read when the call was authenticated, or null when none was proven — a machine grant, a
|
|
87
|
+
* principal who is no longer a member, or a grant minted before the claim existed. Fail
|
|
88
|
+
* closed on null; never default it to a role.
|
|
89
|
+
*/
|
|
90
|
+
role: 'owner' | 'admin' | 'member' | 'viewer' | null;
|
|
91
|
+
/**
|
|
92
|
+
* What the platform authenticated. `grant` is a marketplace install's capability grant,
|
|
93
|
+
* `schedule` a capability grant minted for a scheduled run (acting as the schedule's owner),
|
|
94
|
+
* `operator` a tenant member driving the Operator in the app, `mcp` a tenant member calling
|
|
95
|
+
* through the platform's MCP server (an AI client acting for them), `approval` a write a human
|
|
96
|
+
* released from an approval card.
|
|
97
|
+
*
|
|
98
|
+
* Treat this union as OPEN: switch on the values you accept and refuse the rest, so a lane
|
|
99
|
+
* added later fails closed in your connector instead of matching a default branch.
|
|
100
|
+
*/
|
|
101
|
+
via: 'grant' | 'schedule' | 'operator' | 'mcp' | 'approval';
|
|
102
|
+
/**
|
|
103
|
+
* True exactly when a person is behind this call AS IT RUNS: `userId` is non-null and the lane
|
|
104
|
+
* is not `schedule`. A scheduled run names its owner but is still unattended.
|
|
105
|
+
*/
|
|
106
|
+
attended: boolean;
|
|
107
|
+
}
|
|
108
|
+
|
|
55
109
|
// ─── Execution context ───────────────────────────────────────────────────────
|
|
56
110
|
//
|
|
57
111
|
// Handed to an operation (and test()) by the platform at call time. Marketplace
|
|
@@ -71,6 +125,15 @@ export interface OperationContext {
|
|
|
71
125
|
config?: Record<string, string>;
|
|
72
126
|
// Trace-correlated logger. Never log credentials or response payloads.
|
|
73
127
|
log?: (msg: string) => void;
|
|
128
|
+
// WHO this call acts for, as the platform verified it — see OperationActor.
|
|
129
|
+
//
|
|
130
|
+
// Optional because not every lane has one: a connect-time probe and a built-in adapter run
|
|
131
|
+
// without a ctx at all, and a `remote` or `mcp` runtime connector is never handed the ctx core
|
|
132
|
+
// builds (it gets the same actor on its signed call grant instead, read with
|
|
133
|
+
// `verifyConnectorRequest` / `verifyMcpToolCall` from `@keemakr/agent-sdk`). An operation that
|
|
134
|
+
// needs attribution must treat an absent actor the same as an unattended one and refuse, rather
|
|
135
|
+
// than falling back to an args value. Frozen: read it, never write it.
|
|
136
|
+
actor?: Readonly<OperationActor>;
|
|
74
137
|
}
|
|
75
138
|
|
|
76
139
|
// Slim context for the OAuth lifecycle hooks (buildAuthUrl/exchangeCode/refresh):
|
|
@@ -100,6 +163,24 @@ export interface ConnectField {
|
|
|
100
163
|
help?: string;
|
|
101
164
|
}
|
|
102
165
|
|
|
166
|
+
// Outcome of a connect-time (or on-demand) health probe. `status` is the user-
|
|
167
|
+
// facing vocabulary the Hub renders: 'connected' (probe passed), 'needs_rescope'
|
|
168
|
+
// (OAuth completed but the granted scopes can't run this provider's operations —
|
|
169
|
+
// e.g. Google's granular-consent checkboxes were left unticked), 'no_mailbox'
|
|
170
|
+
// (the account exists but has no mailbox/service behind it — a Workspace account
|
|
171
|
+
// on a Microsoft-mail domain), 'unusable' (probe failed for another reason).
|
|
172
|
+
export interface ProbeResult {
|
|
173
|
+
status: 'connected' | 'needs_rescope' | 'no_mailbox' | 'unusable';
|
|
174
|
+
/** One human-readable sentence for the Hub's status chip. */
|
|
175
|
+
detail?: string;
|
|
176
|
+
/** Scopes the credential actually carries, when the probe can read them. */
|
|
177
|
+
grantedScopes?: string[];
|
|
178
|
+
/** Probed account identity (e.g. the mailbox email) — becomes account_label. */
|
|
179
|
+
accountLabel?: string | null;
|
|
180
|
+
/** Extra provider facts worth persisting under meta.probe. */
|
|
181
|
+
meta?: Record<string, unknown>;
|
|
182
|
+
}
|
|
183
|
+
|
|
103
184
|
export interface ConnectionProvider {
|
|
104
185
|
provider: string;
|
|
105
186
|
displayName: string;
|
|
@@ -112,6 +193,29 @@ export interface ConnectionProvider {
|
|
|
112
193
|
// Connect-path maturity. Omitted ⇒ 'ready'. See Maturity above.
|
|
113
194
|
maturity?: Maturity;
|
|
114
195
|
|
|
196
|
+
// NOTE: a connection's scope (personal 'user' vs org-shared 'tenant') is NOT
|
|
197
|
+
// declared on the adapter. The live value is connections_registry.connection_mode
|
|
198
|
+
// (the platform-controlled overlay + rollback lever). Marketplace connectors
|
|
199
|
+
// seed it by declaring `connection_mode` in the MANIFEST (first publish only —
|
|
200
|
+
// the registry column is authoritative afterward, and reclassifying a live
|
|
201
|
+
// provider is a guarded staff console action). Built-ins still seed from
|
|
202
|
+
// registry-state's DEFAULT_USER_MODE_PROVIDERS, which stays the fail-closed
|
|
203
|
+
// fallback for unreadable registry reads.
|
|
204
|
+
|
|
205
|
+
// OAuth scopes this provider's operations actually NEED — unlike `scopes`
|
|
206
|
+
// (display-only), these are compared against the granted scope string by the
|
|
207
|
+
// connect-time probe; missing entries mark the connection 'needs_rescope'.
|
|
208
|
+
requiredScopes?: string[];
|
|
209
|
+
|
|
210
|
+
// Connect-time health probe: one cheap read (a profile fetch) that proves the
|
|
211
|
+
// credential can actually operate, run after the OAuth reports success and on
|
|
212
|
+
// demand from the Hub's "Check connection". Omitted ⇒ the connection is
|
|
213
|
+
// trusted as-is (status stays whatever the OAuth layer reported).
|
|
214
|
+
probe?(
|
|
215
|
+
cred: { accessToken: string; meta: Record<string, unknown> },
|
|
216
|
+
ctx?: OperationContext,
|
|
217
|
+
): Promise<ProbeResult>;
|
|
218
|
+
|
|
115
219
|
// Audience classification — see ConnectorSurface. Drives whether the end-user
|
|
116
220
|
// client surfaces this connector and how. Optional in the type for now so
|
|
117
221
|
// existing/external adapters compile through the SDK bump; defineProvider warns
|
|
@@ -204,6 +308,9 @@ export interface ConnectionProvider {
|
|
|
204
308
|
// sees it. Keep `call` pure-ish: do the request, return JSON-serializable data.
|
|
205
309
|
export interface ConnectionOperation {
|
|
206
310
|
description?: string;
|
|
311
|
+
// Platform-only provider primitive. It remains available to trusted internal callers that
|
|
312
|
+
// invoke the adapter directly, but is omitted from catalogs and agent/user operation surfaces.
|
|
313
|
+
internal?: boolean;
|
|
207
314
|
// Access class, for grant scoping + install consent. Omitted ⇒ 'write' (the
|
|
208
315
|
// most restrictive assumption: unlabeled ops require the write scope and are
|
|
209
316
|
// listed as writes on the consent screen).
|
|
@@ -224,6 +331,45 @@ export interface ConnectionOperation {
|
|
|
224
331
|
): Promise<unknown>;
|
|
225
332
|
}
|
|
226
333
|
|
|
334
|
+
/** An operation's input schema: anything with `safeParse` (zod, valibot, a hand-rolled check). */
|
|
335
|
+
export type OperationInputSchema = ConnectionOperation['inputSchema'];
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* The `args` a `call` receives for an input schema: what the schema parses to, when it says (a
|
|
339
|
+
* Standard Schema — zod among them — declares its output type), intersected with an open record;
|
|
340
|
+
* an open record when it does not. The intersection is deliberate: known keys are typed, and any
|
|
341
|
+
* other key still reads as `unknown`, so code written against `Record<string, unknown>` keeps
|
|
342
|
+
* compiling.
|
|
343
|
+
*/
|
|
344
|
+
export type OperationArgs<S> = S extends { readonly '~standard': { readonly types?: infer T } }
|
|
345
|
+
? NonNullable<T> extends { readonly output: infer O }
|
|
346
|
+
? O extends Record<string, unknown>
|
|
347
|
+
? O & Record<string, unknown>
|
|
348
|
+
: Record<string, unknown>
|
|
349
|
+
: Record<string, unknown>
|
|
350
|
+
: Record<string, unknown>;
|
|
351
|
+
|
|
352
|
+
/** A ConnectionOperation whose `call` is typed by its own input schema (see defineProvider). */
|
|
353
|
+
export interface TypedConnectionOperation<S extends OperationInputSchema> extends Omit<
|
|
354
|
+
ConnectionOperation,
|
|
355
|
+
'inputSchema' | 'call'
|
|
356
|
+
> {
|
|
357
|
+
inputSchema: S;
|
|
358
|
+
call(
|
|
359
|
+
cred: { accessToken: string; meta: Record<string, unknown> },
|
|
360
|
+
args: OperationArgs<S>,
|
|
361
|
+
ctx?: OperationContext,
|
|
362
|
+
): Promise<unknown>;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/** What `defineProvider` takes: a ConnectionProvider whose operations are typed by their schemas. */
|
|
366
|
+
export type ProviderDefinition<Ops extends Record<string, OperationInputSchema>> = Omit<
|
|
367
|
+
ConnectionProvider,
|
|
368
|
+
'operations'
|
|
369
|
+
> & {
|
|
370
|
+
operations?: { [K in keyof Ops]: TypedConnectionOperation<Ops[K]> };
|
|
371
|
+
};
|
|
372
|
+
|
|
227
373
|
// Machine-readable failure classes. The capability proxy maps these to HTTP
|
|
228
374
|
// codes + health-machine outcomes; a provider outage must never read as a
|
|
229
375
|
// broken connector, and an auth failure must revoke, not retry.
|
|
@@ -237,7 +383,16 @@ export type OperationErrorCode =
|
|
|
237
383
|
| 'connector_error'
|
|
238
384
|
| 'auth_revoked'
|
|
239
385
|
| 'egress_blocked'
|
|
240
|
-
| 'rate_limited'
|
|
386
|
+
| 'rate_limited'
|
|
387
|
+
// The CONNECTOR refused the call on policy — typically an action that must be attributable
|
|
388
|
+
// reached it unattended. A denial, not a failure: it does not count against the connector's
|
|
389
|
+
// health or the provider's circuit breaker, so refusing correctly cannot degrade you.
|
|
390
|
+
| 'forbidden'
|
|
391
|
+
// The provider ANSWERED and rejected what the caller sent — bad args, an unknown record, a
|
|
392
|
+
// failed validation. The caller's fault, not the provider's: like `forbidden` it does not count
|
|
393
|
+
// against health or the circuit breaker, so one client's bad input cannot shed the connector
|
|
394
|
+
// for every tenant.
|
|
395
|
+
| 'invalid_request';
|
|
241
396
|
|
|
242
397
|
// Throw from a ConnectionOperation.call to surface a typed, agent-safe failure
|
|
243
398
|
// (maps to a 4xx with a clean message — never leaks the credential).
|
|
@@ -280,8 +435,20 @@ export class EgressViolationError extends OperationError {
|
|
|
280
435
|
// - a provider key that isn't lowercase-kebab (must match the URL segment and
|
|
281
436
|
// the connections_registry row)
|
|
282
437
|
//
|
|
438
|
+
// It also TYPES each operation's `call`: `args` is what the operation's own
|
|
439
|
+
// inputSchema parses to (z.infer of a zod schema, or any Standard Schema's
|
|
440
|
+
// output), so `args.limit` is a number when the schema says so — no casts. Known
|
|
441
|
+
// keys are typed; any other key still reads as `unknown`, exactly as it did when
|
|
442
|
+
// `args` was a bare Record<string, unknown>, so existing connectors that annotate
|
|
443
|
+
// `args` or parse it themselves compile unchanged.
|
|
444
|
+
//
|
|
283
445
|
// Usage: see keemakr-core's lib/connections/providers/_template.ts.
|
|
284
|
-
export function defineProvider
|
|
446
|
+
export function defineProvider<
|
|
447
|
+
Ops extends Record<string, OperationInputSchema> = Record<string, OperationInputSchema>,
|
|
448
|
+
>(definition: ProviderDefinition<Ops>): ConnectionProvider {
|
|
449
|
+
// The typed operations are a narrowing of ConnectionOperation's, never a widening: each `call`
|
|
450
|
+
// accepts at least what its schema parses to, which is all the platform ever passes it.
|
|
451
|
+
const p = definition as unknown as ConnectionProvider;
|
|
285
452
|
if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(p.provider)) {
|
|
286
453
|
throw new Error(
|
|
287
454
|
`connections: provider key '${p.provider}' must be lowercase-kebab (e.g. 'mailchimp', 'google-ads')`,
|
package/src/manifest.ts
CHANGED
|
@@ -26,11 +26,107 @@ const domainPattern =
|
|
|
26
26
|
// ('contacts.search', 'weather.current'), hyphens allowed within a segment.
|
|
27
27
|
const opName = /^[a-z0-9]+(?:[.-][a-z0-9]+)*$/;
|
|
28
28
|
|
|
29
|
+
// Top-level JSON Schema keywords an operation's declared input may NOT use. Each one lets the
|
|
30
|
+
// input mean something its top-level `properties` do not say — a union or intersection of shapes,
|
|
31
|
+
// a negation or a condition, a pattern of names, a reference resolved elsewhere — and the declared
|
|
32
|
+
// input exists precisely so that the list of argument names a caller may send can be READ off it:
|
|
33
|
+
// by a reviewer, by the forwarder that refuses anything else, and by channel reach, which consents
|
|
34
|
+
// to an operation only when it can see that no argument lets the caller pick the resource.
|
|
35
|
+
const REFUSED_INPUT_KEYWORDS = [
|
|
36
|
+
'oneOf',
|
|
37
|
+
'anyOf',
|
|
38
|
+
'allOf',
|
|
39
|
+
'not',
|
|
40
|
+
'if',
|
|
41
|
+
'then',
|
|
42
|
+
'else',
|
|
43
|
+
'patternProperties',
|
|
44
|
+
'dependentSchemas',
|
|
45
|
+
'$ref',
|
|
46
|
+
'$dynamicRef',
|
|
47
|
+
] as const;
|
|
48
|
+
|
|
49
|
+
/** The most arguments one operation may declare, and the longest a declared input may be. */
|
|
50
|
+
const MAX_INPUT_PROPERTIES = 100;
|
|
51
|
+
const MAX_INPUT_SCHEMA_BYTES = 16_384;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* An operation's declared INPUT, as a JSON Schema object: the arguments a caller may send and
|
|
55
|
+
* nothing else. Optional, and read by core only for the lanes that have no adapter code to carry a
|
|
56
|
+
* schema of their own — remote (Lane B) and MCP connectors. There, core builds the operation's
|
|
57
|
+
* input validator from it (unknown arguments refused, required ones and primitive types checked),
|
|
58
|
+
* and channel reach can name the operation only when it is declared.
|
|
59
|
+
*
|
|
60
|
+
* Deliberately narrow: `type: 'object'`, `properties` listed, `additionalProperties: false`, and
|
|
61
|
+
* none of the keywords above at the top level. Property schemas are ordinary JSON Schema; core
|
|
62
|
+
* checks their `type` and `enum` and leaves the rest to the connector.
|
|
63
|
+
*/
|
|
64
|
+
export const operationInputJsonSchema = z
|
|
65
|
+
.looseObject({
|
|
66
|
+
type: z.literal('object'),
|
|
67
|
+
properties: z.record(
|
|
68
|
+
z.string().min(1).max(100),
|
|
69
|
+
z.record(z.string(), z.unknown(), 'each property must be a JSON Schema object'),
|
|
70
|
+
),
|
|
71
|
+
required: z.array(z.string()).max(MAX_INPUT_PROPERTIES).optional(),
|
|
72
|
+
additionalProperties: z.literal(
|
|
73
|
+
false,
|
|
74
|
+
'input_schema must set additionalProperties: false — an operation that accepts arguments it does not list cannot be reviewed by its arguments',
|
|
75
|
+
),
|
|
76
|
+
description: z.string().max(500).optional(),
|
|
77
|
+
})
|
|
78
|
+
.superRefine((schema, ctx) => {
|
|
79
|
+
for (const keyword of REFUSED_INPUT_KEYWORDS) {
|
|
80
|
+
if (keyword in schema) {
|
|
81
|
+
ctx.addIssue({
|
|
82
|
+
code: 'custom',
|
|
83
|
+
path: [keyword],
|
|
84
|
+
message: `input_schema may not use '${keyword}' at the top level — declare the arguments as plain properties`,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
const names = Object.keys(schema.properties);
|
|
89
|
+
if (names.length > MAX_INPUT_PROPERTIES) {
|
|
90
|
+
ctx.addIssue({
|
|
91
|
+
code: 'custom',
|
|
92
|
+
path: ['properties'],
|
|
93
|
+
message: `input_schema may declare at most ${MAX_INPUT_PROPERTIES} properties`,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
const seen = new Set<string>();
|
|
97
|
+
for (const [i, name] of (schema.required ?? []).entries()) {
|
|
98
|
+
if (!names.includes(name)) {
|
|
99
|
+
ctx.addIssue({
|
|
100
|
+
code: 'custom',
|
|
101
|
+
path: ['required', i],
|
|
102
|
+
message: `required names '${name}', which is not one of its properties`,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
if (seen.has(name)) {
|
|
106
|
+
ctx.addIssue({
|
|
107
|
+
code: 'custom',
|
|
108
|
+
path: ['required', i],
|
|
109
|
+
message: `required names '${name}' twice`,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
seen.add(name);
|
|
113
|
+
}
|
|
114
|
+
if (JSON.stringify(schema).length > MAX_INPUT_SCHEMA_BYTES) {
|
|
115
|
+
ctx.addIssue({
|
|
116
|
+
code: 'custom',
|
|
117
|
+
message: `input_schema must be at most ${MAX_INPUT_SCHEMA_BYTES} characters of JSON`,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
|
|
29
122
|
export const connectorOperationDeclaration = z.object({
|
|
30
123
|
name: z.string().regex(opName, 'operation names are lowercase, dot/hyphen-scoped'),
|
|
31
124
|
readWrite: z.enum(['read', 'write']),
|
|
32
125
|
idempotent: z.boolean(),
|
|
33
126
|
description: z.string().max(500).optional(),
|
|
127
|
+
// OPTIONAL, and it must stay optional: stored manifests of already-published versions are
|
|
128
|
+
// re-parsed with the current schema at every publish, and they do not carry it.
|
|
129
|
+
input_schema: operationInputJsonSchema.optional(),
|
|
34
130
|
});
|
|
35
131
|
|
|
36
132
|
export const connectorManifestSchema = z
|
|
@@ -46,6 +142,19 @@ export const connectorManifestSchema = z
|
|
|
46
142
|
// publish gate. Optional (the adapter is authoritative); declaring it lets
|
|
47
143
|
// the upload preview show it before any code runs.
|
|
48
144
|
auth_kind: z.enum(['oauth2', 'api_key', 'app_password']).optional(),
|
|
145
|
+
// OWNERSHIP axis (orthogonal to `surface`): 'user' = each member connects
|
|
146
|
+
// their OWN account (one row per member, invisible to everyone else — a
|
|
147
|
+
// personal mailbox/drive/calendar); 'tenant' = one org-shared connection.
|
|
148
|
+
// Declare 'user' whenever the credential is one person's own account.
|
|
149
|
+
//
|
|
150
|
+
// Contract: this value SEEDS connections_registry.connection_mode at FIRST
|
|
151
|
+
// publish only — the platform column is authoritative afterward. Changing
|
|
152
|
+
// it in a later version raises a publish-diff warning and does NOT apply;
|
|
153
|
+
// live reclassification is a guarded staff console action (adopt/clean-cut).
|
|
154
|
+
// NEVER make this required, and never make the schema strict: stored
|
|
155
|
+
// manifests of already-published versions are re-parsed with the current
|
|
156
|
+
// schema at every publish, and only a defaulted optional field survives that.
|
|
157
|
+
connection_mode: z.enum(['tenant', 'user']).default('tenant'),
|
|
49
158
|
// The operator-sdk contract version the connector targets (semver).
|
|
50
159
|
sdk_version: z.string().regex(/^\d+\.\d+\.\d+$/, 'sdk_version must be exact semver'),
|
|
51
160
|
// Module (relative path inside the bundle) whose named export is the
|
|
@@ -170,6 +279,7 @@ export const connectorManifestSchema = z
|
|
|
170
279
|
});
|
|
171
280
|
|
|
172
281
|
export type ConnectorOperationDeclaration = z.infer<typeof connectorOperationDeclaration>;
|
|
282
|
+
export type OperationInputJsonSchema = z.infer<typeof operationInputJsonSchema>;
|
|
173
283
|
export type ConnectorManifest = z.infer<typeof connectorManifestSchema>;
|
|
174
284
|
|
|
175
285
|
// Parse + normalize an entry.json object. Throws ZodError on invalid input;
|