@krak-stack/registry 0.1.38 → 0.1.39
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 +106 -29
- package/dist/components/ui/app-brand.js +17 -17
- package/dist/components/ui/code-block.js +4 -4
- package/dist/components/ui/copy-button.js +2 -2
- package/dist/components/ui/data-table.js +55 -55
- package/dist/components/ui/editing-locale-switcher.js +8 -8
- package/dist/components/ui/effect-form.js +12 -12
- package/dist/components/ui/error-component.js +6 -6
- package/dist/components/ui/file-picker.js +2 -2
- package/dist/components/ui/form.js +24 -24
- package/dist/components/ui/icon-input.js +5 -5
- package/dist/components/ui/locale-switcher.js +2 -2
- package/dist/components/ui/search-menu.js +2 -2
- package/dist/components/ui/sidebar-layout.js +1 -1
- package/dist/components/ui/theme-switcher.js +3 -3
- package/dist/lib/docs.js +52 -111
- package/dist/lib/docs.server.js +0 -28
- package/dist/lib/documentation-toolkit.js +0 -28
- package/dist/lib/httpapi-adapter.d.ts +73 -0
- package/dist/lib/httpapi-adapter.js +189 -0
- package/dist/lib/httpapi-cli.d.ts +4 -22
- package/dist/lib/httpapi-cli.js +199 -313
- package/dist/lib/httpapi-toolkit.d.ts +7 -16
- package/dist/lib/httpapi-toolkit.js +158 -214
- package/dist/lib/query.d.ts +2 -2
- package/dist/lib/query.js +2 -2
- package/dist/lib/webfetch-toolkit.d.ts +1 -1
- package/dist/lib/webfetch-toolkit.js +48 -50
- package/dist/oxlint/anti-slop/index.js +2 -2
- package/dist/services/agent/client/index.js +6 -6
- package/dist/services/agent/index.js +2 -2
- package/package.json +4 -12
- package/dist/lib/httpapi-client.d.ts +0 -34
- package/dist/lib/httpapi-client.js +0 -122
- package/dist/lib/httpapi-helpers.d.ts +0 -79
- package/dist/lib/httpapi-helpers.js +0 -130
- package/dist/lib/httpapi-mcp.d.ts +0 -25
- package/dist/lib/httpapi-mcp.js +0 -321
package/dist/lib/httpapi-cli.js
CHANGED
|
@@ -1,39 +1,44 @@
|
|
|
1
1
|
// ../../src/lib/httpapi-cli.ts
|
|
2
|
-
import {
|
|
3
|
-
Cause,
|
|
4
|
-
Console,
|
|
5
|
-
Context as Context3,
|
|
6
|
-
Effect as Effect3,
|
|
7
|
-
Exit,
|
|
8
|
-
FileSystem,
|
|
9
|
-
Layer as Layer3,
|
|
10
|
-
Option as Option2,
|
|
11
|
-
Path,
|
|
12
|
-
Schema as Schema3,
|
|
13
|
-
Stdio,
|
|
14
|
-
Stream,
|
|
15
|
-
Terminal
|
|
16
|
-
} from "effect";
|
|
2
|
+
import { Console, Effect as Effect2, Option as Option2, Schema as Schema2, Stream } from "effect";
|
|
17
3
|
import { Argument, Command, Flag } from "effect/cli";
|
|
18
|
-
import { ChildProcessSpawner } from "effect/process/ChildProcessSpawner";
|
|
19
4
|
|
|
20
|
-
// ../../src/lib/httpapi-
|
|
21
|
-
import { Context, Effect, Layer, Schema, SchemaTransformation } from "effect";
|
|
22
|
-
import { HttpClient } from "effect/http";
|
|
5
|
+
// ../../src/lib/httpapi-adapter.ts
|
|
23
6
|
import {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
7
|
+
Context,
|
|
8
|
+
Effect,
|
|
9
|
+
Layer,
|
|
10
|
+
Option,
|
|
11
|
+
Schema,
|
|
12
|
+
SchemaTransformation
|
|
13
|
+
} from "effect";
|
|
14
|
+
import { HttpClient } from "effect/http";
|
|
15
|
+
import { HttpApi, HttpApiClient, OpenApi } from "effect/http-api";
|
|
16
|
+
var HttpApiMethods = [
|
|
17
|
+
"get",
|
|
18
|
+
"post",
|
|
19
|
+
"put",
|
|
20
|
+
"patch",
|
|
21
|
+
"delete"
|
|
22
|
+
];
|
|
23
|
+
var HttpApiOperation = Schema.Struct({
|
|
24
|
+
operationId: Schema.optional(Schema.String),
|
|
25
|
+
summary: Schema.optional(Schema.String),
|
|
26
|
+
description: Schema.optional(Schema.String),
|
|
27
|
+
tags: Schema.optional(Schema.Array(Schema.String)),
|
|
28
|
+
parameters: Schema.optional(Schema.Array(Schema.Struct({
|
|
29
|
+
name: Schema.String,
|
|
30
|
+
in: Schema.Literals(["path", "query", "header", "cookie"]),
|
|
31
|
+
required: Schema.optional(Schema.Boolean),
|
|
32
|
+
description: Schema.optional(Schema.String),
|
|
33
|
+
schema: Schema.optional(Schema.Json)
|
|
34
|
+
}))),
|
|
35
|
+
requestBody: Schema.optional(Schema.Struct({
|
|
36
|
+
required: Schema.optional(Schema.Boolean),
|
|
37
|
+
content: Schema.optional(Schema.Record(Schema.String, Schema.Struct({
|
|
38
|
+
schema: Schema.optional(Schema.Json)
|
|
39
|
+
})))
|
|
40
|
+
}))
|
|
41
|
+
}).annotate({ identifier: "HttpApiOperation" });
|
|
37
42
|
var UndefinedFromNull = Schema.Null.pipe(Schema.decodeTo(Schema.Undefined, SchemaTransformation.transform({
|
|
38
43
|
decode: () => {
|
|
39
44
|
return;
|
|
@@ -55,238 +60,163 @@ var HttpApiOperationResult = Schema.Union([
|
|
|
55
60
|
HttpApiOperationResultValue,
|
|
56
61
|
Schema.Undefined
|
|
57
62
|
]).annotate({ identifier: "HttpApiOperationResult" });
|
|
58
|
-
var encodeHttpApiOperationResult = Effect.fn("
|
|
63
|
+
var encodeHttpApiOperationResult = Effect.fn("HttpApiAdapter.encodeOperationResult")((result) => Schema.encodeUnknownEffect(HttpApiOperationResult)(result).pipe(Effect.map((encoded) => encoded ?? null), Effect.mapError((cause) => new Error("HTTP API result is not serializable", { cause }))));
|
|
64
|
+
var encodeOperationResult = Effect.fn("HttpApiAdapter.encodeResult")((result, operation) => operation.successSchema ? Schema.encodeUnknownEffect(operation.successSchema)(result).pipe(Effect.flatMap(encodeHttpApiOperationResult), Effect.mapError((cause) => new Error("HTTP API result does not match its success schema", {
|
|
65
|
+
cause
|
|
66
|
+
}))) : encodeHttpApiOperationResult(result));
|
|
59
67
|
var reflectedSchema = (schema) => Schema.make(schema.ast);
|
|
60
|
-
var
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}
|
|
72
|
-
|
|
68
|
+
var sanitizeHttpName = (name) => name.replace(/[^a-zA-Z0-9_-]/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "");
|
|
69
|
+
var httpApiToolName = (entry) => (entry.operation.operationId || `${entry.method}_${entry.path.replace(/^\/api\//, "").replace(/[/:{}]/g, "_")}`).replace(/[^a-zA-Z0-9_-]/g, "_").replace(/_+/g, "_").slice(0, 64);
|
|
70
|
+
var makeCatalogue = (api, methods, include) => Effect.try({
|
|
71
|
+
try: () => {
|
|
72
|
+
if (!HttpApi.isHttpApi(api))
|
|
73
|
+
throw new Error("HttpApiAdapter requires a valid HttpApi");
|
|
74
|
+
const spec = OpenApi.fromApi(api);
|
|
75
|
+
const reflected = new Map;
|
|
76
|
+
HttpApi.reflect(api, {
|
|
77
|
+
onGroup: () => {
|
|
78
|
+
return;
|
|
79
|
+
},
|
|
80
|
+
onEndpoint: ({ endpoint, group, successes }) => {
|
|
81
|
+
const operationId = Context.getOrElse(endpoint.annotations, OpenApi.Identifier, () => group.topLevel ? endpoint.identifier : `${group.identifier}.${endpoint.identifier}`);
|
|
82
|
+
if (reflected.has(operationId))
|
|
83
|
+
throw new Error(`Duplicate HTTP API operation ID: ${operationId}`);
|
|
84
|
+
const fields = {};
|
|
85
|
+
if (endpoint.params)
|
|
86
|
+
fields.params = Schema.toType(reflectedSchema(endpoint.params));
|
|
87
|
+
if (endpoint.query)
|
|
88
|
+
fields.query = Schema.optionalKey(Schema.toType(reflectedSchema(endpoint.query)));
|
|
89
|
+
if (endpoint.headers)
|
|
90
|
+
fields.headers = Schema.optionalKey(Schema.toType(reflectedSchema(endpoint.headers)));
|
|
91
|
+
const payloads = Array.from(endpoint.payload.values()).flatMap(({ schemas }) => schemas.map(reflectedSchema));
|
|
92
|
+
if (payloads.length === 1 && payloads[0])
|
|
93
|
+
fields.body = payloads[0];
|
|
94
|
+
else if (payloads.length > 1)
|
|
95
|
+
fields.body = Schema.Union(payloads);
|
|
96
|
+
const input = Object.keys(fields).length === 0 ? Schema.Record(Schema.String, Schema.Never) : Schema.Struct(fields);
|
|
97
|
+
const inputSchema = Schema.make(Schema.toCodecJson(input).ast).annotate({
|
|
98
|
+
identifier: `${sanitizeHttpName(operationId)}ToolInput`
|
|
99
|
+
});
|
|
100
|
+
const successSchemas = Array.from(successes.values()).flat().map(reflectedSchema);
|
|
101
|
+
reflected.set(operationId, {
|
|
102
|
+
groupIdentifier: group.identifier,
|
|
103
|
+
endpointIdentifier: endpoint.identifier,
|
|
104
|
+
topLevel: group.topLevel,
|
|
105
|
+
inputSchema,
|
|
106
|
+
successSchema: successSchemas.length === 1 ? successSchemas[0] : successSchemas.length > 1 ? Schema.Union(successSchemas) : undefined
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
const names = new Set;
|
|
111
|
+
const operations = [];
|
|
112
|
+
for (const [path, pathItem] of Object.entries(spec.paths)) {
|
|
113
|
+
for (const method of methods) {
|
|
114
|
+
const decoded = Schema.decodeUnknownOption(HttpApiOperation)(pathItem[method]);
|
|
115
|
+
if (Option.isNone(decoded))
|
|
116
|
+
continue;
|
|
117
|
+
const entry = { method, path, operation: decoded.value };
|
|
118
|
+
if (include && !include(entry))
|
|
119
|
+
continue;
|
|
120
|
+
const metadata = entry.operation.operationId ? reflected.get(entry.operation.operationId) : undefined;
|
|
121
|
+
if (!metadata)
|
|
122
|
+
throw new Error(`Missing endpoint schema for ${entry.operation.operationId}`);
|
|
123
|
+
const name = httpApiToolName(entry);
|
|
124
|
+
if (!name || names.has(name))
|
|
125
|
+
throw new Error(`Duplicate or empty HTTP API tool name: ${name}`);
|
|
126
|
+
names.add(name);
|
|
127
|
+
const document = Schema.toJsonSchemaDocument(metadata.inputSchema.annotate({ identifier: undefined }));
|
|
128
|
+
operations.push({
|
|
129
|
+
...entry,
|
|
130
|
+
...metadata,
|
|
131
|
+
name,
|
|
132
|
+
inputJsonSchema: {
|
|
133
|
+
...document.schema,
|
|
134
|
+
$defs: document.definitions
|
|
135
|
+
},
|
|
136
|
+
readOnly: method === "get",
|
|
137
|
+
destructive: method === "delete",
|
|
138
|
+
idempotent: method === "get" || method === "put" || method === "delete"
|
|
139
|
+
});
|
|
73
140
|
}
|
|
74
141
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const schemas = operationResultSchemas(api);
|
|
80
|
-
return Effect.fn("HttpApiClient.encodeOperationResultWithSchema")((result, operation) => {
|
|
81
|
-
const operationId = operation.operation.operationId;
|
|
82
|
-
const schema = operationId ? schemas.get(operationId) : undefined;
|
|
83
|
-
if (!schema)
|
|
84
|
-
return encodeHttpApiOperationResult(result);
|
|
85
|
-
return Schema.encodeUnknownEffect(schema)(result).pipe(Effect.flatMap(encodeHttpApiOperationResult), Effect.mapError((cause) => new Error("HTTP API result does not match its success schema", {
|
|
86
|
-
cause
|
|
87
|
-
})));
|
|
88
|
-
});
|
|
89
|
-
};
|
|
142
|
+
return { info: spec.info, operations };
|
|
143
|
+
},
|
|
144
|
+
catch: (cause) => new Error("Failed to build HTTP API adapter catalogue", { cause })
|
|
145
|
+
});
|
|
90
146
|
var GeneratedOperation = Schema.declare((value) => value instanceof Function).annotate({ identifier: "GeneratedOperation" });
|
|
91
147
|
var GeneratedOperationEffect = Schema.declare((value) => Effect.isEffect(value)).annotate({ identifier: "GeneratedOperationEffect" });
|
|
92
|
-
var
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
if (!operationId) {
|
|
99
|
-
return yield* Effect.fail(new Error(`No generated API client operation for ${entry.method} ${entry.path}`));
|
|
100
|
-
}
|
|
101
|
-
const target = Object(client);
|
|
102
|
-
const groupName = Object.keys(target).filter((name) => operationId.startsWith(`${name}.`)).sort((a, b) => b.length - a.length)[0];
|
|
103
|
-
const group = groupName ? Object.entries(target).find(([name]) => name === groupName)?.[1] : target;
|
|
104
|
-
const endpointName = groupName ? operationId.slice(groupName.length + 1) : operationId;
|
|
105
|
-
const endpointCandidate = group instanceof Function ? undefined : Object.entries(Object(group)).find(([name]) => name === endpointName)?.[1];
|
|
106
|
-
const endpoint = Schema.decodeUnknownOption(GeneratedOperation)(endpointCandidate);
|
|
107
|
-
if (endpoint._tag === "None") {
|
|
108
|
-
return yield* Effect.fail(new Error(`No generated API client operation for ${operationId}`));
|
|
109
|
-
}
|
|
148
|
+
var executeGeneratedOperation = Effect.fn("HttpApiAdapter.executeGeneratedOperation")(function* (client, { operation, input }) {
|
|
149
|
+
const group = operation.topLevel ? client : Object.entries(Object(client)).find(([name]) => name === operation.groupIdentifier)?.[1];
|
|
150
|
+
const candidate = Object.entries(Object(group)).find(([name]) => name === operation.endpointIdentifier)?.[1];
|
|
151
|
+
const endpoint = Schema.decodeUnknownOption(GeneratedOperation)(candidate);
|
|
152
|
+
if (Option.isNone(endpoint))
|
|
153
|
+
return yield* Effect.fail(new Error(`No generated API client operation for ${operation.operation.operationId}`));
|
|
110
154
|
const result = Schema.decodeUnknownOption(GeneratedOperationEffect)(endpoint.value({
|
|
111
|
-
headers: input.headers,
|
|
112
|
-
params: input.params,
|
|
113
|
-
query: input.query,
|
|
155
|
+
headers: input.headers ?? {},
|
|
156
|
+
params: input.params ?? {},
|
|
157
|
+
query: input.query ?? {},
|
|
114
158
|
payload: input.body
|
|
115
159
|
}));
|
|
116
|
-
if (result
|
|
117
|
-
return yield* Effect.fail(new Error(`Generated API client operation ${operationId} is invalid`));
|
|
118
|
-
}
|
|
160
|
+
if (Option.isNone(result))
|
|
161
|
+
return yield* Effect.fail(new Error(`Generated API client operation ${operation.operation.operationId} is invalid`));
|
|
119
162
|
return yield* result.value;
|
|
120
163
|
});
|
|
121
164
|
|
|
122
|
-
class
|
|
123
|
-
static
|
|
124
|
-
const
|
|
125
|
-
const
|
|
126
|
-
const client = yield*
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
165
|
+
class HttpApiAdapter extends Context.Service()("@krak-stack/registry/HttpApiAdapter") {
|
|
166
|
+
static make = Effect.fn("HttpApiAdapter.make")(function* (config) {
|
|
167
|
+
const catalogue = yield* makeCatalogue(config.api, config.methods ?? HttpApiMethods, config.include);
|
|
168
|
+
const httpClient = yield* HttpClient.HttpClient;
|
|
169
|
+
const client = yield* HttpApiClient.makeWith(config.api, {
|
|
170
|
+
baseUrl: config.baseUrl,
|
|
171
|
+
httpClient
|
|
172
|
+
});
|
|
173
|
+
return HttpApiAdapter.of({
|
|
174
|
+
...catalogue,
|
|
175
|
+
encodeResult: config.encodeResult ?? encodeOperationResult,
|
|
176
|
+
execute: Effect.fn("HttpApiAdapter.execute")(function* (options) {
|
|
177
|
+
const operation = catalogue.operations.find((entry) => entry.method === options.operation.method && entry.path === options.operation.path && entry.operation.operationId === options.operation.operation.operationId);
|
|
178
|
+
if (!operation) {
|
|
179
|
+
return yield* Effect.fail(new Error("Operation is not in this HTTP API adapter catalogue"));
|
|
180
|
+
}
|
|
181
|
+
return yield* executeGeneratedOperation(client, {
|
|
182
|
+
operation,
|
|
183
|
+
input: options.input
|
|
184
|
+
});
|
|
131
185
|
})
|
|
132
|
-
};
|
|
133
|
-
}));
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// ../../src/lib/httpapi-helpers.tsx
|
|
137
|
-
import { Context as Context2, Effect as Effect2, Layer as Layer2, Option, Schema as Schema2 } from "effect";
|
|
138
|
-
import { HttpApi as HttpApi2, OpenApi as OpenApi2 } from "effect/http-api";
|
|
139
|
-
var JsonObjectSchema = Schema2.Record(Schema2.String, Schema2.Json).annotate({
|
|
140
|
-
identifier: "HttpJsonObject",
|
|
141
|
-
title: "HTTP JSON object",
|
|
142
|
-
description: "A JSON object passed to an HTTP API operation.",
|
|
143
|
-
examples: [{ id: "example-id" }]
|
|
144
|
-
});
|
|
145
|
-
var JsonObjectFromString = Schema2.fromJsonString(JsonObjectSchema);
|
|
146
|
-
var JsonValueFromString = Schema2.fromJsonString(Schema2.Json);
|
|
147
|
-
var HttpApiMethods = [
|
|
148
|
-
"get",
|
|
149
|
-
"post",
|
|
150
|
-
"put",
|
|
151
|
-
"patch",
|
|
152
|
-
"delete"
|
|
153
|
-
];
|
|
154
|
-
var HttpApiOperationSchema = Schema2.declare((value) => Schema2.is(JsonObjectSchema)(value)).annotate({ identifier: "HttpApiOperation" });
|
|
155
|
-
var decodeHttpApiOperation = Schema2.decodeUnknownOption(HttpApiOperationSchema);
|
|
156
|
-
var toHttpError = (message, error) => new Error(message, { cause: error });
|
|
157
|
-
var sanitizeHttpName = (name) => name.replace(/[^a-zA-Z0-9_-]/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "");
|
|
158
|
-
var httpApiToolName = (method, path, operation) => {
|
|
159
|
-
const fallback = `${method}_${path.replace(/^\/api\//, "").replace(/[/:{}]/g, "_")}`;
|
|
160
|
-
return (operation.operationId || fallback).replace(/[^a-zA-Z0-9_-]/g, "_").replace(/_+/g, "_").slice(0, 64);
|
|
161
|
-
};
|
|
162
|
-
var httpApiOperations = ({
|
|
163
|
-
spec,
|
|
164
|
-
methods = HttpApiMethods
|
|
165
|
-
}) => {
|
|
166
|
-
const operations = [];
|
|
167
|
-
for (const [path, pathItem] of Object.entries(spec.paths)) {
|
|
168
|
-
for (const method of methods) {
|
|
169
|
-
const operation = decodeHttpApiOperation(pathItem[method]);
|
|
170
|
-
if (Option.isSome(operation))
|
|
171
|
-
operations.push({ method, path, operation: operation.value });
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
return operations;
|
|
175
|
-
};
|
|
176
|
-
var reflectedSchema2 = (schema) => Schema2.make(schema.ast);
|
|
177
|
-
var reflectedOperationInputSchemas = (api) => {
|
|
178
|
-
const schemas = new Map;
|
|
179
|
-
HttpApi2.reflect(api, {
|
|
180
|
-
onGroup: () => {
|
|
181
|
-
return;
|
|
182
|
-
},
|
|
183
|
-
onEndpoint: ({ endpoint, group }) => {
|
|
184
|
-
const operationId = Context2.getOrElse(endpoint.annotations, OpenApi2.Identifier, () => group.topLevel ? endpoint.identifier : `${group.identifier}.${endpoint.identifier}`);
|
|
185
|
-
const fields = {};
|
|
186
|
-
if (endpoint.params)
|
|
187
|
-
fields.params = Schema2.toType(reflectedSchema2(endpoint.params));
|
|
188
|
-
if (endpoint.query)
|
|
189
|
-
fields.query = Schema2.optionalKey(Schema2.toType(reflectedSchema2(endpoint.query)));
|
|
190
|
-
if (endpoint.headers)
|
|
191
|
-
fields.headers = Schema2.optionalKey(Schema2.toType(reflectedSchema2(endpoint.headers)));
|
|
192
|
-
const payloads = Array.from(endpoint.payload.values()).flatMap(({ schemas: schemas2 }) => schemas2.map(reflectedSchema2));
|
|
193
|
-
if (payloads.length === 1 && payloads[0])
|
|
194
|
-
fields.body = payloads[0];
|
|
195
|
-
else if (payloads.length > 1)
|
|
196
|
-
fields.body = Schema2.Union(payloads);
|
|
197
|
-
const input = Object.keys(fields).length === 0 ? Schema2.Record(Schema2.String, Schema2.Never) : Schema2.Struct(fields);
|
|
198
|
-
schemas.set(operationId, Schema2.make(Schema2.toCodecJson(input).ast).annotate({ identifier: `${sanitizeHttpName(operationId)}ToolInput` }));
|
|
199
|
-
}
|
|
186
|
+
});
|
|
200
187
|
});
|
|
201
|
-
|
|
202
|
-
};
|
|
203
|
-
var parseJsonObject = Effect2.fn("Http.parseJsonObject")(function* (value, label) {
|
|
204
|
-
if (!value)
|
|
205
|
-
return {};
|
|
206
|
-
return yield* Schema2.decodeUnknownEffect(JsonObjectFromString)(value).pipe(Effect2.mapError(() => new Error(`${label} must be a JSON object`)));
|
|
207
|
-
});
|
|
208
|
-
var parseJsonValue = Effect2.fn("Http.parseJsonValue")(function* (value, label) {
|
|
209
|
-
if (!value)
|
|
210
|
-
return;
|
|
211
|
-
return yield* Schema2.decodeUnknownEffect(JsonValueFromString)(value).pipe(Effect2.mapError(() => new Error(`${label} must be valid JSON`)));
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
class HttpApiSpec extends Context2.Service()("HttpApiSpec", {
|
|
215
|
-
make: (config) => Effect2.try({
|
|
216
|
-
try: () => {
|
|
217
|
-
const api = config.api;
|
|
218
|
-
if (!HttpApi2.isHttpApi(api))
|
|
219
|
-
throw new Error("HttpApiSpec requires a valid HttpApi");
|
|
220
|
-
const spec = OpenApi2.fromApi(api);
|
|
221
|
-
const schemas = reflectedOperationInputSchemas(api);
|
|
222
|
-
const names = new Set;
|
|
223
|
-
const operations = httpApiOperations({
|
|
224
|
-
spec,
|
|
225
|
-
methods: config.methods
|
|
226
|
-
}).filter((entry) => config.include?.(entry) ?? true).map((entry) => {
|
|
227
|
-
const name = httpApiToolName(entry.method, entry.path, entry.operation);
|
|
228
|
-
if (!name || names.has(name))
|
|
229
|
-
throw new Error(`Duplicate or empty HTTP API tool name: ${name}`);
|
|
230
|
-
names.add(name);
|
|
231
|
-
const inputSchema = entry.operation.operationId ? schemas.get(entry.operation.operationId) : undefined;
|
|
232
|
-
if (!inputSchema)
|
|
233
|
-
throw new Error(`Missing endpoint schema for ${entry.operation.operationId}`);
|
|
234
|
-
const document = Schema2.toJsonSchemaDocument(inputSchema.annotate({ identifier: undefined }));
|
|
235
|
-
return {
|
|
236
|
-
...entry,
|
|
237
|
-
name,
|
|
238
|
-
inputSchema,
|
|
239
|
-
inputJsonSchema: {
|
|
240
|
-
...document.schema,
|
|
241
|
-
$defs: document.definitions
|
|
242
|
-
},
|
|
243
|
-
readOnly: entry.method === "get",
|
|
244
|
-
destructive: entry.method === "delete",
|
|
245
|
-
idempotent: entry.method === "get" || entry.method === "put" || entry.method === "delete"
|
|
246
|
-
};
|
|
247
|
-
});
|
|
248
|
-
return { info: spec.info, operations };
|
|
249
|
-
},
|
|
250
|
-
catch: (error) => toHttpError("Failed to build HTTP API spec", error)
|
|
251
|
-
})
|
|
252
|
-
}) {
|
|
253
|
-
static layer = (config) => Layer2.effect(this, this.make(config));
|
|
188
|
+
static layer = (config) => Layer.effect(this, this.make(config));
|
|
254
189
|
}
|
|
255
190
|
|
|
256
191
|
// ../../src/lib/httpapi-cli.ts
|
|
257
|
-
var CliJsonSchema =
|
|
258
|
-
type:
|
|
259
|
-
description:
|
|
260
|
-
enum:
|
|
261
|
-
items:
|
|
262
|
-
properties:
|
|
263
|
-
required:
|
|
264
|
-
pattern:
|
|
265
|
-
allOf:
|
|
266
|
-
anyOf:
|
|
267
|
-
$ref:
|
|
268
|
-
$defs:
|
|
192
|
+
var CliJsonSchema = Schema2.Struct({
|
|
193
|
+
type: Schema2.optional(Schema2.Union([Schema2.String, Schema2.Array(Schema2.String)])),
|
|
194
|
+
description: Schema2.optional(Schema2.String),
|
|
195
|
+
enum: Schema2.optional(Schema2.Array(Schema2.Json)),
|
|
196
|
+
items: Schema2.optional(Schema2.Json),
|
|
197
|
+
properties: Schema2.optional(Schema2.Record(Schema2.String, Schema2.Json)),
|
|
198
|
+
required: Schema2.optional(Schema2.Array(Schema2.String)),
|
|
199
|
+
pattern: Schema2.optional(Schema2.String),
|
|
200
|
+
allOf: Schema2.optional(Schema2.Array(Schema2.Json)),
|
|
201
|
+
anyOf: Schema2.optional(Schema2.Array(Schema2.Json)),
|
|
202
|
+
$ref: Schema2.optional(Schema2.String),
|
|
203
|
+
$defs: Schema2.optional(Schema2.Record(Schema2.String, Schema2.Json))
|
|
269
204
|
}).annotate({ identifier: "HttpApiCliJsonSchema" });
|
|
270
|
-
var decodeCliJsonSchema =
|
|
271
|
-
var JsonFromString =
|
|
272
|
-
var CliParsedOption =
|
|
205
|
+
var decodeCliJsonSchema = Schema2.decodeUnknownOption(CliJsonSchema);
|
|
206
|
+
var JsonFromString = Schema2.fromJsonString(Schema2.Json);
|
|
207
|
+
var CliParsedOption = Schema2.declare((value) => Option2.isOption(value) && Option2.match(value, {
|
|
273
208
|
onNone: () => true,
|
|
274
|
-
onSome:
|
|
209
|
+
onSome: Schema2.is(Schema2.Json)
|
|
275
210
|
})).annotate({ identifier: "HttpApiCliParsedOption" });
|
|
276
|
-
var CliParsedConfig =
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
};
|
|
286
|
-
})
|
|
287
|
-
}) {
|
|
288
|
-
static layer = Layer3.effect(this, this.make());
|
|
289
|
-
}
|
|
211
|
+
var CliParsedConfig = Schema2.Record(Schema2.String, Schema2.Union([Schema2.Json, CliParsedOption])).annotate({ identifier: "HttpApiCliParsedConfig" });
|
|
212
|
+
var makeHttpApiCli = Effect2.fn("HttpApiCli.make")(function* () {
|
|
213
|
+
const spec = yield* HttpApiAdapter;
|
|
214
|
+
const command = yield* makeHttpApiCliCommand();
|
|
215
|
+
return {
|
|
216
|
+
command,
|
|
217
|
+
run: (args) => Command.runWith(command, { version: spec.info.version })(args)
|
|
218
|
+
};
|
|
219
|
+
});
|
|
290
220
|
var toHttpApiCliName = (value) => value.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1-$2").replace(/[^a-zA-Z0-9]+/g, "-").replace(/^-+|-+$/g, "").toLowerCase();
|
|
291
221
|
var fallbackName = (value, fallback) => toHttpApiCliName(value) || fallback;
|
|
292
222
|
var operationIdParts = (operation) => operation.operationId?.split(".").filter(Boolean) ?? [];
|
|
@@ -343,8 +273,8 @@ var schemaType = (schema) => {
|
|
|
343
273
|
return type;
|
|
344
274
|
return schemaType(effectiveSchema(schema));
|
|
345
275
|
};
|
|
346
|
-
var schemaEnum = (schema) =>
|
|
347
|
-
var jsonFlag = (name) => Flag.String(name).pipe(Flag.mapTryCatch((value) =>
|
|
276
|
+
var schemaEnum = (schema) => Schema2.decodeUnknownOption(Schema2.Array(Schema2.String))(effectiveSchema(schema).enum).pipe(Option2.getOrElse(() => []));
|
|
277
|
+
var jsonFlag = (name) => Flag.String(name).pipe(Flag.mapTryCatch((value) => Schema2.decodeUnknownSync(JsonFromString)(value), () => `--${name} must be valid JSON`));
|
|
348
278
|
var primitiveFlag = (name, schema, required) => {
|
|
349
279
|
const type = schemaType(schema);
|
|
350
280
|
const choices = schemaEnum(schema);
|
|
@@ -452,16 +382,16 @@ var operationInputs = (operation) => {
|
|
|
452
382
|
};
|
|
453
383
|
});
|
|
454
384
|
};
|
|
455
|
-
var HttpApiCliResultStream =
|
|
456
|
-
var printHttpApiCliResult =
|
|
457
|
-
const stream =
|
|
385
|
+
var HttpApiCliResultStream = Schema2.declare((value) => Stream.isStream(value)).annotate({ identifier: "HttpApiCliResultStream" });
|
|
386
|
+
var printHttpApiCliResult = Effect2.fn("HttpApiCli.printResult")(function* (response, operation, client) {
|
|
387
|
+
const stream = Schema2.decodeUnknownOption(HttpApiCliResultStream)(response);
|
|
458
388
|
if (stream._tag === "Some") {
|
|
459
389
|
return yield* stream.value.pipe(Stream.mapEffect((event) => client.encodeResult(event, operation)), Stream.runForEach((event) => print(JSON.stringify(event))));
|
|
460
390
|
}
|
|
461
391
|
const encoded = yield* client.encodeResult(response, operation);
|
|
462
392
|
return yield* print(JSON.stringify(encoded, null, 2) ?? "null");
|
|
463
393
|
});
|
|
464
|
-
var callOperation =
|
|
394
|
+
var callOperation = Effect2.fn("HttpApiCli.callOperation")(function* (operation, inputs, callConfig, client) {
|
|
465
395
|
const emptyValues = () => ({});
|
|
466
396
|
const assembled = {
|
|
467
397
|
body: emptyValues(),
|
|
@@ -469,28 +399,28 @@ var callOperation = Effect3.fn("HttpApiCli.callOperation")(function* (operation,
|
|
|
469
399
|
params: emptyValues(),
|
|
470
400
|
query: emptyValues()
|
|
471
401
|
};
|
|
472
|
-
for (const
|
|
473
|
-
const parsed = callConfig[
|
|
402
|
+
for (const input of inputs) {
|
|
403
|
+
const parsed = callConfig[input.configKey];
|
|
474
404
|
const value = Option2.isOption(parsed) ? Option2.getOrUndefined(parsed) : parsed;
|
|
475
405
|
if (value !== undefined) {
|
|
476
|
-
assembled[
|
|
406
|
+
assembled[input.location][input.name] = value;
|
|
477
407
|
}
|
|
478
408
|
}
|
|
479
|
-
const bodyInput = inputs.some((
|
|
409
|
+
const bodyInput = inputs.some((input) => input.location === "body" && input.name === "body") ? assembled.body.body : Object.keys(assembled.body).length > 0 || cliJsonSchema(operation.inputJsonSchema).required?.includes("body") ? assembled.body : undefined;
|
|
480
410
|
const rawInput = {};
|
|
481
|
-
if (inputs.some((
|
|
411
|
+
if (inputs.some((input) => input.location === "params")) {
|
|
482
412
|
rawInput.params = assembled.params;
|
|
483
413
|
}
|
|
484
|
-
if (inputs.some((
|
|
414
|
+
if (inputs.some((input) => input.location === "headers")) {
|
|
485
415
|
rawInput.headers = assembled.headers;
|
|
486
416
|
}
|
|
487
|
-
if (inputs.some((
|
|
417
|
+
if (inputs.some((input) => input.location === "query")) {
|
|
488
418
|
rawInput.query = assembled.query;
|
|
489
419
|
}
|
|
490
420
|
if (bodyInput !== undefined)
|
|
491
421
|
rawInput.body = bodyInput;
|
|
492
|
-
const input = yield*
|
|
493
|
-
const response = yield*
|
|
422
|
+
const input = yield* Schema2.decodeUnknownEffect(operation.inputSchema)(rawInput).pipe(Effect2.mapError((error) => new Error(`Invalid input for ${operation.groupName} ${operation.name}: ${error.message}`)));
|
|
423
|
+
const response = yield* client.execute({ operation, input });
|
|
494
424
|
return yield* printHttpApiCliResult(response, operation, client);
|
|
495
425
|
});
|
|
496
426
|
var listCommand = (groups) => Command.make("list", {}, () => print([
|
|
@@ -499,75 +429,31 @@ var listCommand = (groups) => Command.make("list", {}, () => print([
|
|
|
499
429
|
].join(`
|
|
500
430
|
`))).pipe(Command.withDescription("List command groups"));
|
|
501
431
|
var groupListCommand = (group) => Command.make("list", {}, () => listOperations(group.operations)).pipe(Command.withDescription(`List ${group.title} operations`));
|
|
502
|
-
var operationCommand = (operation, client) =>
|
|
432
|
+
var operationCommand = (operation, client) => Effect2.sync(() => {
|
|
503
433
|
const inputs = operationInputs(operation);
|
|
504
434
|
const config = Object.fromEntries(inputs.map((input) => [input.configKey, input.param]));
|
|
505
|
-
return Command.make(operation.name, config, (callConfig) =>
|
|
435
|
+
return Command.make(operation.name, config, (callConfig) => Schema2.decodeUnknownEffect(CliParsedConfig)(callConfig).pipe(Effect2.flatMap((parsed) => callOperation(operation, inputs, parsed, client)))).pipe(Command.withDescription(operation.summary || `${operation.method.toUpperCase()} ${operation.path}`), Command.withExamples([
|
|
506
436
|
{
|
|
507
437
|
command: `${operation.groupName} ${operation.name}`,
|
|
508
438
|
description: `${operation.method.toUpperCase()} ${operation.path}`
|
|
509
439
|
}
|
|
510
440
|
]));
|
|
511
441
|
});
|
|
512
|
-
var groupCommand = (group, client) =>
|
|
442
|
+
var groupCommand = (group, client) => Effect2.all(group.operations.map((operation) => operationCommand(operation, client))).pipe(Effect2.map((operationCommands) => Command.make(group.name).pipe(Command.withDescription(group.title), Command.withSubcommands([
|
|
513
443
|
groupListCommand(group),
|
|
514
444
|
...operationCommands
|
|
515
445
|
]))));
|
|
516
|
-
var makeHttpApiCliCommand =
|
|
517
|
-
const spec = yield*
|
|
518
|
-
const client =
|
|
446
|
+
var makeHttpApiCliCommand = Effect2.fn("HttpApiCli.makeCommand")(function* () {
|
|
447
|
+
const spec = yield* HttpApiAdapter;
|
|
448
|
+
const client = spec;
|
|
519
449
|
const name = fallbackName(spec.info.title, "api");
|
|
520
|
-
const groups = yield*
|
|
450
|
+
const groups = yield* Effect2.try({
|
|
521
451
|
try: () => httpApiCliOperationGroups(spec.operations.map(toCliOperation)),
|
|
522
452
|
catch: (error) => error instanceof Error ? error : new Error(String(error))
|
|
523
453
|
});
|
|
524
|
-
const groupCommands = yield*
|
|
454
|
+
const groupCommands = yield* Effect2.all(groups.map((group) => groupCommand(group, client)));
|
|
525
455
|
return Command.make(name).pipe(Command.withDescription(spec.info.description ?? spec.info.title), Command.withSubcommands([listCommand(groups), ...groupCommands]));
|
|
526
456
|
});
|
|
527
|
-
var cliEnvironmentLayer = (args) => Layer3.mergeAll(FileSystem.layerNoop({}), Path.layer, Stdio.layerTest({ args: Effect3.succeed(Array.from(args)) }), Layer3.succeed(Terminal.Terminal, Terminal.make({
|
|
528
|
-
columns: Effect3.sync(() => process.stdout.columns ?? 80),
|
|
529
|
-
rows: Effect3.sync(() => process.stdout.rows ?? 24),
|
|
530
|
-
readInput: Effect3.die("Terminal input is not supported"),
|
|
531
|
-
readLine: Effect3.die("Terminal input is not supported"),
|
|
532
|
-
display: (text) => Effect3.sync(() => process.stdout.write(text))
|
|
533
|
-
})), Layer3.succeed(ChildProcessSpawner, ChildProcessSpawner.of({
|
|
534
|
-
spawn: () => Effect3.die("Child processes are not supported"),
|
|
535
|
-
exitCode: () => Effect3.die("Child processes are not supported"),
|
|
536
|
-
streamString: () => Stream.die("Child processes are not supported"),
|
|
537
|
-
streamLines: () => Stream.die("Child processes are not supported"),
|
|
538
|
-
lines: () => Effect3.die("Child processes are not supported"),
|
|
539
|
-
string: () => Effect3.die("Child processes are not supported")
|
|
540
|
-
})));
|
|
541
|
-
var httpApiCliEnvironmentLayer = (args) => cliEnvironmentLayer(args);
|
|
542
|
-
var httpApiCli = (args = process.argv.slice(2)) => Effect3.gen(function* () {
|
|
543
|
-
const cli = yield* HttpApiCli;
|
|
544
|
-
return yield* cli.run(args);
|
|
545
|
-
});
|
|
546
|
-
var formatHttpApiCliCause = (cause) => {
|
|
547
|
-
const errors = Cause.prettyErrors(cause);
|
|
548
|
-
if (errors.length === 0)
|
|
549
|
-
return Cause.pretty(cause);
|
|
550
|
-
return errors.map((error) => error.message ? `${error.name}: ${error.message}` : error.name).join(`
|
|
551
|
-
`);
|
|
552
|
-
};
|
|
553
|
-
var runHttpApiCli = (layer, args = process.argv.slice(2)) => {
|
|
554
|
-
Effect3.runPromiseExit(httpApiCli(args).pipe(Effect3.provide(layer), Effect3.provide(httpApiCliEnvironmentLayer(args)))).then(Exit.match({
|
|
555
|
-
onSuccess: () => {
|
|
556
|
-
return;
|
|
557
|
-
},
|
|
558
|
-
onFailure: (cause) => {
|
|
559
|
-
console.error(formatHttpApiCliCause(cause));
|
|
560
|
-
process.exitCode = 1;
|
|
561
|
-
}
|
|
562
|
-
}));
|
|
563
|
-
};
|
|
564
457
|
export {
|
|
565
|
-
|
|
566
|
-
formatHttpApiCliCause,
|
|
567
|
-
httpApiCli,
|
|
568
|
-
httpApiCliEnvironmentLayer,
|
|
569
|
-
makeHttpApiCliCommand,
|
|
570
|
-
printHttpApiCliResult,
|
|
571
|
-
runHttpApiCli,
|
|
572
|
-
toHttpApiCliName
|
|
458
|
+
makeHttpApiCli
|
|
573
459
|
};
|
|
@@ -1,31 +1,22 @@
|
|
|
1
|
-
import { Effect,
|
|
1
|
+
import { Effect, Schema } from "effect";
|
|
2
2
|
import type { Json } from "effect/Schema";
|
|
3
3
|
import { Tool, Toolkit } from "effect/ai";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import { HttpApiAdapter } from "./httpapi-adapter.js";
|
|
5
|
+
import { type HttpApiOperationEntry } from "./httpapi-adapter.js";
|
|
6
6
|
export type HttpApiToolkitConfig = {
|
|
7
7
|
readonly toolMetaKey?: string;
|
|
8
8
|
readonly needsApproval?: (operation: HttpApiOperationEntry) => boolean;
|
|
9
9
|
readonly strict?: (operation: HttpApiOperationEntry) => boolean;
|
|
10
10
|
readonly transformResult?: (operation: HttpApiOperationEntry, result: Json) => Json;
|
|
11
11
|
};
|
|
12
|
-
export declare const makeHttpApiToolkit: (config
|
|
12
|
+
export declare const makeHttpApiToolkit: (config?: HttpApiToolkitConfig | undefined) => Effect.Effect<{
|
|
13
13
|
toolkit: Toolkit.Toolkit<{
|
|
14
14
|
readonly [x: string]: Tool.Tool<string, {
|
|
15
|
-
readonly parameters: Schema.Codec<Partial<import("./httpapi-
|
|
15
|
+
readonly parameters: Schema.Codec<Partial<import("./httpapi-adapter.js").HttpApiOperationInput>, Json, never, never>;
|
|
16
16
|
readonly success: Schema.Codec<Json, Json, never, never>;
|
|
17
17
|
readonly failure: Schema.String;
|
|
18
18
|
readonly failureMode: "return";
|
|
19
19
|
}, never>;
|
|
20
20
|
}>;
|
|
21
|
-
handlers: Layer.Layer<Tool.Handler<string>, never,
|
|
22
|
-
}, never,
|
|
23
|
-
export declare const HttpApiToolkit: (config: HttpApiToolkitConfig) => Effect.Effect<Toolkit.Toolkit<{
|
|
24
|
-
readonly [x: string]: Tool.Tool<string, {
|
|
25
|
-
readonly parameters: Schema.Codec<Partial<import("./httpapi-helpers.js").HttpApiOperationInput>, Json, never, never>;
|
|
26
|
-
readonly success: Schema.Codec<Json, Json, never, never>;
|
|
27
|
-
readonly failure: Schema.String;
|
|
28
|
-
readonly failureMode: "return";
|
|
29
|
-
}, never>;
|
|
30
|
-
}>, never, HttpApiSpec>;
|
|
31
|
-
export declare const HttpApiToolkitLayer: (config: HttpApiToolkitConfig) => Layer.Layer<Tool.Handler<string>, never, ApiClient | HttpApiSpec>;
|
|
21
|
+
handlers: import("effect/Layer").Layer<Tool.Handler<string>, never, HttpApiAdapter>;
|
|
22
|
+
}, never, HttpApiAdapter>;
|