@caltra/backend-sdk 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -0
- package/dist/client.js +1 -1
- package/dist/protocol.d.ts +41 -2
- package/dist/protocol.js +33 -9
- package/examples/quickstart.ts +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -58,6 +58,11 @@ The URL must use `wss://`; `ws://` is allowed only for loopback development. Cre
|
|
|
58
58
|
|
|
59
59
|
`ToolContext` carries trusted organization, workspace, environment, principal, invocation ID and deadline. Hosted calls also include principal, session, turn and agent IDs. Tool inputs cannot replace these fields. Enforce your application's business permissions using the trusted context.
|
|
60
60
|
|
|
61
|
+
|
|
62
|
+
Protocol 2 always supplies `workspaceExternalId: string | null` from the authorized workspace row and `tenantUserExternalId: string | null` from the execution principal's tenant-user row. Values are exact stored IDs, including any workspace namespace prefix; Caltra does not strip or guess them. A null means that the row has no external ID, or, for the user field, that the execution is not a tenant user. Agent-identity, system and platform-user playground calls always receive a null user external ID, even when the session has a user owner or initiator. Hosted `principalId` and `principal.id` remain the Caltra tenant-principal UUID, not the tenant-user UUID or external ID. Use the environment and workspace context together with external IDs when selecting application data.
|
|
63
|
+
|
|
64
|
+
Upgrade all Caltra API nodes before starting clients from this SDK revision. The client explicitly registers `protocolVersion: 2` on the existing `/backends/v1/connect` URL; older servers reject registration and there is no silent fallback. Updated servers still accept SDK 0.1.0 / protocol 1 replicas and omit both external-ID fields entirely for them, including calls routed across API nodes. Redis forwarding preserves the legacy call shape and carries the new identity fields separately, so older API socket owners continue serving legacy clients during the server rollout. Protocol 2 requires both fields, so incomplete context from an older API node fails before handler execution during a mixed-server rollout. Protocol versions are independent of manifest versions. Publication of this SDK revision and deployment of Caltra are separate release steps.
|
|
65
|
+
|
|
61
66
|
Caltra routes each call to one available matching-manifest replica and rechecks current access at dispatch. There is no cancellation API or automatic execution retry. A timeout/disconnect can leave the outcome unknown; your handler may continue. Deduplicate business writes using your own operation key before retrying. A newer manifest can temporarily make older replicas unavailable; publish deliberately and start matching replicas.
|
|
62
67
|
|
|
63
68
|
See the [backend tools documentation](https://caltra.dev/docs/backend-tools) for grants, version rollout, context, limits and wire protocol. The `/protocol` and `/peer` exports support protocol integrations; ordinary applications need only the package root.
|
package/dist/client.js
CHANGED
|
@@ -49,7 +49,7 @@ export class CaltraBackendClient {
|
|
|
49
49
|
peer.method("tools/call", params => this.invoke(params));
|
|
50
50
|
try {
|
|
51
51
|
this.registration = RegistrationSchema.parse(await peer.request("instances/register", {
|
|
52
|
-
protocolVersion:
|
|
52
|
+
protocolVersion: 2, manifestHash: BackendProtocol.hash(this.manifest), label: this.options.label, maxConcurrency: this.options.maxConcurrency ?? 1,
|
|
53
53
|
}));
|
|
54
54
|
this.scheduleHeartbeat(peer);
|
|
55
55
|
return this.registration;
|
package/dist/protocol.d.ts
CHANGED
|
@@ -17,7 +17,7 @@ export declare const ManifestVersionSchema: z.ZodObject<{
|
|
|
17
17
|
hash: z.ZodString;
|
|
18
18
|
}, z.core.$strip>;
|
|
19
19
|
export declare const RegisterSchema: z.ZodObject<{
|
|
20
|
-
protocolVersion: z.ZodLiteral<1>;
|
|
20
|
+
protocolVersion: z.ZodUnion<readonly [z.ZodLiteral<1>, z.ZodLiteral<2>]>;
|
|
21
21
|
manifestHash: z.ZodString;
|
|
22
22
|
label: z.ZodString;
|
|
23
23
|
maxConcurrency: z.ZodNumber;
|
|
@@ -31,6 +31,32 @@ export declare const RegistrationSchema: z.ZodObject<{
|
|
|
31
31
|
}, z.core.$strip>;
|
|
32
32
|
heartbeatMs: z.ZodNumber;
|
|
33
33
|
}, z.core.$strip>;
|
|
34
|
+
export declare const LegacyCallContextSchema: z.ZodUnion<readonly [z.ZodObject<{
|
|
35
|
+
organizationId: z.ZodString;
|
|
36
|
+
workspaceId: z.ZodString;
|
|
37
|
+
environmentId: z.ZodString;
|
|
38
|
+
principal: z.ZodObject<{
|
|
39
|
+
kind: z.ZodLiteral<"platform_user">;
|
|
40
|
+
id: z.ZodString;
|
|
41
|
+
}, z.core.$strict>;
|
|
42
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
43
|
+
organizationId: z.ZodString;
|
|
44
|
+
workspaceId: z.ZodString;
|
|
45
|
+
environmentId: z.ZodString;
|
|
46
|
+
principalId: z.ZodString;
|
|
47
|
+
principal: z.ZodObject<{
|
|
48
|
+
kind: z.ZodEnum<{
|
|
49
|
+
tenant_user: "tenant_user";
|
|
50
|
+
agent_identity: "agent_identity";
|
|
51
|
+
system: "system";
|
|
52
|
+
}>;
|
|
53
|
+
id: z.ZodString;
|
|
54
|
+
}, z.core.$strict>;
|
|
55
|
+
sessionId: z.ZodString;
|
|
56
|
+
turnId: z.ZodString;
|
|
57
|
+
agentIdentityId: z.ZodNullable<z.ZodString>;
|
|
58
|
+
agentId: z.ZodString;
|
|
59
|
+
}, z.core.$strict>]>;
|
|
34
60
|
export declare const CallContextSchema: z.ZodUnion<readonly [z.ZodObject<{
|
|
35
61
|
organizationId: z.ZodString;
|
|
36
62
|
workspaceId: z.ZodString;
|
|
@@ -39,6 +65,8 @@ export declare const CallContextSchema: z.ZodUnion<readonly [z.ZodObject<{
|
|
|
39
65
|
kind: z.ZodLiteral<"platform_user">;
|
|
40
66
|
id: z.ZodString;
|
|
41
67
|
}, z.core.$strict>;
|
|
68
|
+
tenantUserExternalId: z.ZodNull;
|
|
69
|
+
workspaceExternalId: z.ZodNullable<z.ZodString>;
|
|
42
70
|
}, z.core.$strict>, z.ZodObject<{
|
|
43
71
|
organizationId: z.ZodString;
|
|
44
72
|
workspaceId: z.ZodString;
|
|
@@ -56,6 +84,8 @@ export declare const CallContextSchema: z.ZodUnion<readonly [z.ZodObject<{
|
|
|
56
84
|
turnId: z.ZodString;
|
|
57
85
|
agentIdentityId: z.ZodNullable<z.ZodString>;
|
|
58
86
|
agentId: z.ZodString;
|
|
87
|
+
workspaceExternalId: z.ZodNullable<z.ZodString>;
|
|
88
|
+
tenantUserExternalId: z.ZodNullable<z.ZodString>;
|
|
59
89
|
}, z.core.$strict>]>;
|
|
60
90
|
export declare const CallSchema: z.ZodObject<{
|
|
61
91
|
invocationId: z.ZodString;
|
|
@@ -70,6 +100,8 @@ export declare const CallSchema: z.ZodObject<{
|
|
|
70
100
|
kind: z.ZodLiteral<"platform_user">;
|
|
71
101
|
id: z.ZodString;
|
|
72
102
|
}, z.core.$strict>;
|
|
103
|
+
tenantUserExternalId: z.ZodNull;
|
|
104
|
+
workspaceExternalId: z.ZodNullable<z.ZodString>;
|
|
73
105
|
}, z.core.$strict>, z.ZodObject<{
|
|
74
106
|
organizationId: z.ZodString;
|
|
75
107
|
workspaceId: z.ZodString;
|
|
@@ -87,6 +119,8 @@ export declare const CallSchema: z.ZodObject<{
|
|
|
87
119
|
turnId: z.ZodString;
|
|
88
120
|
agentIdentityId: z.ZodNullable<z.ZodString>;
|
|
89
121
|
agentId: z.ZodString;
|
|
122
|
+
workspaceExternalId: z.ZodNullable<z.ZodString>;
|
|
123
|
+
tenantUserExternalId: z.ZodNullable<z.ZodString>;
|
|
90
124
|
}, z.core.$strict>]>;
|
|
91
125
|
deadline: z.ZodString;
|
|
92
126
|
}, z.core.$strict>;
|
|
@@ -94,12 +128,17 @@ export type ToolManifest = z.infer<typeof ToolManifestSchema>;
|
|
|
94
128
|
export type ManifestVersion = z.infer<typeof ManifestVersionSchema>;
|
|
95
129
|
export type Registration = z.infer<typeof RegistrationSchema>;
|
|
96
130
|
export type ToolCall = z.infer<typeof CallSchema>;
|
|
131
|
+
export type LegacyToolCall = Omit<ToolCall, "context"> & {
|
|
132
|
+
context: z.infer<typeof LegacyCallContextSchema>;
|
|
133
|
+
};
|
|
97
134
|
export type ToolContext = z.infer<typeof CallContextSchema> & {
|
|
98
135
|
invocationId: string;
|
|
99
136
|
deadline: Date;
|
|
100
137
|
};
|
|
101
|
-
/** Defines
|
|
138
|
+
/** Defines negotiated wire contracts and a stable digest independent of JSON object key order. */
|
|
102
139
|
export declare class BackendProtocol {
|
|
140
|
+
/** Projects at the socket owner so legacy replicas never see protocol-two fields. */
|
|
141
|
+
static forVersion(call: ToolCall | LegacyToolCall, version: 1 | 2): ToolCall | LegacyToolCall;
|
|
103
142
|
static readonly path = "/backends/v1/connect";
|
|
104
143
|
static readonly maxBytes: number;
|
|
105
144
|
static readonly heartbeatMs = 10000;
|
package/dist/protocol.js
CHANGED
|
@@ -11,24 +11,48 @@ export const ManifestSchema = z.array(ToolManifestSchema).max(100).superRefine((
|
|
|
11
11
|
context.addIssue({ code: "custom", message: "Duplicate tool names." });
|
|
12
12
|
});
|
|
13
13
|
export const ManifestVersionSchema = z.object({ id: z.string().uuid(), version: z.number().int().positive(), hash: z.string().length(64) });
|
|
14
|
-
export const RegisterSchema = z.object({ protocolVersion: z.literal(1), manifestHash: z.string().length(64), label: z.string().min(1).max(100).regex(/\S/u), maxConcurrency: z.number().int().min(1).max(100) }).strict();
|
|
14
|
+
export const RegisterSchema = z.object({ protocolVersion: z.union([z.literal(1), z.literal(2)]), manifestHash: z.string().length(64), label: z.string().min(1).max(100).regex(/\S/u), maxConcurrency: z.number().int().min(1).max(100) }).strict();
|
|
15
15
|
export const RegistrationSchema = z.object({ instanceId: z.string().uuid(), manifest: ManifestVersionSchema, heartbeatMs: z.number().int().positive() });
|
|
16
16
|
const ContextScope = z.object({
|
|
17
17
|
organizationId: z.string().uuid(), workspaceId: z.string().uuid(), environmentId: z.string().uuid(),
|
|
18
18
|
});
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
19
|
+
const PlatformContextSchema = z.object({
|
|
20
|
+
organizationId: z.string().uuid(), workspaceId: z.string().uuid(), environmentId: z.string().uuid(),
|
|
21
|
+
principal: z.object({ kind: z.literal("platform_user"), id: z.string().uuid() }).strict(),
|
|
22
|
+
}).strict();
|
|
23
|
+
const HostedContextSchema = ContextScope.extend({
|
|
24
|
+
principalId: z.string().uuid(), principal: z.object({ kind: z.enum(["tenant_user", "agent_identity", "system"]), id: z.string().uuid() }).strict(),
|
|
25
|
+
sessionId: z.string().uuid(), turnId: z.string().uuid(), agentIdentityId: z.string().uuid().nullable(), agentId: z.string().uuid(),
|
|
26
|
+
}).strict();
|
|
27
|
+
// Version one remains frozen: deployed 0.1.0 clients reject unknown context keys.
|
|
28
|
+
export const LegacyCallContextSchema = z.union([PlatformContextSchema, HostedContextSchema]);
|
|
29
|
+
const ExternalContext = {
|
|
30
|
+
workspaceExternalId: z.string().nullable(),
|
|
31
|
+
tenantUserExternalId: z.string().nullable(),
|
|
32
|
+
};
|
|
33
|
+
export const CallContextSchema = z.union([
|
|
34
|
+
PlatformContextSchema.extend({ ...ExternalContext, tenantUserExternalId: z.null() }),
|
|
35
|
+
HostedContextSchema.extend(ExternalContext).superRefine((context, issue) => {
|
|
36
|
+
if (context.principal.kind !== "tenant_user" && context.tenantUserExternalId !== null) {
|
|
37
|
+
issue.addIssue({ code: "custom", message: "Only tenant-user execution may carry a user external ID." });
|
|
38
|
+
}
|
|
39
|
+
}),
|
|
40
|
+
]);
|
|
26
41
|
export const CallSchema = z.object({
|
|
27
42
|
invocationId: z.string().uuid(), manifestVersion: z.number().int().positive(), name: z.string(),
|
|
28
43
|
inputs: z.unknown(), context: CallContextSchema, deadline: z.string().datetime(),
|
|
29
44
|
}).strict();
|
|
30
|
-
/** Defines
|
|
45
|
+
/** Defines negotiated wire contracts and a stable digest independent of JSON object key order. */
|
|
31
46
|
export class BackendProtocol {
|
|
47
|
+
/** Projects at the socket owner so legacy replicas never see protocol-two fields. */
|
|
48
|
+
static forVersion(call, version) {
|
|
49
|
+
if (version === 2)
|
|
50
|
+
return CallSchema.parse(call);
|
|
51
|
+
const context = { ...call.context };
|
|
52
|
+
delete context.workspaceExternalId;
|
|
53
|
+
delete context.tenantUserExternalId;
|
|
54
|
+
return { ...call, context: LegacyCallContextSchema.parse(context) };
|
|
55
|
+
}
|
|
32
56
|
static path = "/backends/v1/connect";
|
|
33
57
|
static maxBytes = 1024 * 1024;
|
|
34
58
|
static heartbeatMs = 10000;
|
package/examples/quickstart.ts
CHANGED
|
@@ -15,8 +15,8 @@ class BackendQuickstart {
|
|
|
15
15
|
name: "diagnostics.echo",
|
|
16
16
|
description: "Echo a test message with the trusted workspace and serving replica.",
|
|
17
17
|
input: z.object({ message: z.string().min(1) }).strict(),
|
|
18
|
-
output: z.object({ message: z.string(), workspaceId: z.uuid(), handledBy: z.string() }).strict(),
|
|
19
|
-
handler: async (context, inputs) => ({ message: inputs.message, workspaceId: context.workspaceId, handledBy: config.label }),
|
|
18
|
+
output: z.object({ message: z.string(), workspaceId: z.uuid(), workspaceExternalId: z.string().nullable(), tenantUserExternalId: z.string().nullable(), handledBy: z.string() }).strict(),
|
|
19
|
+
handler: async (context, inputs) => ({ message: inputs.message, workspaceId: context.workspaceId, workspaceExternalId: context.workspaceExternalId, tenantUserExternalId: context.tenantUserExternalId, handledBy: config.label }),
|
|
20
20
|
})],
|
|
21
21
|
});
|
|
22
22
|
if (command === "publish") {
|