@neutrome/platform-contracts 0.1.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 +20 -0
- package/src/auth.ts +24 -0
- package/src/billing-trace.test.ts +51 -0
- package/src/billing-trace.ts +42 -0
- package/src/catalog.ts +71 -0
- package/src/index.ts +164 -0
- package/src/management.ts +98 -0
- package/src/namespace.ts +17 -0
- package/src/records.ts +297 -0
- package/src/resources.ts +163 -0
- package/src/runtime-jwt.test.ts +106 -0
- package/src/runtime-jwt.ts +201 -0
- package/src/workspace.ts +17 -0
- package/tsconfig.json +17 -0
package/package.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@neutrome/platform-contracts",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"exports": {
|
|
6
|
+
".": "./src/index.ts"
|
|
7
|
+
},
|
|
8
|
+
"dependencies": {
|
|
9
|
+
"jose": "^6.2.0",
|
|
10
|
+
"zod": "^4.4.3"
|
|
11
|
+
},
|
|
12
|
+
"devDependencies": {
|
|
13
|
+
"typescript": "^6.0.3",
|
|
14
|
+
"vitest": "^4.1.6"
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"test": "vitest run",
|
|
18
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
19
|
+
}
|
|
20
|
+
}
|
package/src/auth.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
const nonEmptyString = z.string().min(1);
|
|
4
|
+
|
|
5
|
+
export const PlatformSessionSchema = z
|
|
6
|
+
.object({
|
|
7
|
+
user: z
|
|
8
|
+
.object({
|
|
9
|
+
id: nonEmptyString,
|
|
10
|
+
email: nonEmptyString,
|
|
11
|
+
name: z.string().nullable().optional(),
|
|
12
|
+
})
|
|
13
|
+
.passthrough(),
|
|
14
|
+
session: z
|
|
15
|
+
.object({
|
|
16
|
+
id: nonEmptyString.optional(),
|
|
17
|
+
expiresAt: nonEmptyString.optional(),
|
|
18
|
+
})
|
|
19
|
+
.passthrough()
|
|
20
|
+
.nullable(),
|
|
21
|
+
})
|
|
22
|
+
.passthrough();
|
|
23
|
+
|
|
24
|
+
export type PlatformSession = z.infer<typeof PlatformSessionSchema>;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { BillingExecutionTraceSchema } from "./billing-trace.ts";
|
|
3
|
+
|
|
4
|
+
const trace = {
|
|
5
|
+
spanId: "span-1",
|
|
6
|
+
startedAt: "2026-07-12T12:00:00.000Z",
|
|
7
|
+
spans: [
|
|
8
|
+
{
|
|
9
|
+
kind: "provider",
|
|
10
|
+
event: "provider.finished",
|
|
11
|
+
requestId: "request-1",
|
|
12
|
+
executionId: "provider-1",
|
|
13
|
+
startedAt: "2026-07-12T12:00:00.000Z",
|
|
14
|
+
tokensInput: 10,
|
|
15
|
+
tokensOutput: 20,
|
|
16
|
+
cachedTokensInput: 2,
|
|
17
|
+
requestBytes: 100,
|
|
18
|
+
responseBytes: 200,
|
|
19
|
+
},
|
|
20
|
+
],
|
|
21
|
+
totalTokensInput: 10,
|
|
22
|
+
totalTokensOutput: 20,
|
|
23
|
+
totalCachedTokensInput: 2,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
describe("BillingExecutionTrace", () => {
|
|
27
|
+
it("accepts complete router trace data", () => {
|
|
28
|
+
expect(BillingExecutionTraceSchema.safeParse(trace).data).toEqual(trace);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("rejects malformed spans and negative counters", () => {
|
|
32
|
+
expect(
|
|
33
|
+
BillingExecutionTraceSchema.safeParse({
|
|
34
|
+
...trace,
|
|
35
|
+
spans: [{ ...trace.spans[0], kind: "other" }],
|
|
36
|
+
}).success,
|
|
37
|
+
).toBe(false);
|
|
38
|
+
expect(
|
|
39
|
+
BillingExecutionTraceSchema.safeParse({
|
|
40
|
+
...trace,
|
|
41
|
+
spans: [{ ...trace.spans[0], tokensOutput: -1 }],
|
|
42
|
+
}).success,
|
|
43
|
+
).toBe(false);
|
|
44
|
+
expect(
|
|
45
|
+
BillingExecutionTraceSchema.safeParse({
|
|
46
|
+
...trace,
|
|
47
|
+
totalTokensInput: "10",
|
|
48
|
+
}).success,
|
|
49
|
+
).toBe(false);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
const counter = z.number().finite().nonnegative();
|
|
4
|
+
const optionalText = z.string().min(1).optional();
|
|
5
|
+
|
|
6
|
+
export const BillingTraceSpanSchema = z.object({
|
|
7
|
+
kind: z.enum(["request", "provider", "tool", "executor"]),
|
|
8
|
+
event: z.string().min(1),
|
|
9
|
+
requestId: z.string().min(1),
|
|
10
|
+
executionId: z.string().min(1),
|
|
11
|
+
parentExecutionId: optionalText,
|
|
12
|
+
model: optionalText,
|
|
13
|
+
provider: optionalText,
|
|
14
|
+
toolName: optionalText,
|
|
15
|
+
executor: optionalText,
|
|
16
|
+
startedAt: z.string().min(1),
|
|
17
|
+
finishedAt: optionalText,
|
|
18
|
+
tokensInput: counter,
|
|
19
|
+
tokensOutput: counter,
|
|
20
|
+
cachedTokensInput: counter,
|
|
21
|
+
requestBytes: counter,
|
|
22
|
+
responseBytes: counter,
|
|
23
|
+
error: optionalText,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
export const BillingExecutionTraceSchema = z.object({
|
|
27
|
+
spanId: z.string().min(1),
|
|
28
|
+
startedAt: z.string().min(1),
|
|
29
|
+
finishedAt: optionalText,
|
|
30
|
+
spans: z.array(BillingTraceSpanSchema),
|
|
31
|
+
totalTokensInput: counter,
|
|
32
|
+
totalTokensOutput: counter,
|
|
33
|
+
totalCachedTokensInput: counter,
|
|
34
|
+
error: optionalText,
|
|
35
|
+
});
|
|
36
|
+
export const BillingTraceRequestSchema = z.object({
|
|
37
|
+
workspaceId: z.string().min(1),
|
|
38
|
+
trace: BillingExecutionTraceSchema,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
export type BillingTraceSpan = z.infer<typeof BillingTraceSpanSchema>;
|
|
42
|
+
export type BillingExecutionTrace = z.infer<typeof BillingExecutionTraceSchema>;
|
package/src/catalog.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
const modelPattern = z
|
|
4
|
+
.string()
|
|
5
|
+
.trim()
|
|
6
|
+
.regex(/^[^/]+(?:\/[^/]+)+$/, "Expected a slash-separated model path");
|
|
7
|
+
const cost = z.number().finite().int().nonnegative();
|
|
8
|
+
const sourceExtension = z.enum([".js", ".ts"]);
|
|
9
|
+
const galleryKind = z.enum(["model", "tool", "transform"]);
|
|
10
|
+
|
|
11
|
+
export const CreateModelPricingRequestSchema = z.object({
|
|
12
|
+
modelPattern,
|
|
13
|
+
inputCostMicrosPerM: cost,
|
|
14
|
+
outputCostMicrosPerM: cost,
|
|
15
|
+
cachedInputCostMicrosPerM: cost,
|
|
16
|
+
});
|
|
17
|
+
export const UpdateModelPricingRequestSchema =
|
|
18
|
+
CreateModelPricingRequestSchema.partial()
|
|
19
|
+
.refine((input) => Object.keys(input).length > 0)
|
|
20
|
+
.transform((input) => ({
|
|
21
|
+
...(input.modelPattern === undefined
|
|
22
|
+
? {}
|
|
23
|
+
: { modelPattern: input.modelPattern }),
|
|
24
|
+
...(input.inputCostMicrosPerM === undefined
|
|
25
|
+
? {}
|
|
26
|
+
: { inputCostMicrosPerM: input.inputCostMicrosPerM }),
|
|
27
|
+
...(input.outputCostMicrosPerM === undefined
|
|
28
|
+
? {}
|
|
29
|
+
: { outputCostMicrosPerM: input.outputCostMicrosPerM }),
|
|
30
|
+
...(input.cachedInputCostMicrosPerM === undefined
|
|
31
|
+
? {}
|
|
32
|
+
: { cachedInputCostMicrosPerM: input.cachedInputCostMicrosPerM }),
|
|
33
|
+
}));
|
|
34
|
+
|
|
35
|
+
export const TemplateGalleryKindSchema = galleryKind;
|
|
36
|
+
export const CreateTemplateGalleryRequestSchema = z.object({
|
|
37
|
+
kind: galleryKind,
|
|
38
|
+
name: z.string().trim().min(1),
|
|
39
|
+
description: z.string().trim(),
|
|
40
|
+
sourceCode: z.string().min(1),
|
|
41
|
+
sourceExtension,
|
|
42
|
+
});
|
|
43
|
+
export const UpdateTemplateGalleryRequestSchema =
|
|
44
|
+
CreateTemplateGalleryRequestSchema.partial()
|
|
45
|
+
.refine((input) => Object.keys(input).length > 0)
|
|
46
|
+
.transform((input) => ({
|
|
47
|
+
...(input.kind === undefined ? {} : { kind: input.kind }),
|
|
48
|
+
...(input.name === undefined ? {} : { name: input.name }),
|
|
49
|
+
...(input.description === undefined
|
|
50
|
+
? {}
|
|
51
|
+
: { description: input.description }),
|
|
52
|
+
...(input.sourceCode === undefined
|
|
53
|
+
? {}
|
|
54
|
+
: { sourceCode: input.sourceCode }),
|
|
55
|
+
...(input.sourceExtension === undefined
|
|
56
|
+
? {}
|
|
57
|
+
: { sourceExtension: input.sourceExtension }),
|
|
58
|
+
}));
|
|
59
|
+
|
|
60
|
+
export type CreateModelPricingRequest = z.infer<
|
|
61
|
+
typeof CreateModelPricingRequestSchema
|
|
62
|
+
>;
|
|
63
|
+
export type UpdateModelPricingRequest = z.infer<
|
|
64
|
+
typeof UpdateModelPricingRequestSchema
|
|
65
|
+
>;
|
|
66
|
+
export type CreateTemplateGalleryRequest = z.infer<
|
|
67
|
+
typeof CreateTemplateGalleryRequestSchema
|
|
68
|
+
>;
|
|
69
|
+
export type UpdateTemplateGalleryRequest = z.infer<
|
|
70
|
+
typeof UpdateTemplateGalleryRequestSchema
|
|
71
|
+
>;
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
export { PlatformSessionSchema } from "./auth.ts";
|
|
2
|
+
export type { PlatformSession } from "./auth.ts";
|
|
3
|
+
export {
|
|
4
|
+
createRuntimeJwtIssuer,
|
|
5
|
+
createRuntimeJwtAuthenticator,
|
|
6
|
+
RUNTIME_JWT_AUDIENCE,
|
|
7
|
+
RUNTIME_JWT_ISSUER,
|
|
8
|
+
RuntimeJwtClaimsSchema,
|
|
9
|
+
RuntimeJwtIssueInputSchema,
|
|
10
|
+
verifyRuntimeJwt,
|
|
11
|
+
} from "./runtime-jwt.ts";
|
|
12
|
+
export type {
|
|
13
|
+
RuntimeJwtClaims,
|
|
14
|
+
RuntimeJwtAuthenticator,
|
|
15
|
+
RuntimeJwtAuthConfig,
|
|
16
|
+
RuntimeJwtIssueInput,
|
|
17
|
+
RuntimeJwtIssuer,
|
|
18
|
+
RuntimeJwtVerificationConfig,
|
|
19
|
+
} from "./runtime-jwt.ts";
|
|
20
|
+
export {
|
|
21
|
+
BillingExecutionTraceSchema,
|
|
22
|
+
BillingTraceRequestSchema,
|
|
23
|
+
BillingTraceSpanSchema,
|
|
24
|
+
} from "./billing-trace.ts";
|
|
25
|
+
export type {
|
|
26
|
+
BillingExecutionTrace,
|
|
27
|
+
BillingTraceSpan,
|
|
28
|
+
} from "./billing-trace.ts";
|
|
29
|
+
export {
|
|
30
|
+
CreateWorkspaceInputSchema,
|
|
31
|
+
UpdateWorkspaceInputSchema,
|
|
32
|
+
} from "./workspace.ts";
|
|
33
|
+
export type {
|
|
34
|
+
CreateWorkspaceInput,
|
|
35
|
+
UpdateWorkspaceInput,
|
|
36
|
+
} from "./workspace.ts";
|
|
37
|
+
export {
|
|
38
|
+
CreateProviderNamespaceInputSchema,
|
|
39
|
+
UpdateProviderNamespaceInputSchema,
|
|
40
|
+
} from "./namespace.ts";
|
|
41
|
+
export type {
|
|
42
|
+
CreateProviderNamespaceInput,
|
|
43
|
+
UpdateProviderNamespaceInput,
|
|
44
|
+
} from "./namespace.ts";
|
|
45
|
+
export {
|
|
46
|
+
CreateModelRequestSchema,
|
|
47
|
+
CreateRemoteToolRequestSchema,
|
|
48
|
+
CreateToolRequestSchema,
|
|
49
|
+
CreateTransformRequestSchema,
|
|
50
|
+
RemoteMcpToolSnapshotSchema,
|
|
51
|
+
UpdateModelRequestSchema,
|
|
52
|
+
UpdateRemoteToolRequestSchema,
|
|
53
|
+
UpdateToolRequestSchema,
|
|
54
|
+
UpdateTransformRequestSchema,
|
|
55
|
+
UpsertModelSourceRequestSchema,
|
|
56
|
+
} from "./resources.ts";
|
|
57
|
+
export {
|
|
58
|
+
BalanceCheckSchema,
|
|
59
|
+
BillingLogSchema,
|
|
60
|
+
BillingSummarySchema,
|
|
61
|
+
BillingTransactionSchema,
|
|
62
|
+
CreatePlatformApiKeyResultSchema,
|
|
63
|
+
CheckoutSessionSchema,
|
|
64
|
+
DeployStatusSchema,
|
|
65
|
+
EmptyResponseSchema,
|
|
66
|
+
ModelPricingSchema,
|
|
67
|
+
ModelSchema,
|
|
68
|
+
OpenAiDeviceAuthStartSchema,
|
|
69
|
+
OpenAiDevicePollSchema,
|
|
70
|
+
PlatformApiKeySchema,
|
|
71
|
+
ProviderNamespaceSchema,
|
|
72
|
+
RemoteToolSchema,
|
|
73
|
+
TemplateGalleryPageSchema,
|
|
74
|
+
TemplateGallerySchema,
|
|
75
|
+
ToolSchema,
|
|
76
|
+
TransformSchema,
|
|
77
|
+
TriggerDeployResultSchema,
|
|
78
|
+
UpstreamProviderSchema,
|
|
79
|
+
WorkspaceCustomDomainSchema,
|
|
80
|
+
WorkspaceSchema,
|
|
81
|
+
WorkspaceStatsSchema,
|
|
82
|
+
WorkspacePermissionsSchema,
|
|
83
|
+
} from "./records.ts";
|
|
84
|
+
export type {
|
|
85
|
+
BillingLog,
|
|
86
|
+
BillingLogRecord,
|
|
87
|
+
BillingSummary,
|
|
88
|
+
BalanceCheckResult,
|
|
89
|
+
BillingTransaction,
|
|
90
|
+
BillingTransactionRecord,
|
|
91
|
+
CreatePlatformApiKeyResult,
|
|
92
|
+
DeployStatus,
|
|
93
|
+
Model,
|
|
94
|
+
ModelPricingRecord,
|
|
95
|
+
ModelRecord,
|
|
96
|
+
ModelPricing,
|
|
97
|
+
OpenAiDeviceAuthStart,
|
|
98
|
+
OpenAiDevicePoll,
|
|
99
|
+
PlatformApiKey,
|
|
100
|
+
PlatformApiKeyRecord,
|
|
101
|
+
ProviderNamespace,
|
|
102
|
+
ProviderNamespaceRecord,
|
|
103
|
+
RemoteTool,
|
|
104
|
+
RemoteToolRecord,
|
|
105
|
+
TemplateGallery,
|
|
106
|
+
TemplateGalleryKind,
|
|
107
|
+
TemplateGalleryPage,
|
|
108
|
+
TemplateGalleryRecord,
|
|
109
|
+
Tool,
|
|
110
|
+
ToolRecord,
|
|
111
|
+
TriggerDeployResult,
|
|
112
|
+
Transform,
|
|
113
|
+
TransformRecord,
|
|
114
|
+
UpstreamProvider,
|
|
115
|
+
UpstreamProviderRecord,
|
|
116
|
+
Workspace,
|
|
117
|
+
WorkspacePermissions,
|
|
118
|
+
WorkspaceRecord,
|
|
119
|
+
WorkspaceCustomDomainRecord,
|
|
120
|
+
ModelSourceExtension,
|
|
121
|
+
ModelStatus,
|
|
122
|
+
WorkspaceStats,
|
|
123
|
+
} from "./records.ts";
|
|
124
|
+
export {
|
|
125
|
+
CheckoutRequestSchema,
|
|
126
|
+
CreateApiKeyRequestSchema,
|
|
127
|
+
CreateUpstreamProviderRequestSchema,
|
|
128
|
+
CustomDomainRequestSchema,
|
|
129
|
+
OpenAiDevicePollRequestSchema,
|
|
130
|
+
OpenAiRefreshRequestSchema,
|
|
131
|
+
UpdateApiKeyRequestSchema,
|
|
132
|
+
UpdateUpstreamProviderRequestSchema,
|
|
133
|
+
} from "./management.ts";
|
|
134
|
+
export {
|
|
135
|
+
CreateModelPricingRequestSchema,
|
|
136
|
+
CreateTemplateGalleryRequestSchema,
|
|
137
|
+
TemplateGalleryKindSchema,
|
|
138
|
+
UpdateModelPricingRequestSchema,
|
|
139
|
+
UpdateTemplateGalleryRequestSchema,
|
|
140
|
+
} from "./catalog.ts";
|
|
141
|
+
export type {
|
|
142
|
+
CreateModelPricingRequest,
|
|
143
|
+
CreateTemplateGalleryRequest,
|
|
144
|
+
UpdateModelPricingRequest,
|
|
145
|
+
UpdateTemplateGalleryRequest,
|
|
146
|
+
} from "./catalog.ts";
|
|
147
|
+
export type {
|
|
148
|
+
CreateApiKeyRequest,
|
|
149
|
+
CreateUpstreamProviderRequest,
|
|
150
|
+
UpdateApiKeyRequest,
|
|
151
|
+
UpdateUpstreamProviderRequest,
|
|
152
|
+
} from "./management.ts";
|
|
153
|
+
export type {
|
|
154
|
+
CreateModelRequest,
|
|
155
|
+
CreateRemoteToolRequest,
|
|
156
|
+
CreateToolRequest,
|
|
157
|
+
CreateTransformRequest,
|
|
158
|
+
RemoteMcpToolSnapshot,
|
|
159
|
+
UpdateModelRequest,
|
|
160
|
+
UpdateRemoteToolRequest,
|
|
161
|
+
UpdateToolRequest,
|
|
162
|
+
UpdateTransformRequest,
|
|
163
|
+
UpsertModelSourceRequest,
|
|
164
|
+
} from "./resources.ts";
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
const nonEmptyString = z.string().min(1);
|
|
4
|
+
const nullableText = z.string().nullable();
|
|
5
|
+
|
|
6
|
+
export const CustomDomainRequestSchema = z.object({ hostname: nonEmptyString });
|
|
7
|
+
|
|
8
|
+
export const CreateApiKeyRequestSchema = z
|
|
9
|
+
.object({
|
|
10
|
+
displayName: nonEmptyString,
|
|
11
|
+
allowedModels: z.array(nonEmptyString).optional(),
|
|
12
|
+
allowedNamespaces: z.array(nonEmptyString).optional(),
|
|
13
|
+
expiresAt: z.string().optional(),
|
|
14
|
+
isDefault: z.boolean().optional(),
|
|
15
|
+
})
|
|
16
|
+
.transform((input) => ({
|
|
17
|
+
displayName: input.displayName,
|
|
18
|
+
...(input.allowedModels === undefined
|
|
19
|
+
? {}
|
|
20
|
+
: { allowedModels: input.allowedModels }),
|
|
21
|
+
...(input.allowedNamespaces === undefined
|
|
22
|
+
? {}
|
|
23
|
+
: { allowedNamespaces: input.allowedNamespaces }),
|
|
24
|
+
...(input.expiresAt === undefined ? {} : { expiresAt: input.expiresAt }),
|
|
25
|
+
...(input.isDefault === undefined ? {} : { isDefault: input.isDefault }),
|
|
26
|
+
}));
|
|
27
|
+
export const UpdateApiKeyRequestSchema = z
|
|
28
|
+
.object({ displayName: nonEmptyString })
|
|
29
|
+
.partial()
|
|
30
|
+
.refine((input) => Object.keys(input).length > 0)
|
|
31
|
+
.transform((input) =>
|
|
32
|
+
input.displayName === undefined ? {} : { displayName: input.displayName },
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
const upstreamProviderFields = z.object({
|
|
36
|
+
displayName: nonEmptyString,
|
|
37
|
+
apiBaseUrl: nullableText.optional(),
|
|
38
|
+
style: nullableText.optional(),
|
|
39
|
+
apiKey: nullableText.optional(),
|
|
40
|
+
oauthAccessKey: nullableText.optional(),
|
|
41
|
+
oauthRefreshToken: nullableText.optional(),
|
|
42
|
+
oauthExpiresAt: nullableText.optional(),
|
|
43
|
+
});
|
|
44
|
+
const partialUpstreamProviderFields = upstreamProviderFields.partial();
|
|
45
|
+
const upstreamProviderOutput = (
|
|
46
|
+
input: z.input<typeof partialUpstreamProviderFields>,
|
|
47
|
+
) => ({
|
|
48
|
+
...(input.displayName === undefined
|
|
49
|
+
? {}
|
|
50
|
+
: { displayName: input.displayName }),
|
|
51
|
+
...(input.apiBaseUrl === undefined ? {} : { apiBaseUrl: input.apiBaseUrl }),
|
|
52
|
+
...(input.style === undefined ? {} : { style: input.style }),
|
|
53
|
+
...(input.apiKey === undefined ? {} : { apiKey: input.apiKey }),
|
|
54
|
+
...(input.oauthAccessKey === undefined
|
|
55
|
+
? {}
|
|
56
|
+
: { oauthAccessKey: input.oauthAccessKey }),
|
|
57
|
+
...(input.oauthRefreshToken === undefined
|
|
58
|
+
? {}
|
|
59
|
+
: { oauthRefreshToken: input.oauthRefreshToken }),
|
|
60
|
+
...(input.oauthExpiresAt === undefined
|
|
61
|
+
? {}
|
|
62
|
+
: { oauthExpiresAt: input.oauthExpiresAt }),
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
export const CreateUpstreamProviderRequestSchema = upstreamProviderFields
|
|
66
|
+
.extend({
|
|
67
|
+
type: z.enum(["oauth_openai", "byok"]),
|
|
68
|
+
})
|
|
69
|
+
.transform((input) => ({
|
|
70
|
+
...upstreamProviderOutput(input),
|
|
71
|
+
type: input.type,
|
|
72
|
+
displayName: input.displayName,
|
|
73
|
+
}));
|
|
74
|
+
export const UpdateUpstreamProviderRequestSchema = partialUpstreamProviderFields
|
|
75
|
+
.refine((input) => Object.keys(input).length > 0)
|
|
76
|
+
.transform(upstreamProviderOutput);
|
|
77
|
+
|
|
78
|
+
export const OpenAiDevicePollRequestSchema = z.object({
|
|
79
|
+
deviceAuthId: nonEmptyString,
|
|
80
|
+
userCode: nonEmptyString,
|
|
81
|
+
});
|
|
82
|
+
export const OpenAiRefreshRequestSchema = z.object({
|
|
83
|
+
providerId: nonEmptyString,
|
|
84
|
+
});
|
|
85
|
+
export const CheckoutRequestSchema = z.object({
|
|
86
|
+
amountCents: z.number().finite().int().positive(),
|
|
87
|
+
successUrl: z.string().url(),
|
|
88
|
+
cancelUrl: z.string().url(),
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
export type CreateApiKeyRequest = z.infer<typeof CreateApiKeyRequestSchema>;
|
|
92
|
+
export type UpdateApiKeyRequest = z.infer<typeof UpdateApiKeyRequestSchema>;
|
|
93
|
+
export type CreateUpstreamProviderRequest = z.infer<
|
|
94
|
+
typeof CreateUpstreamProviderRequestSchema
|
|
95
|
+
>;
|
|
96
|
+
export type UpdateUpstreamProviderRequest = z.infer<
|
|
97
|
+
typeof UpdateUpstreamProviderRequestSchema
|
|
98
|
+
>;
|
package/src/namespace.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const CreateProviderNamespaceInputSchema = z.object({
|
|
4
|
+
code: z.string().min(1),
|
|
5
|
+
});
|
|
6
|
+
export const UpdateProviderNamespaceInputSchema =
|
|
7
|
+
CreateProviderNamespaceInputSchema.partial()
|
|
8
|
+
.refine((input) => Object.keys(input).length > 0)
|
|
9
|
+
.transform((input): { code?: string } =>
|
|
10
|
+
input.code === undefined ? {} : { code: input.code },
|
|
11
|
+
);
|
|
12
|
+
export type CreateProviderNamespaceInput = z.infer<
|
|
13
|
+
typeof CreateProviderNamespaceInputSchema
|
|
14
|
+
>;
|
|
15
|
+
export type UpdateProviderNamespaceInput = z.infer<
|
|
16
|
+
typeof UpdateProviderNamespaceInputSchema
|
|
17
|
+
>;
|
package/src/records.ts
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { RemoteMcpToolSnapshotSchema } from "./resources.ts";
|
|
3
|
+
|
|
4
|
+
const identifier = z.string().min(1);
|
|
5
|
+
const timestamp = z.string().min(1);
|
|
6
|
+
const sourceExtension = z.enum([".js", ".ts"]);
|
|
7
|
+
const nullableText = z.string().nullable();
|
|
8
|
+
|
|
9
|
+
export const EmptyResponseSchema = z.null().transform(() => undefined);
|
|
10
|
+
|
|
11
|
+
export const WorkspaceCustomDomainSchema = z.object({
|
|
12
|
+
workspaceId: identifier,
|
|
13
|
+
hostname: identifier,
|
|
14
|
+
customHostnameId: identifier,
|
|
15
|
+
workerRouteId: nullableText,
|
|
16
|
+
status: z.string(),
|
|
17
|
+
sslStatus: nullableText,
|
|
18
|
+
verificationErrors: z.array(z.string()),
|
|
19
|
+
cnameTarget: z.string(),
|
|
20
|
+
ownershipTxtName: nullableText,
|
|
21
|
+
ownershipTxtValue: nullableText,
|
|
22
|
+
createdAt: timestamp,
|
|
23
|
+
updatedAt: timestamp,
|
|
24
|
+
lastCheckedAt: nullableText,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
export const WorkspaceSchema = z.object({
|
|
28
|
+
id: identifier,
|
|
29
|
+
code: identifier,
|
|
30
|
+
displayName: identifier,
|
|
31
|
+
deployedUrl: nullableText,
|
|
32
|
+
generatedUrl: nullableText,
|
|
33
|
+
deployedAt: nullableText,
|
|
34
|
+
deployVersion: z.number().int(),
|
|
35
|
+
runtimeBindingsVersion: z.number().int(),
|
|
36
|
+
workerName: nullableText,
|
|
37
|
+
workerTag: nullableText,
|
|
38
|
+
repoConnectionUuid: nullableText,
|
|
39
|
+
buildTriggerUuid: nullableText,
|
|
40
|
+
customDomain: WorkspaceCustomDomainSchema.nullable(),
|
|
41
|
+
createdAt: timestamp,
|
|
42
|
+
updatedAt: timestamp,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
export const ProviderNamespaceSchema = z.object({
|
|
46
|
+
id: identifier,
|
|
47
|
+
workspaceId: identifier,
|
|
48
|
+
code: identifier,
|
|
49
|
+
createdAt: timestamp,
|
|
50
|
+
updatedAt: timestamp,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
export const ModelSchema = z.object({
|
|
54
|
+
id: identifier,
|
|
55
|
+
workspaceId: identifier,
|
|
56
|
+
providerNamespaceId: identifier,
|
|
57
|
+
providerNamespaceCode: identifier,
|
|
58
|
+
code: identifier,
|
|
59
|
+
sourceCode: z.string(),
|
|
60
|
+
sourceExtension,
|
|
61
|
+
status: z.enum(["draft", "deployed", "disabled"]),
|
|
62
|
+
deployedVersion: z.number().int(),
|
|
63
|
+
createdAt: timestamp,
|
|
64
|
+
updatedAt: timestamp,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
export const ToolSchema = z.object({
|
|
68
|
+
id: identifier,
|
|
69
|
+
workspaceId: identifier,
|
|
70
|
+
displayName: identifier,
|
|
71
|
+
sourceCode: z.string(),
|
|
72
|
+
sourceExtension,
|
|
73
|
+
attachedModelIds: z.array(identifier),
|
|
74
|
+
deployedVersion: z.number().int(),
|
|
75
|
+
createdAt: timestamp,
|
|
76
|
+
updatedAt: timestamp,
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
export const TransformSchema = z.object({
|
|
80
|
+
id: identifier,
|
|
81
|
+
workspaceId: identifier,
|
|
82
|
+
code: identifier,
|
|
83
|
+
sourceCode: z.string(),
|
|
84
|
+
sourceExtension,
|
|
85
|
+
deployedVersion: z.number().int(),
|
|
86
|
+
createdAt: timestamp,
|
|
87
|
+
updatedAt: timestamp,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
export const RemoteToolSchema = z.object({
|
|
91
|
+
id: identifier,
|
|
92
|
+
workspaceId: identifier,
|
|
93
|
+
code: identifier,
|
|
94
|
+
displayName: identifier,
|
|
95
|
+
type: z.literal("mcp_streamable_http"),
|
|
96
|
+
endpointUrl: z.string(),
|
|
97
|
+
staticHeaders: z.record(z.string(), z.string()),
|
|
98
|
+
oauthMode: z.literal("none"),
|
|
99
|
+
attachedModelIds: z.array(identifier),
|
|
100
|
+
discoveredTools: z.array(RemoteMcpToolSnapshotSchema),
|
|
101
|
+
lastRefreshedAt: nullableText,
|
|
102
|
+
lastRefreshError: nullableText,
|
|
103
|
+
deployedVersion: z.number().int(),
|
|
104
|
+
createdAt: timestamp,
|
|
105
|
+
updatedAt: timestamp,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
export const PlatformApiKeySchema = z.object({
|
|
109
|
+
id: identifier,
|
|
110
|
+
workspaceId: identifier,
|
|
111
|
+
displayName: identifier,
|
|
112
|
+
allowedModels: z.array(identifier),
|
|
113
|
+
allowedNamespaces: z.array(identifier),
|
|
114
|
+
expiresAt: timestamp,
|
|
115
|
+
revokedAt: nullableText,
|
|
116
|
+
isDefault: z.boolean(),
|
|
117
|
+
createdAt: timestamp,
|
|
118
|
+
updatedAt: timestamp,
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
export const CreatePlatformApiKeyResultSchema = z.object({
|
|
122
|
+
key: PlatformApiKeySchema,
|
|
123
|
+
token: identifier,
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
export const WorkspaceStatsSchema = z.object({
|
|
127
|
+
namespaces: z.number().int().nonnegative(),
|
|
128
|
+
models: z.number().int().nonnegative(),
|
|
129
|
+
tools: z.number().int().nonnegative(),
|
|
130
|
+
transforms: z.number().int().nonnegative(),
|
|
131
|
+
remoteTools: z.number().int().nonnegative(),
|
|
132
|
+
apiKeys: z.number().int().nonnegative(),
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
export const WorkspacePermissionsSchema = z.object({
|
|
136
|
+
templateGalleryWrite: z.boolean(),
|
|
137
|
+
pricingWrite: z.boolean(),
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
export const TriggerDeployResultSchema = z.object({
|
|
141
|
+
workerName: identifier,
|
|
142
|
+
url: z.string().url(),
|
|
143
|
+
commitSha: identifier,
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
export const DeployStatusSchema = z.object({
|
|
147
|
+
status: z.enum([
|
|
148
|
+
"queued",
|
|
149
|
+
"initializing",
|
|
150
|
+
"building",
|
|
151
|
+
"success",
|
|
152
|
+
"failure",
|
|
153
|
+
"canceled",
|
|
154
|
+
]),
|
|
155
|
+
url: nullableText,
|
|
156
|
+
buildUuid: nullableText,
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
export const CheckoutSessionSchema = z.object({
|
|
160
|
+
sessionId: identifier,
|
|
161
|
+
url: z.string().url(),
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
export const BillingLogSchema = z.object({
|
|
165
|
+
id: identifier,
|
|
166
|
+
workspaceId: identifier,
|
|
167
|
+
spanId: identifier,
|
|
168
|
+
providerCalls: z.number().int(),
|
|
169
|
+
toolCalls: z.number().int(),
|
|
170
|
+
tokensInput: z.number().int(),
|
|
171
|
+
tokensOutput: z.number().int(),
|
|
172
|
+
costMicros: z.number().int(),
|
|
173
|
+
errorMessage: nullableText,
|
|
174
|
+
createdAt: timestamp,
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
export const BillingSummarySchema = z.object({
|
|
178
|
+
totalRequests: z.number().int(),
|
|
179
|
+
totalTokensInput: z.number().int(),
|
|
180
|
+
totalTokensOutput: z.number().int(),
|
|
181
|
+
totalCostMicros: z.number().int(),
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
export const BillingTransactionSchema = z.object({
|
|
185
|
+
id: identifier,
|
|
186
|
+
workspaceId: identifier,
|
|
187
|
+
amountMicros: z.number().int(),
|
|
188
|
+
type: z.string(),
|
|
189
|
+
referenceId: nullableText,
|
|
190
|
+
description: nullableText,
|
|
191
|
+
createdAt: timestamp,
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
export const BalanceCheckSchema = z.object({
|
|
195
|
+
allowed: z.boolean(),
|
|
196
|
+
balanceMicros: z.number().int(),
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
export const ModelPricingSchema = z.object({
|
|
200
|
+
id: identifier,
|
|
201
|
+
modelPattern: identifier,
|
|
202
|
+
inputCostMicrosPerM: z.number().int(),
|
|
203
|
+
outputCostMicrosPerM: z.number().int(),
|
|
204
|
+
cachedInputCostMicrosPerM: z.number().int(),
|
|
205
|
+
createdAt: timestamp,
|
|
206
|
+
updatedAt: timestamp,
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
export const TemplateGallerySchema = z.object({
|
|
210
|
+
id: identifier,
|
|
211
|
+
kind: z.enum(["model", "tool", "transform"]),
|
|
212
|
+
name: identifier,
|
|
213
|
+
description: z.string(),
|
|
214
|
+
sourceCode: z.string(),
|
|
215
|
+
sourceExtension,
|
|
216
|
+
sortOrder: z.number().int(),
|
|
217
|
+
isEnabled: z.boolean(),
|
|
218
|
+
createdAt: timestamp,
|
|
219
|
+
updatedAt: timestamp,
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
export const TemplateGalleryPageSchema = z.object({
|
|
223
|
+
items: z.array(TemplateGallerySchema),
|
|
224
|
+
hasMore: z.boolean(),
|
|
225
|
+
nextOffset: z.number().int().nullable(),
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
export const UpstreamProviderSchema = z.object({
|
|
229
|
+
id: identifier,
|
|
230
|
+
workspaceId: identifier,
|
|
231
|
+
code: identifier,
|
|
232
|
+
displayName: identifier,
|
|
233
|
+
type: z.enum(["oauth_openai", "byok"]),
|
|
234
|
+
apiBaseUrl: nullableText,
|
|
235
|
+
style: nullableText,
|
|
236
|
+
apiKey: nullableText,
|
|
237
|
+
oauthAccessKey: nullableText,
|
|
238
|
+
oauthRefreshToken: nullableText,
|
|
239
|
+
oauthExpiresAt: nullableText,
|
|
240
|
+
createdAt: timestamp,
|
|
241
|
+
updatedAt: timestamp,
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
export const OpenAiDeviceAuthStartSchema = z.object({
|
|
245
|
+
deviceAuthId: identifier,
|
|
246
|
+
userCode: identifier,
|
|
247
|
+
verificationUrl: z.string().url(),
|
|
248
|
+
interval: z.number().int().positive(),
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
export const OpenAiDevicePollSchema = z.discriminatedUnion("status", [
|
|
252
|
+
z.object({ status: z.literal("pending") }),
|
|
253
|
+
z.object({ status: z.literal("complete"), provider: UpstreamProviderSchema }),
|
|
254
|
+
]);
|
|
255
|
+
|
|
256
|
+
export type Workspace = z.infer<typeof WorkspaceSchema>;
|
|
257
|
+
export type WorkspaceRecord = Workspace;
|
|
258
|
+
export type WorkspaceCustomDomainRecord = z.infer<
|
|
259
|
+
typeof WorkspaceCustomDomainSchema
|
|
260
|
+
>;
|
|
261
|
+
export type ModelSourceExtension = z.infer<typeof sourceExtension>;
|
|
262
|
+
export type ModelStatus = z.infer<typeof ModelSchema>["status"];
|
|
263
|
+
export type ProviderNamespaceRecord = z.infer<typeof ProviderNamespaceSchema>;
|
|
264
|
+
export type ModelRecord = z.infer<typeof ModelSchema>;
|
|
265
|
+
export type ToolRecord = z.infer<typeof ToolSchema>;
|
|
266
|
+
export type TransformRecord = z.infer<typeof TransformSchema>;
|
|
267
|
+
export type RemoteToolRecord = z.infer<typeof RemoteToolSchema>;
|
|
268
|
+
export type PlatformApiKeyRecord = z.infer<typeof PlatformApiKeySchema>;
|
|
269
|
+
export type CreatePlatformApiKeyResult = z.infer<
|
|
270
|
+
typeof CreatePlatformApiKeyResultSchema
|
|
271
|
+
>;
|
|
272
|
+
export type WorkspaceStats = z.infer<typeof WorkspaceStatsSchema>;
|
|
273
|
+
export type WorkspacePermissions = z.infer<typeof WorkspacePermissionsSchema>;
|
|
274
|
+
export type ModelPricingRecord = z.infer<typeof ModelPricingSchema>;
|
|
275
|
+
export type TemplateGalleryRecord = z.infer<typeof TemplateGallerySchema>;
|
|
276
|
+
export type TemplateGalleryPage = z.infer<typeof TemplateGalleryPageSchema>;
|
|
277
|
+
export type TemplateGalleryKind = TemplateGalleryRecord["kind"];
|
|
278
|
+
export type TriggerDeployResult = z.infer<typeof TriggerDeployResultSchema>;
|
|
279
|
+
export type DeployStatus = z.infer<typeof DeployStatusSchema>;
|
|
280
|
+
export type OpenAiDeviceAuthStart = z.infer<typeof OpenAiDeviceAuthStartSchema>;
|
|
281
|
+
export type OpenAiDevicePoll = z.infer<typeof OpenAiDevicePollSchema>;
|
|
282
|
+
export type ProviderNamespace = z.infer<typeof ProviderNamespaceSchema>;
|
|
283
|
+
export type Model = z.infer<typeof ModelSchema>;
|
|
284
|
+
export type Tool = z.infer<typeof ToolSchema>;
|
|
285
|
+
export type Transform = z.infer<typeof TransformSchema>;
|
|
286
|
+
export type RemoteTool = z.infer<typeof RemoteToolSchema>;
|
|
287
|
+
export type PlatformApiKey = z.infer<typeof PlatformApiKeySchema>;
|
|
288
|
+
export type BillingLog = z.infer<typeof BillingLogSchema>;
|
|
289
|
+
export type BillingLogRecord = BillingLog;
|
|
290
|
+
export type BillingSummary = z.infer<typeof BillingSummarySchema>;
|
|
291
|
+
export type BillingTransaction = z.infer<typeof BillingTransactionSchema>;
|
|
292
|
+
export type BillingTransactionRecord = BillingTransaction;
|
|
293
|
+
export type BalanceCheckResult = z.infer<typeof BalanceCheckSchema>;
|
|
294
|
+
export type ModelPricing = z.infer<typeof ModelPricingSchema>;
|
|
295
|
+
export type TemplateGallery = z.infer<typeof TemplateGallerySchema>;
|
|
296
|
+
export type UpstreamProvider = z.infer<typeof UpstreamProviderSchema>;
|
|
297
|
+
export type UpstreamProviderRecord = UpstreamProvider;
|
package/src/resources.ts
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
const nonEmptyString = z.string().min(1);
|
|
4
|
+
const sourceExtension = z.enum([".js", ".ts"]);
|
|
5
|
+
const remoteToolType = z.literal("mcp_streamable_http");
|
|
6
|
+
const remoteToolOauthMode = z.literal("none");
|
|
7
|
+
const stringList = z.array(nonEmptyString);
|
|
8
|
+
const jsonRecord = z.record(z.string(), z.unknown());
|
|
9
|
+
|
|
10
|
+
export const CreateModelRequestSchema = z.object({
|
|
11
|
+
providerNamespaceId: nonEmptyString,
|
|
12
|
+
code: nonEmptyString,
|
|
13
|
+
sourceCode: z.string().optional(),
|
|
14
|
+
sourceExtension: sourceExtension.optional(),
|
|
15
|
+
});
|
|
16
|
+
export const UpdateModelRequestSchema = CreateModelRequestSchema.partial()
|
|
17
|
+
.extend({
|
|
18
|
+
status: z.enum(["draft", "deployed", "disabled"]).optional(),
|
|
19
|
+
})
|
|
20
|
+
.refine((input) => Object.keys(input).length > 0)
|
|
21
|
+
.transform((input) => ({
|
|
22
|
+
...(input.providerNamespaceId === undefined
|
|
23
|
+
? {}
|
|
24
|
+
: { providerNamespaceId: input.providerNamespaceId }),
|
|
25
|
+
...(input.code === undefined ? {} : { code: input.code }),
|
|
26
|
+
...(input.sourceCode === undefined ? {} : { sourceCode: input.sourceCode }),
|
|
27
|
+
...(input.sourceExtension === undefined
|
|
28
|
+
? {}
|
|
29
|
+
: { sourceExtension: input.sourceExtension }),
|
|
30
|
+
...(input.status === undefined ? {} : { status: input.status }),
|
|
31
|
+
}));
|
|
32
|
+
export const UpsertModelSourceRequestSchema = z.object({
|
|
33
|
+
namespaceCode: nonEmptyString,
|
|
34
|
+
modelCode: nonEmptyString,
|
|
35
|
+
sourceCode: z.string().min(1),
|
|
36
|
+
sourceExtension,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
export const CreateToolRequestSchema = z.object({
|
|
40
|
+
displayName: nonEmptyString,
|
|
41
|
+
sourceCode: z.string().optional(),
|
|
42
|
+
sourceExtension: sourceExtension.optional(),
|
|
43
|
+
attachedModelIds: stringList.optional(),
|
|
44
|
+
});
|
|
45
|
+
export const UpdateToolRequestSchema = CreateToolRequestSchema.partial()
|
|
46
|
+
.refine((input) => Object.keys(input).length > 0)
|
|
47
|
+
.transform((input) => ({
|
|
48
|
+
...(input.displayName === undefined
|
|
49
|
+
? {}
|
|
50
|
+
: { displayName: input.displayName }),
|
|
51
|
+
...(input.sourceCode === undefined ? {} : { sourceCode: input.sourceCode }),
|
|
52
|
+
...(input.sourceExtension === undefined
|
|
53
|
+
? {}
|
|
54
|
+
: { sourceExtension: input.sourceExtension }),
|
|
55
|
+
...(input.attachedModelIds === undefined
|
|
56
|
+
? {}
|
|
57
|
+
: { attachedModelIds: input.attachedModelIds }),
|
|
58
|
+
}));
|
|
59
|
+
|
|
60
|
+
export const CreateTransformRequestSchema = z.object({
|
|
61
|
+
code: nonEmptyString,
|
|
62
|
+
sourceCode: z.string().optional(),
|
|
63
|
+
sourceExtension: sourceExtension.optional(),
|
|
64
|
+
});
|
|
65
|
+
export const UpdateTransformRequestSchema =
|
|
66
|
+
CreateTransformRequestSchema.partial()
|
|
67
|
+
.refine((input) => Object.keys(input).length > 0)
|
|
68
|
+
.transform((input) => ({
|
|
69
|
+
...(input.code === undefined ? {} : { code: input.code }),
|
|
70
|
+
...(input.sourceCode === undefined
|
|
71
|
+
? {}
|
|
72
|
+
: { sourceCode: input.sourceCode }),
|
|
73
|
+
...(input.sourceExtension === undefined
|
|
74
|
+
? {}
|
|
75
|
+
: { sourceExtension: input.sourceExtension }),
|
|
76
|
+
}));
|
|
77
|
+
|
|
78
|
+
export const RemoteMcpToolSnapshotSchema = z.object({
|
|
79
|
+
name: nonEmptyString,
|
|
80
|
+
description: z.string(),
|
|
81
|
+
inputSchema: jsonRecord,
|
|
82
|
+
});
|
|
83
|
+
const remoteToolRequest = z.object({
|
|
84
|
+
code: nonEmptyString,
|
|
85
|
+
displayName: nonEmptyString,
|
|
86
|
+
type: remoteToolType,
|
|
87
|
+
endpointUrl: z.string().optional(),
|
|
88
|
+
staticHeaders: z.record(z.string(), z.string()).optional(),
|
|
89
|
+
oauthMode: remoteToolOauthMode.optional(),
|
|
90
|
+
attachedModelIds: stringList.optional(),
|
|
91
|
+
});
|
|
92
|
+
export const CreateRemoteToolRequestSchema = remoteToolRequest.transform(
|
|
93
|
+
(input) => ({
|
|
94
|
+
code: input.code,
|
|
95
|
+
displayName: input.displayName,
|
|
96
|
+
type: input.type,
|
|
97
|
+
...(input.endpointUrl === undefined
|
|
98
|
+
? {}
|
|
99
|
+
: { endpointUrl: input.endpointUrl }),
|
|
100
|
+
...(input.staticHeaders === undefined
|
|
101
|
+
? {}
|
|
102
|
+
: { staticHeaders: input.staticHeaders }),
|
|
103
|
+
...(input.oauthMode === undefined ? {} : { oauthMode: input.oauthMode }),
|
|
104
|
+
...(input.attachedModelIds === undefined
|
|
105
|
+
? {}
|
|
106
|
+
: { attachedModelIds: input.attachedModelIds }),
|
|
107
|
+
}),
|
|
108
|
+
);
|
|
109
|
+
export const UpdateRemoteToolRequestSchema = remoteToolRequest
|
|
110
|
+
.partial()
|
|
111
|
+
.extend({
|
|
112
|
+
discoveredTools: z.array(RemoteMcpToolSnapshotSchema).optional(),
|
|
113
|
+
lastRefreshedAt: z.string().nullable().optional(),
|
|
114
|
+
lastRefreshError: z.string().nullable().optional(),
|
|
115
|
+
})
|
|
116
|
+
.refine((input) => Object.keys(input).length > 0)
|
|
117
|
+
.transform((input) => ({
|
|
118
|
+
...(input.code === undefined ? {} : { code: input.code }),
|
|
119
|
+
...(input.displayName === undefined
|
|
120
|
+
? {}
|
|
121
|
+
: { displayName: input.displayName }),
|
|
122
|
+
...(input.type === undefined ? {} : { type: input.type }),
|
|
123
|
+
...(input.endpointUrl === undefined
|
|
124
|
+
? {}
|
|
125
|
+
: { endpointUrl: input.endpointUrl }),
|
|
126
|
+
...(input.staticHeaders === undefined
|
|
127
|
+
? {}
|
|
128
|
+
: { staticHeaders: input.staticHeaders }),
|
|
129
|
+
...(input.oauthMode === undefined ? {} : { oauthMode: input.oauthMode }),
|
|
130
|
+
...(input.attachedModelIds === undefined
|
|
131
|
+
? {}
|
|
132
|
+
: { attachedModelIds: input.attachedModelIds }),
|
|
133
|
+
...(input.discoveredTools === undefined
|
|
134
|
+
? {}
|
|
135
|
+
: { discoveredTools: input.discoveredTools }),
|
|
136
|
+
...(input.lastRefreshedAt === undefined
|
|
137
|
+
? {}
|
|
138
|
+
: { lastRefreshedAt: input.lastRefreshedAt }),
|
|
139
|
+
...(input.lastRefreshError === undefined
|
|
140
|
+
? {}
|
|
141
|
+
: { lastRefreshError: input.lastRefreshError }),
|
|
142
|
+
}));
|
|
143
|
+
|
|
144
|
+
export type CreateModelRequest = z.infer<typeof CreateModelRequestSchema>;
|
|
145
|
+
export type UpdateModelRequest = z.infer<typeof UpdateModelRequestSchema>;
|
|
146
|
+
export type UpsertModelSourceRequest = z.infer<
|
|
147
|
+
typeof UpsertModelSourceRequestSchema
|
|
148
|
+
>;
|
|
149
|
+
export type CreateToolRequest = z.infer<typeof CreateToolRequestSchema>;
|
|
150
|
+
export type UpdateToolRequest = z.infer<typeof UpdateToolRequestSchema>;
|
|
151
|
+
export type CreateTransformRequest = z.infer<
|
|
152
|
+
typeof CreateTransformRequestSchema
|
|
153
|
+
>;
|
|
154
|
+
export type UpdateTransformRequest = z.infer<
|
|
155
|
+
typeof UpdateTransformRequestSchema
|
|
156
|
+
>;
|
|
157
|
+
export type CreateRemoteToolRequest = z.infer<
|
|
158
|
+
typeof CreateRemoteToolRequestSchema
|
|
159
|
+
>;
|
|
160
|
+
export type UpdateRemoteToolRequest = z.infer<
|
|
161
|
+
typeof UpdateRemoteToolRequestSchema
|
|
162
|
+
>;
|
|
163
|
+
export type RemoteMcpToolSnapshot = z.infer<typeof RemoteMcpToolSnapshotSchema>;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { exportJWK, generateKeyPair } from "jose";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import {
|
|
4
|
+
createRuntimeJwtIssuer,
|
|
5
|
+
RuntimeJwtIssueInputSchema,
|
|
6
|
+
verifyRuntimeJwt,
|
|
7
|
+
} from "./runtime-jwt.ts";
|
|
8
|
+
|
|
9
|
+
describe("runtime JWT contracts", () => {
|
|
10
|
+
it("verifies valid claims and enforces workspace, audience, and revocation", async () => {
|
|
11
|
+
const issuer = await testIssuer("key-1");
|
|
12
|
+
const token = await issuer.issue({
|
|
13
|
+
workspaceId: "workspace-1",
|
|
14
|
+
keyId: "api-key-1",
|
|
15
|
+
expiresAt: futureDate(),
|
|
16
|
+
allowedModels: ["default/chat"],
|
|
17
|
+
allowedNamespaces: null,
|
|
18
|
+
});
|
|
19
|
+
const config = {
|
|
20
|
+
jwksJson: issuer.publicJwksJson(),
|
|
21
|
+
workspaceId: "workspace-1",
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
await expect(verifyRuntimeJwt(token, config)).resolves.toEqual({
|
|
25
|
+
workspaceId: "workspace-1",
|
|
26
|
+
keyId: "api-key-1",
|
|
27
|
+
allowedModels: ["default/chat"],
|
|
28
|
+
allowedNamespaces: null,
|
|
29
|
+
});
|
|
30
|
+
await expect(
|
|
31
|
+
verifyRuntimeJwt(token, { ...config, workspaceId: "other" }),
|
|
32
|
+
).resolves.toBeNull();
|
|
33
|
+
await expect(
|
|
34
|
+
verifyRuntimeJwt(token, { ...config, audience: "other" }),
|
|
35
|
+
).resolves.toBeNull();
|
|
36
|
+
await expect(
|
|
37
|
+
verifyRuntimeJwt(token, { ...config, revokedKeyIds: ["api-key-1"] }),
|
|
38
|
+
).resolves.toBeNull();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("rejects expired and malformed tokens", async () => {
|
|
42
|
+
const issuer = await testIssuer("key-1");
|
|
43
|
+
const expired = await issuer.issue({
|
|
44
|
+
workspaceId: "workspace-1",
|
|
45
|
+
keyId: "api-key-1",
|
|
46
|
+
expiresAt: new Date(Date.now() - 1_000).toISOString(),
|
|
47
|
+
allowedModels: null,
|
|
48
|
+
allowedNamespaces: null,
|
|
49
|
+
});
|
|
50
|
+
const config = { jwksJson: issuer.publicJwksJson() };
|
|
51
|
+
|
|
52
|
+
await expect(verifyRuntimeJwt(expired, config)).resolves.toBeNull();
|
|
53
|
+
await expect(verifyRuntimeJwt(`${expired}x`, config)).resolves.toBeNull();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("accepts active keys during key rotation", async () => {
|
|
57
|
+
const previous = await testIssuer("old-key");
|
|
58
|
+
const current = await testIssuer("new-key");
|
|
59
|
+
const jwksJson = JSON.stringify({
|
|
60
|
+
keys: [
|
|
61
|
+
...jwks(previous.publicJwksJson()),
|
|
62
|
+
...jwks(current.publicJwksJson()),
|
|
63
|
+
],
|
|
64
|
+
});
|
|
65
|
+
const input = {
|
|
66
|
+
workspaceId: "workspace-1",
|
|
67
|
+
keyId: "api-key-1",
|
|
68
|
+
expiresAt: futureDate(),
|
|
69
|
+
allowedModels: null,
|
|
70
|
+
allowedNamespaces: ["default"],
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
await expect(
|
|
74
|
+
verifyRuntimeJwt(await previous.issue(input), { jwksJson }),
|
|
75
|
+
).resolves.not.toBeNull();
|
|
76
|
+
await expect(
|
|
77
|
+
verifyRuntimeJwt(await current.issue(input), { jwksJson }),
|
|
78
|
+
).resolves.not.toBeNull();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("rejects malformed issue input at the schema boundary", () => {
|
|
82
|
+
expect(
|
|
83
|
+
RuntimeJwtIssueInputSchema.safeParse({
|
|
84
|
+
workspaceId: "workspace-1",
|
|
85
|
+
keyId: "api-key-1",
|
|
86
|
+
expiresAt: "not-a-date",
|
|
87
|
+
allowedModels: ["default/chat", 1],
|
|
88
|
+
allowedNamespaces: null,
|
|
89
|
+
}).success,
|
|
90
|
+
).toBe(false);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
async function testIssuer(keyId: string) {
|
|
95
|
+
const pair = await generateKeyPair("ES256", { extractable: true });
|
|
96
|
+
const privateJwk = await exportJWK(pair.privateKey);
|
|
97
|
+
return createRuntimeJwtIssuer(JSON.stringify({ ...privateJwk, kid: keyId }));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function futureDate(): string {
|
|
101
|
+
return new Date(Date.now() + 60_000).toISOString();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function jwks(value: string): JsonWebKey[] {
|
|
105
|
+
return (JSON.parse(value) as { keys: JsonWebKey[] }).keys;
|
|
106
|
+
}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { SignJWT, createLocalJWKSet, importJWK, jwtVerify } from "jose";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
export const RUNTIME_JWT_ISSUER = "neutrome-platform";
|
|
5
|
+
export const RUNTIME_JWT_AUDIENCE = "workspace-runtime";
|
|
6
|
+
|
|
7
|
+
const nonEmptyString = z.string().min(1);
|
|
8
|
+
const nullableStringArray = z
|
|
9
|
+
.array(z.string())
|
|
10
|
+
.nullish()
|
|
11
|
+
.transform((value) => value ?? null);
|
|
12
|
+
const runtimeJwk = z
|
|
13
|
+
.object({
|
|
14
|
+
kty: z.literal("EC"),
|
|
15
|
+
crv: z.literal("P-256"),
|
|
16
|
+
x: nonEmptyString,
|
|
17
|
+
y: nonEmptyString,
|
|
18
|
+
})
|
|
19
|
+
.passthrough();
|
|
20
|
+
const parseJson = <T extends z.ZodType>(schema: T) =>
|
|
21
|
+
z
|
|
22
|
+
.string()
|
|
23
|
+
.transform((value, context) => {
|
|
24
|
+
try {
|
|
25
|
+
return JSON.parse(value);
|
|
26
|
+
} catch {
|
|
27
|
+
context.addIssue({ code: "custom", message: "Expected JSON" });
|
|
28
|
+
return z.NEVER;
|
|
29
|
+
}
|
|
30
|
+
})
|
|
31
|
+
.pipe(schema);
|
|
32
|
+
const privateRuntimeJwkJson = parseJson(
|
|
33
|
+
runtimeJwk.extend({ d: nonEmptyString }),
|
|
34
|
+
);
|
|
35
|
+
const runtimeJwksJson = parseJson(z.object({ keys: z.array(runtimeJwk) }));
|
|
36
|
+
const revocationsJson = parseJson(z.array(nonEmptyString));
|
|
37
|
+
const envString = nonEmptyString.optional();
|
|
38
|
+
|
|
39
|
+
export const RuntimeJwtClaimsSchema = z.object({
|
|
40
|
+
workspaceId: nonEmptyString,
|
|
41
|
+
keyId: nonEmptyString,
|
|
42
|
+
allowedModels: nullableStringArray,
|
|
43
|
+
allowedNamespaces: nullableStringArray,
|
|
44
|
+
});
|
|
45
|
+
const runtimeJwtPayload = z
|
|
46
|
+
.object({
|
|
47
|
+
sub: nonEmptyString,
|
|
48
|
+
jti: nonEmptyString,
|
|
49
|
+
models: nullableStringArray,
|
|
50
|
+
namespaces: nullableStringArray,
|
|
51
|
+
})
|
|
52
|
+
.transform(({ sub, jti, models, namespaces }) => ({
|
|
53
|
+
workspaceId: sub,
|
|
54
|
+
keyId: jti,
|
|
55
|
+
allowedModels: models,
|
|
56
|
+
allowedNamespaces: namespaces,
|
|
57
|
+
}));
|
|
58
|
+
|
|
59
|
+
export type RuntimeJwtClaims = z.infer<typeof RuntimeJwtClaimsSchema>;
|
|
60
|
+
export const RuntimeJwtIssueInputSchema = RuntimeJwtClaimsSchema.extend({
|
|
61
|
+
expiresAt: z
|
|
62
|
+
.string()
|
|
63
|
+
.refine((value) => Number.isFinite(new Date(value).getTime()), {
|
|
64
|
+
message: "Runtime JWT expiration must be an ISO timestamp",
|
|
65
|
+
}),
|
|
66
|
+
});
|
|
67
|
+
export type RuntimeJwtIssueInput = z.infer<typeof RuntimeJwtIssueInputSchema>;
|
|
68
|
+
|
|
69
|
+
export type RuntimeJwtIssuer = {
|
|
70
|
+
publicJwksJson(): string;
|
|
71
|
+
issue(input: RuntimeJwtIssueInput): Promise<string>;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export type RuntimeJwtVerificationConfig = {
|
|
75
|
+
jwksJson: string;
|
|
76
|
+
issuer?: string;
|
|
77
|
+
audience?: string;
|
|
78
|
+
revokedKeyIds?: readonly string[];
|
|
79
|
+
workspaceId?: string;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export type RuntimeJwtAuthenticator = {
|
|
83
|
+
authenticate(
|
|
84
|
+
request: Request,
|
|
85
|
+
env: Record<string, unknown>,
|
|
86
|
+
): Promise<RuntimeJwtClaims | null>;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
export type RuntimeJwtAuthConfig = Omit<
|
|
90
|
+
RuntimeJwtVerificationConfig,
|
|
91
|
+
"jwksJson" | "revokedKeyIds"
|
|
92
|
+
> & {
|
|
93
|
+
jwksJson?: string;
|
|
94
|
+
revokedKeyIdsJson?: string;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export function createRuntimeJwtIssuer(
|
|
98
|
+
privateJwkJson: string,
|
|
99
|
+
): RuntimeJwtIssuer {
|
|
100
|
+
const privateJwk = privateRuntimeJwkJson.parse(privateJwkJson);
|
|
101
|
+
const keyId = nonEmptyString.safeParse(privateJwk.kid).data ?? "runtime-key";
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
publicJwksJson() {
|
|
105
|
+
const { d: _d, key_ops: _keyOps, ...publicJwk } = privateJwk;
|
|
106
|
+
return JSON.stringify({
|
|
107
|
+
keys: [{ ...publicJwk, kid: keyId, alg: "ES256", use: "sig" }],
|
|
108
|
+
});
|
|
109
|
+
},
|
|
110
|
+
async issue(input) {
|
|
111
|
+
const parsedInput = RuntimeJwtIssueInputSchema.parse(input);
|
|
112
|
+
const expiresAt = Math.floor(
|
|
113
|
+
new Date(parsedInput.expiresAt).getTime() / 1000,
|
|
114
|
+
);
|
|
115
|
+
const key = await importJWK(privateJwk, "ES256");
|
|
116
|
+
return new SignJWT({
|
|
117
|
+
...(parsedInput.allowedModels
|
|
118
|
+
? { models: parsedInput.allowedModels }
|
|
119
|
+
: {}),
|
|
120
|
+
...(parsedInput.allowedNamespaces
|
|
121
|
+
? { namespaces: parsedInput.allowedNamespaces }
|
|
122
|
+
: {}),
|
|
123
|
+
})
|
|
124
|
+
.setProtectedHeader({ alg: "ES256", kid: keyId, typ: "JWT" })
|
|
125
|
+
.setIssuer(RUNTIME_JWT_ISSUER)
|
|
126
|
+
.setAudience(RUNTIME_JWT_AUDIENCE)
|
|
127
|
+
.setSubject(parsedInput.workspaceId)
|
|
128
|
+
.setJti(parsedInput.keyId)
|
|
129
|
+
.setIssuedAt()
|
|
130
|
+
.setExpirationTime(expiresAt)
|
|
131
|
+
.sign(key);
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export async function verifyRuntimeJwt(
|
|
137
|
+
token: string,
|
|
138
|
+
config: RuntimeJwtVerificationConfig,
|
|
139
|
+
): Promise<RuntimeJwtClaims | null> {
|
|
140
|
+
try {
|
|
141
|
+
const jwks = createLocalJWKSet(runtimeJwksJson.parse(config.jwksJson));
|
|
142
|
+
const { payload } = await jwtVerify(token, jwks, {
|
|
143
|
+
issuer: config.issuer ?? RUNTIME_JWT_ISSUER,
|
|
144
|
+
audience: config.audience ?? RUNTIME_JWT_AUDIENCE,
|
|
145
|
+
});
|
|
146
|
+
const claims = runtimeJwtPayload.safeParse(payload);
|
|
147
|
+
if (
|
|
148
|
+
!claims.success ||
|
|
149
|
+
(config.workspaceId && claims.data.workspaceId !== config.workspaceId)
|
|
150
|
+
)
|
|
151
|
+
return null;
|
|
152
|
+
if (config.revokedKeyIds?.includes(claims.data.keyId)) return null;
|
|
153
|
+
return claims.data;
|
|
154
|
+
} catch {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function createRuntimeJwtAuthenticator(
|
|
160
|
+
config: RuntimeJwtAuthConfig = {},
|
|
161
|
+
): RuntimeJwtAuthenticator {
|
|
162
|
+
return {
|
|
163
|
+
async authenticate(request, env) {
|
|
164
|
+
const token = readRuntimeToken(request);
|
|
165
|
+
if (!token) return null;
|
|
166
|
+
const issuer =
|
|
167
|
+
config.issuer ?? envString.safeParse(env.LIL_RUNTIME_AUTH_ISSUER).data;
|
|
168
|
+
const audience =
|
|
169
|
+
config.audience ??
|
|
170
|
+
envString.safeParse(env.LIL_RUNTIME_AUTH_AUDIENCE).data;
|
|
171
|
+
return verifyRuntimeJwt(token, {
|
|
172
|
+
jwksJson:
|
|
173
|
+
config.jwksJson ??
|
|
174
|
+
envString.safeParse(env.LIL_RUNTIME_AUTH_JWKS_JSON).data ??
|
|
175
|
+
"",
|
|
176
|
+
revokedKeyIds:
|
|
177
|
+
revocationsJson.safeParse(
|
|
178
|
+
config.revokedKeyIdsJson ??
|
|
179
|
+
envString.safeParse(env.LIL_RUNTIME_REVOKED_KEY_IDS_JSON).data ??
|
|
180
|
+
"[]",
|
|
181
|
+
).data ?? [],
|
|
182
|
+
...(issuer ? { issuer } : {}),
|
|
183
|
+
...(audience ? { audience } : {}),
|
|
184
|
+
...(config.workspaceId ? { workspaceId: config.workspaceId } : {}),
|
|
185
|
+
});
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function readRuntimeToken(request: Request): string | null {
|
|
191
|
+
const bearer = request.headers
|
|
192
|
+
.get("authorization")
|
|
193
|
+
?.match(/^Bearer\s+(.+)$/i)?.[1];
|
|
194
|
+
if (bearer) return bearer;
|
|
195
|
+
return (
|
|
196
|
+
request.headers.get("x-api-key") ??
|
|
197
|
+
request.headers.get("x-goog-api-key") ??
|
|
198
|
+
new URL(request.url).searchParams.get("key") ??
|
|
199
|
+
new URL(request.url).searchParams.get("api_key")
|
|
200
|
+
);
|
|
201
|
+
}
|
package/src/workspace.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
const nonEmptyString = z.string().min(1);
|
|
4
|
+
export const CreateWorkspaceInputSchema = z.object({
|
|
5
|
+
code: nonEmptyString,
|
|
6
|
+
displayName: nonEmptyString,
|
|
7
|
+
});
|
|
8
|
+
export const UpdateWorkspaceInputSchema = CreateWorkspaceInputSchema.partial()
|
|
9
|
+
.refine((input) => Object.keys(input).length > 0)
|
|
10
|
+
.transform((input): { code?: string; displayName?: string } => ({
|
|
11
|
+
...(input.code === undefined ? {} : { code: input.code }),
|
|
12
|
+
...(input.displayName === undefined
|
|
13
|
+
? {}
|
|
14
|
+
: { displayName: input.displayName }),
|
|
15
|
+
}));
|
|
16
|
+
export type CreateWorkspaceInput = z.infer<typeof CreateWorkspaceInputSchema>;
|
|
17
|
+
export type UpdateWorkspaceInput = z.infer<typeof UpdateWorkspaceInputSchema>;
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "Bundler",
|
|
6
|
+
"lib": ["ES2022", "DOM"],
|
|
7
|
+
"strict": true,
|
|
8
|
+
"allowImportingTsExtensions": true,
|
|
9
|
+
"rewriteRelativeImportExtensions": true,
|
|
10
|
+
"noUncheckedIndexedAccess": true,
|
|
11
|
+
"exactOptionalPropertyTypes": true,
|
|
12
|
+
"skipLibCheck": true,
|
|
13
|
+
"noEmit": true,
|
|
14
|
+
"types": ["vitest/globals"]
|
|
15
|
+
},
|
|
16
|
+
"include": ["src/**/*.ts"]
|
|
17
|
+
}
|