@krak-stack/registry 0.1.38 → 0.1.40
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 +30 -3
- package/dist/lib/query.js +13 -3
- 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
|
@@ -1,24 +1,44 @@
|
|
|
1
1
|
// ../../src/lib/httpapi-toolkit.ts
|
|
2
|
-
import { Effect as
|
|
2
|
+
import { Effect as Effect2, Schema as Schema2 } from "effect";
|
|
3
3
|
import { Tool, Toolkit } from "effect/ai";
|
|
4
4
|
|
|
5
|
-
// ../../src/lib/httpapi-
|
|
6
|
-
import { Context, Effect, Layer, Schema, SchemaTransformation } from "effect";
|
|
7
|
-
import { HttpClient } from "effect/http";
|
|
5
|
+
// ../../src/lib/httpapi-adapter.ts
|
|
8
6
|
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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" });
|
|
22
42
|
var UndefinedFromNull = Schema.Null.pipe(Schema.decodeTo(Schema.Undefined, SchemaTransformation.transform({
|
|
23
43
|
decode: () => {
|
|
24
44
|
return;
|
|
@@ -40,202 +60,132 @@ var HttpApiOperationResult = Schema.Union([
|
|
|
40
60
|
HttpApiOperationResultValue,
|
|
41
61
|
Schema.Undefined
|
|
42
62
|
]).annotate({ identifier: "HttpApiOperationResult" });
|
|
43
|
-
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));
|
|
44
67
|
var reflectedSchema = (schema) => Schema.make(schema.ast);
|
|
45
|
-
var
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
57
|
-
|
|
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
|
+
});
|
|
58
140
|
}
|
|
59
141
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const schemas = operationResultSchemas(api);
|
|
65
|
-
return Effect.fn("HttpApiClient.encodeOperationResultWithSchema")((result, operation) => {
|
|
66
|
-
const operationId = operation.operation.operationId;
|
|
67
|
-
const schema = operationId ? schemas.get(operationId) : undefined;
|
|
68
|
-
if (!schema)
|
|
69
|
-
return encodeHttpApiOperationResult(result);
|
|
70
|
-
return Schema.encodeUnknownEffect(schema)(result).pipe(Effect.flatMap(encodeHttpApiOperationResult), Effect.mapError((cause) => new Error("HTTP API result does not match its success schema", {
|
|
71
|
-
cause
|
|
72
|
-
})));
|
|
73
|
-
});
|
|
74
|
-
};
|
|
142
|
+
return { info: spec.info, operations };
|
|
143
|
+
},
|
|
144
|
+
catch: (cause) => new Error("Failed to build HTTP API adapter catalogue", { cause })
|
|
145
|
+
});
|
|
75
146
|
var GeneratedOperation = Schema.declare((value) => value instanceof Function).annotate({ identifier: "GeneratedOperation" });
|
|
76
147
|
var GeneratedOperationEffect = Schema.declare((value) => Effect.isEffect(value)).annotate({ identifier: "GeneratedOperationEffect" });
|
|
77
|
-
var
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
if (!operationId) {
|
|
84
|
-
return yield* Effect.fail(new Error(`No generated API client operation for ${entry.method} ${entry.path}`));
|
|
85
|
-
}
|
|
86
|
-
const target = Object(client);
|
|
87
|
-
const groupName = Object.keys(target).filter((name) => operationId.startsWith(`${name}.`)).sort((a, b) => b.length - a.length)[0];
|
|
88
|
-
const group = groupName ? Object.entries(target).find(([name]) => name === groupName)?.[1] : target;
|
|
89
|
-
const endpointName = groupName ? operationId.slice(groupName.length + 1) : operationId;
|
|
90
|
-
const endpointCandidate = group instanceof Function ? undefined : Object.entries(Object(group)).find(([name]) => name === endpointName)?.[1];
|
|
91
|
-
const endpoint = Schema.decodeUnknownOption(GeneratedOperation)(endpointCandidate);
|
|
92
|
-
if (endpoint._tag === "None") {
|
|
93
|
-
return yield* Effect.fail(new Error(`No generated API client operation for ${operationId}`));
|
|
94
|
-
}
|
|
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}`));
|
|
95
154
|
const result = Schema.decodeUnknownOption(GeneratedOperationEffect)(endpoint.value({
|
|
96
|
-
headers: input.headers,
|
|
97
|
-
params: input.params,
|
|
98
|
-
query: input.query,
|
|
155
|
+
headers: input.headers ?? {},
|
|
156
|
+
params: input.params ?? {},
|
|
157
|
+
query: input.query ?? {},
|
|
99
158
|
payload: input.body
|
|
100
159
|
}));
|
|
101
|
-
if (result
|
|
102
|
-
return yield* Effect.fail(new Error(`Generated API client operation ${operationId} is invalid`));
|
|
103
|
-
}
|
|
160
|
+
if (Option.isNone(result))
|
|
161
|
+
return yield* Effect.fail(new Error(`Generated API client operation ${operation.operation.operationId} is invalid`));
|
|
104
162
|
return yield* result.value;
|
|
105
163
|
});
|
|
106
164
|
|
|
107
|
-
class
|
|
108
|
-
static
|
|
109
|
-
const
|
|
110
|
-
const
|
|
111
|
-
const client = yield*
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
+
});
|
|
116
185
|
})
|
|
117
|
-
};
|
|
118
|
-
}));
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// ../../src/lib/httpapi-helpers.tsx
|
|
122
|
-
import { Context as Context2, Effect as Effect2, Layer as Layer2, Option, Schema as Schema2 } from "effect";
|
|
123
|
-
import { HttpApi as HttpApi2, OpenApi as OpenApi2 } from "effect/http-api";
|
|
124
|
-
var JsonObjectSchema = Schema2.Record(Schema2.String, Schema2.Json).annotate({
|
|
125
|
-
identifier: "HttpJsonObject",
|
|
126
|
-
title: "HTTP JSON object",
|
|
127
|
-
description: "A JSON object passed to an HTTP API operation.",
|
|
128
|
-
examples: [{ id: "example-id" }]
|
|
129
|
-
});
|
|
130
|
-
var JsonObjectFromString = Schema2.fromJsonString(JsonObjectSchema);
|
|
131
|
-
var JsonValueFromString = Schema2.fromJsonString(Schema2.Json);
|
|
132
|
-
var HttpApiMethods = [
|
|
133
|
-
"get",
|
|
134
|
-
"post",
|
|
135
|
-
"put",
|
|
136
|
-
"patch",
|
|
137
|
-
"delete"
|
|
138
|
-
];
|
|
139
|
-
var HttpApiOperationSchema = Schema2.declare((value) => Schema2.is(JsonObjectSchema)(value)).annotate({ identifier: "HttpApiOperation" });
|
|
140
|
-
var decodeHttpApiOperation = Schema2.decodeUnknownOption(HttpApiOperationSchema);
|
|
141
|
-
var toHttpError = (message, error) => new Error(message, { cause: error });
|
|
142
|
-
var sanitizeHttpName = (name) => name.replace(/[^a-zA-Z0-9_-]/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "");
|
|
143
|
-
var httpApiToolName = (method, path, operation) => {
|
|
144
|
-
const fallback = `${method}_${path.replace(/^\/api\//, "").replace(/[/:{}]/g, "_")}`;
|
|
145
|
-
return (operation.operationId || fallback).replace(/[^a-zA-Z0-9_-]/g, "_").replace(/_+/g, "_").slice(0, 64);
|
|
146
|
-
};
|
|
147
|
-
var httpApiOperations = ({
|
|
148
|
-
spec,
|
|
149
|
-
methods = HttpApiMethods
|
|
150
|
-
}) => {
|
|
151
|
-
const operations = [];
|
|
152
|
-
for (const [path, pathItem] of Object.entries(spec.paths)) {
|
|
153
|
-
for (const method of methods) {
|
|
154
|
-
const operation = decodeHttpApiOperation(pathItem[method]);
|
|
155
|
-
if (Option.isSome(operation))
|
|
156
|
-
operations.push({ method, path, operation: operation.value });
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
return operations;
|
|
160
|
-
};
|
|
161
|
-
var reflectedSchema2 = (schema) => Schema2.make(schema.ast);
|
|
162
|
-
var reflectedOperationInputSchemas = (api) => {
|
|
163
|
-
const schemas = new Map;
|
|
164
|
-
HttpApi2.reflect(api, {
|
|
165
|
-
onGroup: () => {
|
|
166
|
-
return;
|
|
167
|
-
},
|
|
168
|
-
onEndpoint: ({ endpoint, group }) => {
|
|
169
|
-
const operationId = Context2.getOrElse(endpoint.annotations, OpenApi2.Identifier, () => group.topLevel ? endpoint.identifier : `${group.identifier}.${endpoint.identifier}`);
|
|
170
|
-
const fields = {};
|
|
171
|
-
if (endpoint.params)
|
|
172
|
-
fields.params = Schema2.toType(reflectedSchema2(endpoint.params));
|
|
173
|
-
if (endpoint.query)
|
|
174
|
-
fields.query = Schema2.optionalKey(Schema2.toType(reflectedSchema2(endpoint.query)));
|
|
175
|
-
if (endpoint.headers)
|
|
176
|
-
fields.headers = Schema2.optionalKey(Schema2.toType(reflectedSchema2(endpoint.headers)));
|
|
177
|
-
const payloads = Array.from(endpoint.payload.values()).flatMap(({ schemas: schemas2 }) => schemas2.map(reflectedSchema2));
|
|
178
|
-
if (payloads.length === 1 && payloads[0])
|
|
179
|
-
fields.body = payloads[0];
|
|
180
|
-
else if (payloads.length > 1)
|
|
181
|
-
fields.body = Schema2.Union(payloads);
|
|
182
|
-
const input = Object.keys(fields).length === 0 ? Schema2.Record(Schema2.String, Schema2.Never) : Schema2.Struct(fields);
|
|
183
|
-
schemas.set(operationId, Schema2.make(Schema2.toCodecJson(input).ast).annotate({ identifier: `${sanitizeHttpName(operationId)}ToolInput` }));
|
|
184
|
-
}
|
|
186
|
+
});
|
|
185
187
|
});
|
|
186
|
-
|
|
187
|
-
};
|
|
188
|
-
var parseJsonObject = Effect2.fn("Http.parseJsonObject")(function* (value, label) {
|
|
189
|
-
if (!value)
|
|
190
|
-
return {};
|
|
191
|
-
return yield* Schema2.decodeUnknownEffect(JsonObjectFromString)(value).pipe(Effect2.mapError(() => new Error(`${label} must be a JSON object`)));
|
|
192
|
-
});
|
|
193
|
-
var parseJsonValue = Effect2.fn("Http.parseJsonValue")(function* (value, label) {
|
|
194
|
-
if (!value)
|
|
195
|
-
return;
|
|
196
|
-
return yield* Schema2.decodeUnknownEffect(JsonValueFromString)(value).pipe(Effect2.mapError(() => new Error(`${label} must be valid JSON`)));
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
class HttpApiSpec extends Context2.Service()("HttpApiSpec", {
|
|
200
|
-
make: (config) => Effect2.try({
|
|
201
|
-
try: () => {
|
|
202
|
-
const api = config.api;
|
|
203
|
-
if (!HttpApi2.isHttpApi(api))
|
|
204
|
-
throw new Error("HttpApiSpec requires a valid HttpApi");
|
|
205
|
-
const spec = OpenApi2.fromApi(api);
|
|
206
|
-
const schemas = reflectedOperationInputSchemas(api);
|
|
207
|
-
const names = new Set;
|
|
208
|
-
const operations = httpApiOperations({
|
|
209
|
-
spec,
|
|
210
|
-
methods: config.methods
|
|
211
|
-
}).filter((entry) => config.include?.(entry) ?? true).map((entry) => {
|
|
212
|
-
const name = httpApiToolName(entry.method, entry.path, entry.operation);
|
|
213
|
-
if (!name || names.has(name))
|
|
214
|
-
throw new Error(`Duplicate or empty HTTP API tool name: ${name}`);
|
|
215
|
-
names.add(name);
|
|
216
|
-
const inputSchema = entry.operation.operationId ? schemas.get(entry.operation.operationId) : undefined;
|
|
217
|
-
if (!inputSchema)
|
|
218
|
-
throw new Error(`Missing endpoint schema for ${entry.operation.operationId}`);
|
|
219
|
-
const document = Schema2.toJsonSchemaDocument(inputSchema.annotate({ identifier: undefined }));
|
|
220
|
-
return {
|
|
221
|
-
...entry,
|
|
222
|
-
name,
|
|
223
|
-
inputSchema,
|
|
224
|
-
inputJsonSchema: {
|
|
225
|
-
...document.schema,
|
|
226
|
-
$defs: document.definitions
|
|
227
|
-
},
|
|
228
|
-
readOnly: entry.method === "get",
|
|
229
|
-
destructive: entry.method === "delete",
|
|
230
|
-
idempotent: entry.method === "get" || entry.method === "put" || entry.method === "delete"
|
|
231
|
-
};
|
|
232
|
-
});
|
|
233
|
-
return { info: spec.info, operations };
|
|
234
|
-
},
|
|
235
|
-
catch: (error) => toHttpError("Failed to build HTTP API spec", error)
|
|
236
|
-
})
|
|
237
|
-
}) {
|
|
238
|
-
static layer = (config) => Layer2.effect(this, this.make(config));
|
|
188
|
+
static layer = (config) => Layer.effect(this, this.make(config));
|
|
239
189
|
}
|
|
240
190
|
|
|
241
191
|
// ../../src/lib/httpapi-toolkit.ts
|
|
@@ -250,8 +200,8 @@ var makeOperationTool = (entry, config) => {
|
|
|
250
200
|
|
|
251
201
|
${guidance}` : guidance,
|
|
252
202
|
parameters: entry.inputSchema,
|
|
253
|
-
success:
|
|
254
|
-
failure:
|
|
203
|
+
success: Schema2.Json,
|
|
204
|
+
failure: Schema2.String,
|
|
255
205
|
failureMode: "return",
|
|
256
206
|
needsApproval: config.needsApproval?.(entry) ?? !readOnly
|
|
257
207
|
}).annotate(Tool.Title, operation.summary ?? operation.operationId ?? entry.name).annotate(Tool.Strict, strict).annotate(Tool.Readonly, readOnly).annotate(Tool.Destructive, entry.destructive).annotate(Tool.Idempotent, entry.idempotent).annotate(Tool.OpenWorld, false).annotate(Tool.Meta, {
|
|
@@ -261,29 +211,23 @@ ${guidance}` : guidance,
|
|
|
261
211
|
}
|
|
262
212
|
});
|
|
263
213
|
};
|
|
264
|
-
var makeHttpApiToolkit =
|
|
265
|
-
const spec = yield*
|
|
214
|
+
var makeHttpApiToolkit = Effect2.fn("HttpApiToolkit.make")(function* (config = {}) {
|
|
215
|
+
const spec = yield* HttpApiAdapter;
|
|
266
216
|
const entries = spec.operations.map((operation) => ({
|
|
267
217
|
operation,
|
|
268
218
|
tool: makeOperationTool(operation, config)
|
|
269
219
|
}));
|
|
270
220
|
const toolkit = Toolkit.make(...entries.map(({ tool }) => tool));
|
|
271
|
-
const handlers = toolkit.toLayer(
|
|
221
|
+
const handlers = toolkit.toLayer(Effect2.map(HttpApiAdapter, (client) => Object.fromEntries(entries.map(({ operation: entry, tool }) => [
|
|
272
222
|
tool.name,
|
|
273
|
-
(input) =>
|
|
274
|
-
const result = yield*
|
|
223
|
+
(input) => Effect2.gen(function* () {
|
|
224
|
+
const result = yield* client.execute({ operation: entry, input });
|
|
275
225
|
const encodedResult = yield* client.encodeResult(result, entry);
|
|
276
226
|
return config.transformResult?.(entry, encodedResult) ?? encodedResult;
|
|
277
|
-
}).pipe(
|
|
227
|
+
}).pipe(Effect2.mapError((error) => error instanceof Error ? error.message : String(error)))
|
|
278
228
|
]))));
|
|
279
229
|
return { toolkit, handlers };
|
|
280
230
|
});
|
|
281
|
-
var HttpApiToolkit = Effect3.fn("HttpApiToolkit")(function* (config) {
|
|
282
|
-
return (yield* makeHttpApiToolkit(config)).toolkit;
|
|
283
|
-
});
|
|
284
|
-
var HttpApiToolkitLayer = (config) => Layer3.unwrap(makeHttpApiToolkit(config).pipe(Effect3.map(({ handlers }) => handlers)));
|
|
285
231
|
export {
|
|
286
|
-
HttpApiToolkit,
|
|
287
|
-
HttpApiToolkitLayer,
|
|
288
232
|
makeHttpApiToolkit
|
|
289
233
|
};
|
package/dist/lib/query.d.ts
CHANGED
|
@@ -29,8 +29,8 @@ export declare const SortParams: Schema.declare<readonly {
|
|
|
29
29
|
readonly direction: "asc" | "desc";
|
|
30
30
|
}[]>;
|
|
31
31
|
export declare const Query: Schema.Struct<{
|
|
32
|
-
readonly page: Schema.decodeTo<Schema.Int, Schema.
|
|
33
|
-
readonly pageSize: Schema.decodeTo<Schema.Int, Schema.
|
|
32
|
+
readonly page: Schema.decodeTo<Schema.Int, Schema.optional<Schema.Union<readonly [Schema.Number, Schema.NumberFromString]>>, never, never>;
|
|
33
|
+
readonly pageSize: Schema.decodeTo<Schema.Int, Schema.optional<Schema.Union<readonly [Schema.Number, Schema.NumberFromString]>>, never, never>;
|
|
34
34
|
readonly globalFilter: Schema.optional<Schema.String>;
|
|
35
35
|
readonly sort: Schema.optional<Schema.declare<readonly {
|
|
36
36
|
readonly id: string;
|
|
@@ -42,7 +42,34 @@ export declare const Query: Schema.Struct<{
|
|
|
42
42
|
}>;
|
|
43
43
|
export type QueryType = typeof Query.Type;
|
|
44
44
|
export type QueryEncoded = typeof Query.Encoded;
|
|
45
|
-
export declare const QueryStandard:
|
|
45
|
+
export declare const QueryStandard: import("effect/StandardSchema").StandardSchemaV1<{
|
|
46
|
+
readonly globalFilter?: string | undefined;
|
|
47
|
+
readonly sort?: readonly {
|
|
48
|
+
readonly id: string;
|
|
49
|
+
readonly direction: "asc" | "desc";
|
|
50
|
+
}[] | undefined;
|
|
51
|
+
readonly page?: string | number | undefined;
|
|
52
|
+
readonly pageSize?: string | number | undefined;
|
|
53
|
+
}, {
|
|
54
|
+
readonly globalFilter?: string | undefined;
|
|
55
|
+
readonly sort?: readonly {
|
|
56
|
+
readonly id: string;
|
|
57
|
+
readonly direction: "asc" | "desc";
|
|
58
|
+
}[] | undefined;
|
|
59
|
+
readonly page?: number | undefined;
|
|
60
|
+
readonly pageSize?: number | undefined;
|
|
61
|
+
}> & Schema.Struct<{
|
|
62
|
+
readonly globalFilter: Schema.optional<Schema.String>;
|
|
63
|
+
readonly sort: Schema.optional<Schema.declare<readonly {
|
|
64
|
+
readonly id: string;
|
|
65
|
+
readonly direction: "asc" | "desc";
|
|
66
|
+
}[], readonly {
|
|
67
|
+
readonly id: string;
|
|
68
|
+
readonly direction: "asc" | "desc";
|
|
69
|
+
}[]>>;
|
|
70
|
+
readonly page: Schema.decodeTo<Schema.optional<Schema.Int>, Schema.decodeTo<Schema.Int, Schema.optional<Schema.Union<readonly [Schema.Number, Schema.NumberFromString]>>, never, never>, never, never>;
|
|
71
|
+
readonly pageSize: Schema.decodeTo<Schema.optional<Schema.Int>, Schema.decodeTo<Schema.Int, Schema.optional<Schema.Union<readonly [Schema.Number, Schema.NumberFromString]>>, never, never>, never, never>;
|
|
72
|
+
}>;
|
|
46
73
|
export declare const PaginationMeta: Schema.Struct<{
|
|
47
74
|
readonly page: Schema.Int;
|
|
48
75
|
readonly pageSize: Schema.Int;
|
package/dist/lib/query.js
CHANGED
|
@@ -81,11 +81,11 @@ var SortParams = Schema.declare(Schema.is(SortParamsArray), {
|
|
|
81
81
|
toCodecStringTree: () => Schema.link()(SortParamsFromString, SchemaTransformation.passthrough())
|
|
82
82
|
});
|
|
83
83
|
var Query = Schema.Struct({
|
|
84
|
-
page: Schema.
|
|
84
|
+
page: Schema.optional(Schema.Union([Schema.Number, Schema.NumberFromString]).check(Schema.isInt(), Schema.isGreaterThanOrEqualTo(0))).pipe(Schema.decodeTo(Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)), {
|
|
85
85
|
decode: SchemaGetter.withDefault(Effect.succeed(0)),
|
|
86
86
|
encode: SchemaGetter.transformOptional(Option.filter((page) => page !== 0))
|
|
87
87
|
})),
|
|
88
|
-
pageSize: Schema.
|
|
88
|
+
pageSize: Schema.optional(Schema.Union([Schema.Number, Schema.NumberFromString]).check(Schema.isInt(), Schema.isBetween({ minimum: 1, maximum: 100 }))).pipe(Schema.decodeTo(Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: 100 })), {
|
|
89
89
|
decode: SchemaGetter.withDefault(Effect.succeed(20)),
|
|
90
90
|
encode: SchemaGetter.transformOptional(Option.filter((pageSize) => pageSize !== 20))
|
|
91
91
|
})),
|
|
@@ -107,7 +107,17 @@ var Query = Schema.Struct({
|
|
|
107
107
|
}
|
|
108
108
|
]
|
|
109
109
|
});
|
|
110
|
-
var QueryStandard = Schema.toStandardSchemaV1(
|
|
110
|
+
var QueryStandard = Schema.toStandardSchemaV1(Schema.Struct({
|
|
111
|
+
...Query.fields,
|
|
112
|
+
page: Query.fields.page.pipe(Schema.decodeTo(Schema.optional(Schema.Int), {
|
|
113
|
+
decode: SchemaGetter.transform((page) => page === 0 ? undefined : page),
|
|
114
|
+
encode: SchemaGetter.transform((page) => page ?? 0)
|
|
115
|
+
})),
|
|
116
|
+
pageSize: Query.fields.pageSize.pipe(Schema.decodeTo(Schema.optional(Schema.Int), {
|
|
117
|
+
decode: SchemaGetter.transform((pageSize) => pageSize === 20 ? undefined : pageSize),
|
|
118
|
+
encode: SchemaGetter.transform((pageSize) => pageSize ?? 20)
|
|
119
|
+
}))
|
|
120
|
+
}).annotate({ identifier: "QuerySearch" }));
|
|
111
121
|
var PaginationMeta = Schema.Struct({
|
|
112
122
|
page: Schema.Int,
|
|
113
123
|
pageSize: Schema.Int,
|
|
@@ -42,4 +42,4 @@ export declare const WebFetchToolkit: Toolkit.Toolkit<{
|
|
|
42
42
|
readonly failureMode: "return";
|
|
43
43
|
}, never>;
|
|
44
44
|
}>;
|
|
45
|
-
export declare const WebFetchToolkitLayer: (options?: Partial<WebFetchToolkitOptions>) => import("effect/Layer").Layer<Tool.Handler<"webFetch">,
|
|
45
|
+
export declare const WebFetchToolkitLayer: (options?: Partial<WebFetchToolkitOptions>) => import("effect/Layer").Layer<Tool.Handler<"webFetch">, Schema.SchemaError, FileExtractionService | HttpClient.HttpClient>;
|
|
@@ -127,7 +127,7 @@ var defaultWebFetchToolkitOptions = {
|
|
|
127
127
|
maxResponseBytes: 2 * 1024 * 1024,
|
|
128
128
|
maxContentCharacters: 30000
|
|
129
129
|
};
|
|
130
|
-
var decodeOptions = Schema2.
|
|
130
|
+
var decodeOptions = Schema2.decodeUnknownEffect(WebFetchToolkitOptions);
|
|
131
131
|
var isPublicHttpsUrl = Schema2.makeFilter((value) => {
|
|
132
132
|
try {
|
|
133
133
|
const url = new URL(value);
|
|
@@ -213,60 +213,58 @@ var WebFetchTool = Tool.make("webFetch", {
|
|
|
213
213
|
failureMode: "return"
|
|
214
214
|
}).annotate(Tool.Title, "Read web page").annotate(Tool.Readonly, true).annotate(Tool.Destructive, false).annotate(Tool.Idempotent, true).annotate(Tool.OpenWorld, true);
|
|
215
215
|
var WebFetchToolkit = Toolkit.make(WebFetchTool);
|
|
216
|
-
var WebFetchToolkitLayer = (options = {}) => {
|
|
217
|
-
const resolved = decodeOptions({
|
|
216
|
+
var WebFetchToolkitLayer = (options = {}) => WebFetchToolkit.toLayer(Effect2.gen(function* () {
|
|
217
|
+
const resolved = yield* decodeOptions({
|
|
218
218
|
...defaultWebFetchToolkitOptions,
|
|
219
219
|
...options
|
|
220
220
|
});
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
if (Option.isNone(decodedContentLength) || decodedContentLength.value > resolved.maxResponseBytes) {
|
|
240
|
-
return yield* Effect2.fail(failure("too-large", "The web page exceeds the response size limit"));
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
const mediaType = response.headers["content-type"]?.split(";", 1)[0]?.trim().toLowerCase();
|
|
244
|
-
const decodedMediaType = decodeMediaType(mediaType);
|
|
245
|
-
if (Option.isNone(decodedMediaType)) {
|
|
246
|
-
return yield* Effect2.fail(failure("unsupported-content", "The web page has an unsupported content type"));
|
|
247
|
-
}
|
|
248
|
-
const arrayBuffer = yield* response.arrayBuffer.pipe(Effect2.mapError(() => failure("invalid-response", "The web page body could not be read")));
|
|
249
|
-
if (arrayBuffer.byteLength > resolved.maxResponseBytes) {
|
|
221
|
+
const http = yield* HttpClient.HttpClient;
|
|
222
|
+
const extraction = yield* FileExtractionService;
|
|
223
|
+
return WebFetchToolkit.of({
|
|
224
|
+
webFetch: Effect2.fn("WebFetchToolkit.webFetch")(function* ({
|
|
225
|
+
url
|
|
226
|
+
}) {
|
|
227
|
+
const request = HttpClientRequest.get(url).pipe(HttpClientRequest.setHeaders({
|
|
228
|
+
accept: "text/markdown, text/plain;q=0.9, text/html;q=0.8, application/xhtml+xml;q=0.7, application/pdf;q=0.6, application/json;q=0.5",
|
|
229
|
+
"user-agent": "krak-stack-webfetch/1.0"
|
|
230
|
+
}));
|
|
231
|
+
const response = yield* HttpClient.filterStatusOk(http).execute(request).pipe(Effect2.mapError(() => failure("unavailable", "The web page could not be fetched")), Effect2.timeoutOrElse({
|
|
232
|
+
duration: "20 seconds",
|
|
233
|
+
orElse: () => Effect2.fail(failure("unavailable", "The web page request timed out"))
|
|
234
|
+
}));
|
|
235
|
+
const contentLength = response.headers["content-length"];
|
|
236
|
+
if (contentLength !== undefined) {
|
|
237
|
+
const decodedContentLength = decodeContentLength(contentLength);
|
|
238
|
+
if (Option.isNone(decodedContentLength) || decodedContentLength.value > resolved.maxResponseBytes) {
|
|
250
239
|
return yield* Effect2.fail(failure("too-large", "The web page exceeds the response size limit"));
|
|
251
240
|
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
return
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
241
|
+
}
|
|
242
|
+
const mediaType = response.headers["content-type"]?.split(";", 1)[0]?.trim().toLowerCase();
|
|
243
|
+
const decodedMediaType = decodeMediaType(mediaType);
|
|
244
|
+
if (Option.isNone(decodedMediaType)) {
|
|
245
|
+
return yield* Effect2.fail(failure("unsupported-content", "The web page has an unsupported content type"));
|
|
246
|
+
}
|
|
247
|
+
const arrayBuffer = yield* response.arrayBuffer.pipe(Effect2.mapError(() => failure("invalid-response", "The web page body could not be read")));
|
|
248
|
+
if (arrayBuffer.byteLength > resolved.maxResponseBytes) {
|
|
249
|
+
return yield* Effect2.fail(failure("too-large", "The web page exceeds the response size limit"));
|
|
250
|
+
}
|
|
251
|
+
const parsedUrl = new URL(url);
|
|
252
|
+
const filename = parsedUrl.pathname.split("/").at(-1) || "page.html";
|
|
253
|
+
const extracted = yield* extraction.markdown({
|
|
254
|
+
bytes: new Uint8Array(arrayBuffer),
|
|
255
|
+
filename,
|
|
256
|
+
mimeType: decodedMediaType.value
|
|
257
|
+
}).pipe(Effect2.mapError(() => failure("invalid-response", "The web page did not contain readable content")));
|
|
258
|
+
const bounded = truncateContent(extracted.content, resolved.maxContentCharacters);
|
|
259
|
+
return {
|
|
260
|
+
url,
|
|
261
|
+
contentType: decodedMediaType.value,
|
|
262
|
+
content: bounded.content,
|
|
263
|
+
truncated: extracted.truncated || bounded.truncated
|
|
264
|
+
};
|
|
265
|
+
})
|
|
266
|
+
});
|
|
267
|
+
}));
|
|
270
268
|
export {
|
|
271
269
|
WebFetchFailure,
|
|
272
270
|
WebFetchRequest,
|
|
@@ -349,8 +349,8 @@ function classifyWideningTarget(type, environment) {
|
|
|
349
349
|
if (alias === undefined)
|
|
350
350
|
return null;
|
|
351
351
|
if ((alias.typeParameters?.params.length ?? 0) > 0) {
|
|
352
|
-
const
|
|
353
|
-
return
|
|
352
|
+
const substitutions = aliasSubstitution(alias, unwrapped, new Map);
|
|
353
|
+
return substitutions !== null && resolvesToDictionary(alias.typeAnnotation, environment, substitutions, new Set([name])) ? { kind: "generic container" } : null;
|
|
354
354
|
}
|
|
355
355
|
const substitutions = aliasSubstitution(alias, unwrapped, new Map);
|
|
356
356
|
if (substitutions === null)
|