@executor-js/plugin-graphql 0.0.1 → 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.
@@ -1,916 +0,0 @@
1
- // src/sdk/errors.ts
2
- import { Data, Schema } from "effect";
3
- var GraphqlIntrospectionError = class extends Schema.TaggedError()(
4
- "GraphqlIntrospectionError",
5
- {
6
- message: Schema.String
7
- }
8
- ) {
9
- };
10
- var GraphqlExtractionError = class extends Schema.TaggedError()(
11
- "GraphqlExtractionError",
12
- {
13
- message: Schema.String
14
- }
15
- ) {
16
- };
17
- var GraphqlInvocationError = class extends Data.TaggedError("GraphqlInvocationError") {
18
- };
19
-
20
- // src/sdk/introspect.ts
21
- import { Effect } from "effect";
22
- import { HttpClient, HttpClientRequest } from "@effect/platform";
23
- var INTROSPECTION_QUERY = `
24
- query IntrospectionQuery {
25
- __schema {
26
- queryType { name }
27
- mutationType { name }
28
- types {
29
- kind
30
- name
31
- description
32
- fields(includeDeprecated: false) {
33
- name
34
- description
35
- args {
36
- name
37
- description
38
- type {
39
- ...TypeRef
40
- }
41
- defaultValue
42
- }
43
- type {
44
- ...TypeRef
45
- }
46
- }
47
- inputFields {
48
- name
49
- description
50
- type {
51
- ...TypeRef
52
- }
53
- defaultValue
54
- }
55
- enumValues(includeDeprecated: false) {
56
- name
57
- description
58
- }
59
- }
60
- }
61
- }
62
-
63
- fragment TypeRef on __Type {
64
- kind
65
- name
66
- ofType {
67
- kind
68
- name
69
- ofType {
70
- kind
71
- name
72
- ofType {
73
- kind
74
- name
75
- ofType {
76
- kind
77
- name
78
- ofType {
79
- kind
80
- name
81
- ofType {
82
- kind
83
- name
84
- }
85
- }
86
- }
87
- }
88
- }
89
- }
90
- }
91
- `;
92
- var introspect = Effect.fn("GraphQL.introspect")(function* (endpoint, headers) {
93
- const client = yield* HttpClient.HttpClient;
94
- let request = HttpClientRequest.post(endpoint).pipe(
95
- HttpClientRequest.setHeader("Content-Type", "application/json"),
96
- HttpClientRequest.bodyUnsafeJson({
97
- query: INTROSPECTION_QUERY
98
- })
99
- );
100
- if (headers) {
101
- for (const [k, v] of Object.entries(headers)) {
102
- request = HttpClientRequest.setHeader(request, k, v);
103
- }
104
- }
105
- const response = yield* client.execute(request).pipe(
106
- Effect.tapErrorCause((cause) => Effect.logError("graphql introspection request failed", cause)),
107
- Effect.mapError(
108
- (err) => new GraphqlIntrospectionError({
109
- message: `Failed to reach GraphQL endpoint: ${err.message}`
110
- })
111
- )
112
- );
113
- if (response.status !== 200) {
114
- return yield* new GraphqlIntrospectionError({
115
- message: `Introspection failed with status ${response.status}`
116
- });
117
- }
118
- const raw = yield* response.json.pipe(
119
- Effect.tapErrorCause(
120
- (cause) => Effect.logError("graphql introspection JSON parse failed", cause)
121
- ),
122
- Effect.mapError(
123
- () => new GraphqlIntrospectionError({
124
- message: `Failed to parse introspection response as JSON`
125
- })
126
- )
127
- );
128
- const json = raw;
129
- if (json.errors && Array.isArray(json.errors) && json.errors.length > 0) {
130
- return yield* new GraphqlIntrospectionError({
131
- message: `Introspection returned ${json.errors.length} error(s)`
132
- });
133
- }
134
- if (!json.data?.__schema) {
135
- return yield* new GraphqlIntrospectionError({
136
- message: "Introspection response missing __schema"
137
- });
138
- }
139
- return json.data;
140
- });
141
- var parseIntrospectionJson = (text) => Effect.try({
142
- try: () => {
143
- const parsed = JSON.parse(text);
144
- const result = parsed.data ?? parsed;
145
- if (!result.__schema) {
146
- throw new Error("Missing __schema in introspection JSON");
147
- }
148
- return result;
149
- },
150
- catch: (err) => new GraphqlIntrospectionError({
151
- message: `Failed to parse introspection JSON: ${err instanceof Error ? err.message : String(err)}`
152
- })
153
- });
154
-
155
- // src/sdk/types.ts
156
- import { Schema as Schema2 } from "effect";
157
- var GraphqlOperationKind = Schema2.Literal("query", "mutation");
158
- var GraphqlArgument = class extends Schema2.Class("GraphqlArgument")({
159
- name: Schema2.String,
160
- typeName: Schema2.String,
161
- required: Schema2.Boolean,
162
- description: Schema2.optionalWith(Schema2.String, { as: "Option" })
163
- }) {
164
- };
165
- var ExtractedField = class extends Schema2.Class("ExtractedField")({
166
- /** e.g. "user", "createUser" */
167
- fieldName: Schema2.String,
168
- /** "query" or "mutation" */
169
- kind: GraphqlOperationKind,
170
- description: Schema2.optionalWith(Schema2.String, { as: "Option" }),
171
- arguments: Schema2.Array(GraphqlArgument),
172
- /** JSON Schema for the input (built from arguments) */
173
- inputSchema: Schema2.optionalWith(Schema2.Unknown, { as: "Option" }),
174
- /** The return type name for documentation */
175
- returnTypeName: Schema2.String
176
- }) {
177
- };
178
- var ExtractionResult = class extends Schema2.Class("ExtractionResult")({
179
- /** Schema name from introspection */
180
- schemaName: Schema2.optionalWith(Schema2.String, { as: "Option" }),
181
- fields: Schema2.Array(ExtractedField)
182
- }) {
183
- };
184
- var OperationBinding = class extends Schema2.Class("OperationBinding")({
185
- kind: GraphqlOperationKind,
186
- fieldName: Schema2.String,
187
- /** The full GraphQL query/mutation string */
188
- operationString: Schema2.String,
189
- /** Ordered variable names for mapping */
190
- variableNames: Schema2.Array(Schema2.String)
191
- }) {
192
- };
193
- var HeaderValue = Schema2.Union(
194
- Schema2.String,
195
- Schema2.Struct({
196
- secretId: Schema2.String,
197
- prefix: Schema2.optional(Schema2.String)
198
- })
199
- );
200
- var InvocationConfig = class extends Schema2.Class("InvocationConfig")({
201
- /** The GraphQL endpoint URL */
202
- endpoint: Schema2.String,
203
- /** Headers applied to every request. Values can reference secrets. */
204
- headers: Schema2.optionalWith(Schema2.Record({ key: Schema2.String, value: HeaderValue }), {
205
- default: () => ({})
206
- })
207
- }) {
208
- };
209
- var InvocationResult = class extends Schema2.Class("InvocationResult")({
210
- status: Schema2.Number,
211
- data: Schema2.NullOr(Schema2.Unknown),
212
- errors: Schema2.NullOr(Schema2.Unknown)
213
- }) {
214
- };
215
-
216
- // src/sdk/extract.ts
217
- import { Effect as Effect2, Option } from "effect";
218
- var unwrapTypeName = (ref) => {
219
- if (ref.name) return ref.name;
220
- if (ref.ofType) return unwrapTypeName(ref.ofType);
221
- return "Unknown";
222
- };
223
- var isNonNull = (ref) => ref.kind === "NON_NULL";
224
- var buildDefinitions = (types) => {
225
- const defs = {};
226
- for (const [name, type] of types) {
227
- if (name.startsWith("__")) continue;
228
- if (type.kind === "INPUT_OBJECT" && type.inputFields) {
229
- const properties = {};
230
- const required = [];
231
- for (const field of type.inputFields) {
232
- const schema = typeRefToJsonSchema(field.type, types);
233
- if (field.description) {
234
- schema.description = field.description;
235
- }
236
- properties[field.name] = schema;
237
- if (isNonNull(field.type)) {
238
- required.push(field.name);
239
- }
240
- }
241
- const def = { type: "object", properties };
242
- if (required.length > 0) def.required = required;
243
- if (type.description) def.description = type.description;
244
- defs[name] = def;
245
- }
246
- if (type.kind === "ENUM" && type.enumValues) {
247
- defs[name] = {
248
- type: "string",
249
- enum: type.enumValues.map((v) => v.name),
250
- ...type.description ? { description: type.description } : {}
251
- };
252
- }
253
- }
254
- return defs;
255
- };
256
- var typeRefToJsonSchema = (ref, types) => {
257
- switch (ref.kind) {
258
- case "NON_NULL":
259
- return ref.ofType ? typeRefToJsonSchema(ref.ofType, types) : {};
260
- case "LIST":
261
- return {
262
- type: "array",
263
- items: ref.ofType ? typeRefToJsonSchema(ref.ofType, types) : {}
264
- };
265
- case "SCALAR":
266
- return scalarToJsonSchema(ref.name ?? "String");
267
- case "ENUM":
268
- return ref.name ? { $ref: `#/$defs/${ref.name}` } : { type: "string" };
269
- case "INPUT_OBJECT":
270
- return ref.name ? { $ref: `#/$defs/${ref.name}` } : { type: "object" };
271
- case "OBJECT":
272
- case "INTERFACE":
273
- case "UNION":
274
- return { type: "object" };
275
- default:
276
- return {};
277
- }
278
- };
279
- var scalarToJsonSchema = (name) => {
280
- switch (name) {
281
- case "String":
282
- case "ID":
283
- return { type: "string" };
284
- case "Int":
285
- return { type: "integer" };
286
- case "Float":
287
- return { type: "number" };
288
- case "Boolean":
289
- return { type: "boolean" };
290
- default:
291
- return { type: "string", description: `Custom scalar: ${name}` };
292
- }
293
- };
294
- var buildInputSchema = (args, types) => {
295
- if (args.length === 0) return void 0;
296
- const properties = {};
297
- const required = [];
298
- for (const arg of args) {
299
- const schema = typeRefToJsonSchema(arg.type, types);
300
- if (arg.description) {
301
- schema.description = arg.description;
302
- }
303
- properties[arg.name] = schema;
304
- if (isNonNull(arg.type)) {
305
- required.push(arg.name);
306
- }
307
- }
308
- const inputSchema = {
309
- type: "object",
310
- properties
311
- };
312
- if (required.length > 0) inputSchema.required = required;
313
- return inputSchema;
314
- };
315
- var formatTypeRef = (ref) => {
316
- switch (ref.kind) {
317
- case "NON_NULL":
318
- return ref.ofType ? `${formatTypeRef(ref.ofType)}!` : "Unknown!";
319
- case "LIST":
320
- return ref.ofType ? `[${formatTypeRef(ref.ofType)}]` : "[Unknown]";
321
- default:
322
- return ref.name ?? "Unknown";
323
- }
324
- };
325
- var extractFields = (_schema, kind, typeName, types) => {
326
- if (!typeName) return [];
327
- const type = types.get(typeName);
328
- if (!type?.fields) return [];
329
- return type.fields.filter((f) => !f.name.startsWith("__")).map((field) => {
330
- const args = field.args.map(
331
- (arg) => new GraphqlArgument({
332
- name: arg.name,
333
- typeName: formatTypeRef(arg.type),
334
- required: isNonNull(arg.type),
335
- description: arg.description ? Option.some(arg.description) : Option.none()
336
- })
337
- );
338
- const inputSchema = buildInputSchema(field.args, types);
339
- return new ExtractedField({
340
- fieldName: field.name,
341
- kind,
342
- description: field.description ? Option.some(field.description) : Option.none(),
343
- arguments: args,
344
- inputSchema: inputSchema ? Option.some(inputSchema) : Option.none(),
345
- returnTypeName: unwrapTypeName(field.type)
346
- });
347
- });
348
- };
349
- var extract = (introspection) => Effect2.try({
350
- try: () => {
351
- const schema = introspection.__schema;
352
- const typeMap = /* @__PURE__ */ new Map();
353
- for (const t of schema.types) {
354
- typeMap.set(t.name, t);
355
- }
356
- const definitions = buildDefinitions(typeMap);
357
- const queryFields = extractFields(schema, "query", schema.queryType?.name, typeMap);
358
- const mutationFields = extractFields(schema, "mutation", schema.mutationType?.name, typeMap);
359
- return {
360
- result: new ExtractionResult({
361
- schemaName: Option.none(),
362
- fields: [...queryFields, ...mutationFields]
363
- }),
364
- definitions
365
- };
366
- },
367
- catch: (err) => new GraphqlExtractionError({
368
- message: `Failed to extract GraphQL schema: ${err instanceof Error ? err.message : String(err)}`
369
- })
370
- });
371
-
372
- // src/sdk/invoke.ts
373
- import { Effect as Effect3, Option as Option2 } from "effect";
374
- import { HttpClient as HttpClient2, HttpClientRequest as HttpClientRequest2 } from "@effect/platform";
375
- import {
376
- ToolInvocationResult,
377
- ToolInvocationError
378
- } from "@executor-js/sdk";
379
- var resolveHeaders = (headers, secrets, scopeId) => Effect3.gen(function* () {
380
- const resolved = {};
381
- for (const [name, value] of Object.entries(headers)) {
382
- if (typeof value === "string") {
383
- resolved[name] = value;
384
- } else {
385
- const secret = yield* secrets.resolve(value.secretId, scopeId).pipe(
386
- Effect3.mapError(
387
- () => new ToolInvocationError({
388
- toolId: "",
389
- message: `Failed to resolve secret "${value.secretId}" for header "${name}"`,
390
- cause: void 0
391
- })
392
- )
393
- );
394
- resolved[name] = value.prefix ? `${value.prefix}${secret}` : secret;
395
- }
396
- }
397
- return resolved;
398
- });
399
- var isJsonContentType = (ct) => {
400
- if (!ct) return false;
401
- const normalized = ct.split(";")[0]?.trim().toLowerCase() ?? "";
402
- return normalized === "application/json" || normalized.includes("+json") || normalized.includes("json");
403
- };
404
- var invoke = Effect3.fn("GraphQL.invoke")(function* (operation, args, config, resolvedHeaders) {
405
- const client = yield* HttpClient2.HttpClient;
406
- const variables = {};
407
- for (const varName of operation.variableNames) {
408
- if (args[varName] !== void 0) {
409
- variables[varName] = args[varName];
410
- }
411
- }
412
- if (typeof args.variables === "object" && args.variables !== null) {
413
- Object.assign(variables, args.variables);
414
- }
415
- let request = HttpClientRequest2.post(config.endpoint).pipe(
416
- HttpClientRequest2.setHeader("Content-Type", "application/json"),
417
- HttpClientRequest2.bodyUnsafeJson({
418
- query: operation.operationString,
419
- variables: Object.keys(variables).length > 0 ? variables : void 0
420
- })
421
- );
422
- if (resolvedHeaders) {
423
- for (const [name, value] of Object.entries(resolvedHeaders)) {
424
- request = HttpClientRequest2.setHeader(request, name, value);
425
- }
426
- }
427
- const response = yield* client.execute(request).pipe(
428
- Effect3.mapError(
429
- (err) => new GraphqlInvocationError({
430
- message: `GraphQL request failed: ${err.message}`,
431
- statusCode: Option2.none(),
432
- cause: err
433
- })
434
- )
435
- );
436
- const status = response.status;
437
- const contentType = response.headers["content-type"] ?? null;
438
- const body = isJsonContentType(contentType) ? yield* response.json.pipe(Effect3.catchAll(() => response.text)) : yield* response.text;
439
- const gqlBody = body;
440
- const hasErrors = Array.isArray(gqlBody?.errors) && gqlBody.errors.length > 0;
441
- return new InvocationResult({
442
- status,
443
- data: gqlBody?.data ?? null,
444
- errors: hasErrors ? gqlBody.errors : null
445
- });
446
- });
447
- var makeGraphqlInvoker = (opts) => ({
448
- resolveAnnotations: (toolId) => Effect3.gen(function* () {
449
- const entry = yield* opts.operationStore.get(toolId);
450
- if (!entry) return void 0;
451
- if (entry.binding.kind === "mutation") {
452
- return {
453
- requiresApproval: true,
454
- approvalDescription: `mutation ${entry.binding.fieldName}`
455
- };
456
- }
457
- return {};
458
- }),
459
- invoke: (toolId, args) => Effect3.gen(function* () {
460
- const entry = yield* opts.operationStore.get(toolId);
461
- if (!entry) {
462
- return yield* new ToolInvocationError({
463
- toolId,
464
- message: `No GraphQL operation found for tool "${toolId}"`,
465
- cause: void 0
466
- });
467
- }
468
- const source = yield* opts.operationStore.getSource(entry.namespace);
469
- if (!source) {
470
- return yield* new ToolInvocationError({
471
- toolId,
472
- message: `No source found for namespace "${entry.namespace}"`,
473
- cause: void 0
474
- });
475
- }
476
- const { binding } = entry;
477
- const { invocationConfig: config } = source;
478
- const resolvedHeaders = yield* resolveHeaders(config.headers, opts.secrets, opts.scopeId);
479
- const result = yield* invoke(
480
- binding,
481
- args ?? {},
482
- config,
483
- resolvedHeaders
484
- ).pipe(Effect3.provide(opts.httpClientLayer));
485
- return new ToolInvocationResult({
486
- data: result.data,
487
- error: result.errors,
488
- status: result.status
489
- });
490
- }).pipe(
491
- Effect3.catchAll((err) => {
492
- if (typeof err === "object" && err !== null && "_tag" in err && err._tag === "ToolInvocationError") {
493
- return Effect3.fail(err);
494
- }
495
- return Effect3.fail(
496
- new ToolInvocationError({
497
- toolId,
498
- message: `GraphQL invocation failed: ${err instanceof Error ? err.message : String(err)}`,
499
- cause: err
500
- })
501
- );
502
- })
503
- )
504
- });
505
-
506
- // src/sdk/kv-operation-store.ts
507
- import { Effect as Effect4, Schema as Schema4 } from "effect";
508
- import { scopeKv, makeInMemoryScopedKv } from "@executor-js/sdk";
509
-
510
- // src/sdk/stored-source.ts
511
- import { Schema as Schema3 } from "effect";
512
- var StoredSourceSchema = class extends Schema3.Class("GraphqlStoredSource")({
513
- namespace: Schema3.String,
514
- name: Schema3.String,
515
- config: Schema3.Struct({
516
- endpoint: Schema3.String,
517
- introspectionJson: Schema3.optional(Schema3.String),
518
- namespace: Schema3.optional(Schema3.String),
519
- headers: Schema3.optional(Schema3.Record({ key: Schema3.String, value: HeaderValue }))
520
- }),
521
- // TODO(migration): make required once all rows have been migrated to
522
- // carry invocationConfig. Left optional for decode compat with rows
523
- // written before the source-level invocationConfig refactor.
524
- invocationConfig: Schema3.optional(InvocationConfig)
525
- }) {
526
- };
527
-
528
- // src/sdk/kv-operation-store.ts
529
- var StoredEntry = class extends Schema4.Class("StoredEntry")({
530
- namespace: Schema4.String,
531
- binding: OperationBinding
532
- }) {
533
- };
534
- var encodeEntry = Schema4.encodeSync(Schema4.parseJson(StoredEntry));
535
- var decodeEntry = Schema4.decodeUnknownSync(Schema4.parseJson(StoredEntry));
536
- var encodeSource = Schema4.encodeSync(Schema4.parseJson(StoredSourceSchema));
537
- var decodeSource = Schema4.decodeUnknownSync(Schema4.parseJson(StoredSourceSchema));
538
- var rehydrate = (source) => source.invocationConfig ? source : {
539
- ...source,
540
- invocationConfig: new InvocationConfig({
541
- endpoint: source.config.endpoint,
542
- headers: source.config.headers ?? {}
543
- })
544
- };
545
- var makeStore = (bindings, sources) => ({
546
- get: (toolId) => Effect4.gen(function* () {
547
- const raw = yield* bindings.get(toolId);
548
- if (!raw) return null;
549
- const entry = decodeEntry(raw);
550
- return { binding: entry.binding, namespace: entry.namespace };
551
- }),
552
- put: (toolId, namespace, binding) => bindings.set([
553
- { key: toolId, value: encodeEntry(new StoredEntry({ namespace, binding })) }
554
- ]),
555
- remove: (toolId) => bindings.delete([toolId]).pipe(Effect4.asVoid),
556
- listByNamespace: (namespace) => Effect4.gen(function* () {
557
- const entries = yield* bindings.list();
558
- const ids = [];
559
- for (const e of entries) {
560
- const entry = decodeEntry(e.value);
561
- if (entry.namespace === namespace) ids.push(e.key);
562
- }
563
- return ids;
564
- }),
565
- removeByNamespace: (namespace) => Effect4.gen(function* () {
566
- const entries = yield* bindings.list();
567
- const ids = [];
568
- for (const e of entries) {
569
- const entry = decodeEntry(e.value);
570
- if (entry.namespace === namespace) ids.push(e.key);
571
- }
572
- if (ids.length > 0) yield* bindings.delete(ids);
573
- return ids;
574
- }),
575
- putSource: (source) => sources.set([{ key: source.namespace, value: encodeSource(source) }]),
576
- removeSource: (namespace) => sources.delete([namespace]).pipe(Effect4.asVoid),
577
- listSources: () => Effect4.gen(function* () {
578
- const entries = yield* sources.list();
579
- return entries.map((e) => rehydrate(decodeSource(e.value)));
580
- }),
581
- getSource: (namespace) => Effect4.gen(function* () {
582
- const raw = yield* sources.get(namespace);
583
- if (!raw) return null;
584
- const source = decodeSource(raw);
585
- if (source.invocationConfig) return source;
586
- const healed = rehydrate(source);
587
- yield* sources.set([{ key: namespace, value: encodeSource(healed) }]);
588
- return healed;
589
- }),
590
- getSourceConfig: (namespace) => Effect4.gen(function* () {
591
- const raw = yield* sources.get(namespace);
592
- if (!raw) return null;
593
- const source = decodeSource(raw);
594
- return source.config;
595
- })
596
- });
597
- var makeKvOperationStore = (kv, namespace) => makeStore(scopeKv(kv, `${namespace}.bindings`), scopeKv(kv, `${namespace}.sources`));
598
- var makeInMemoryOperationStore = () => makeStore(makeInMemoryScopedKv(), makeInMemoryScopedKv());
599
-
600
- // src/sdk/plugin.ts
601
- import { Effect as Effect5, Option as Option3, Schema as Schema5 } from "effect";
602
- import { FetchHttpClient } from "@effect/platform";
603
- import {
604
- Source,
605
- SourceDetectionResult,
606
- definePlugin,
607
- registerRuntimeTools,
608
- runtimeTool,
609
- ToolId
610
- } from "@executor-js/sdk";
611
- var AddSourceInputSchema = Schema5.Struct({
612
- endpoint: Schema5.String,
613
- name: Schema5.optional(Schema5.String),
614
- introspectionJson: Schema5.optional(Schema5.String),
615
- namespace: Schema5.optional(Schema5.String),
616
- headers: Schema5.optional(Schema5.Record({ key: Schema5.String, value: HeaderValue }))
617
- });
618
- var AddSourceOutputSchema = Schema5.Struct({
619
- sourceId: Schema5.String,
620
- toolCount: Schema5.Number
621
- });
622
- var namespaceFromEndpoint = (endpoint) => {
623
- try {
624
- const url = new URL(endpoint);
625
- return url.hostname.replace(/[^a-z0-9]+/gi, "_").toLowerCase();
626
- } catch {
627
- return "graphql";
628
- }
629
- };
630
- var buildOperationStringForField = (kind, field, types) => {
631
- const opType = kind === "query" ? "query" : "mutation";
632
- const varDefs = field.args.map((arg) => {
633
- const typeName = formatTypeRef2(arg.type);
634
- return `$${arg.name}: ${typeName}`;
635
- });
636
- const argPasses = field.args.map((arg) => `${arg.name}: $${arg.name}`);
637
- const selectionSet = buildSelectionSet(field.type, types, 0, /* @__PURE__ */ new Set());
638
- const varDefsStr = varDefs.length > 0 ? `(${varDefs.join(", ")})` : "";
639
- const argPassStr = argPasses.length > 0 ? `(${argPasses.join(", ")})` : "";
640
- return `${opType}${varDefsStr} { ${field.name}${argPassStr}${selectionSet ? ` ${selectionSet}` : ""} }`;
641
- };
642
- var formatTypeRef2 = (ref) => {
643
- switch (ref.kind) {
644
- case "NON_NULL":
645
- return ref.ofType ? `${formatTypeRef2(ref.ofType)}!` : "Unknown!";
646
- case "LIST":
647
- return ref.ofType ? `[${formatTypeRef2(ref.ofType)}]` : "[Unknown]";
648
- default:
649
- return ref.name ?? "Unknown";
650
- }
651
- };
652
- var unwrapTypeName2 = (ref) => {
653
- if (ref.name) return ref.name;
654
- if (ref.ofType) return unwrapTypeName2(ref.ofType);
655
- return "Unknown";
656
- };
657
- var buildSelectionSet = (ref, types, depth, seen) => {
658
- if (depth > 2) return "";
659
- const leafName = unwrapTypeName2(ref);
660
- if (seen.has(leafName)) return "";
661
- const objectType = types.get(leafName);
662
- if (!objectType?.fields) return "";
663
- const kind = objectType.kind;
664
- if (kind === "SCALAR" || kind === "ENUM") return "";
665
- seen.add(leafName);
666
- const subFields = objectType.fields.filter((f) => !f.name.startsWith("__")).slice(0, 12).map((f) => {
667
- const sub = buildSelectionSet(f.type, types, depth + 1, seen);
668
- return sub ? `${f.name} ${sub}` : f.name;
669
- });
670
- seen.delete(leafName);
671
- return subFields.length > 0 ? `{ ${subFields.join(" ")} }` : "";
672
- };
673
- var toRegistration = (field, namespace) => {
674
- const prefix = field.kind === "mutation" ? "mutation" : "query";
675
- const toolPath = `${prefix}.${field.fieldName}`;
676
- const description = Option3.getOrElse(
677
- field.description,
678
- () => `GraphQL ${field.kind}: ${field.fieldName} -> ${field.returnTypeName}`
679
- );
680
- return {
681
- id: ToolId.make(`${namespace}.${toolPath}`),
682
- pluginKey: "graphql",
683
- sourceId: namespace,
684
- name: toolPath,
685
- description,
686
- inputSchema: Option3.getOrUndefined(field.inputSchema),
687
- outputSchema: void 0
688
- };
689
- };
690
- var graphqlPlugin = (options) => {
691
- const httpClientLayer = options?.httpClientLayer ?? FetchHttpClient.layer;
692
- const operationStore = options?.operationStore ?? makeInMemoryOperationStore();
693
- return definePlugin({
694
- key: "graphql",
695
- init: (ctx) => Effect5.gen(function* () {
696
- yield* ctx.tools.registerInvoker(
697
- "graphql",
698
- makeGraphqlInvoker({
699
- operationStore,
700
- httpClientLayer,
701
- secrets: ctx.secrets,
702
- scopeId: ctx.scope.id
703
- })
704
- );
705
- yield* ctx.sources.addManager({
706
- kind: "graphql",
707
- list: () => operationStore.listSources().pipe(
708
- Effect5.map(
709
- (metas) => metas.map(
710
- (s) => new Source({
711
- id: s.namespace,
712
- name: s.name,
713
- kind: "graphql",
714
- url: s.config.endpoint,
715
- runtime: false,
716
- canRemove: true,
717
- canRefresh: false,
718
- canEdit: true
719
- })
720
- )
721
- )
722
- ),
723
- remove: (sourceId) => Effect5.gen(function* () {
724
- yield* operationStore.removeByNamespace(sourceId);
725
- yield* operationStore.removeSource(sourceId);
726
- yield* ctx.tools.unregisterBySource(sourceId);
727
- }),
728
- detect: (url) => Effect5.gen(function* () {
729
- const trimmed = url.trim();
730
- if (!trimmed) return null;
731
- const parsed = yield* Effect5.try(() => new URL(trimmed)).pipe(Effect5.option);
732
- if (parsed._tag === "None") return null;
733
- const ok = yield* introspect(trimmed).pipe(
734
- Effect5.provide(httpClientLayer),
735
- Effect5.map(() => true),
736
- Effect5.catchAll(() => Effect5.succeed(false))
737
- );
738
- if (!ok) return null;
739
- const name = namespaceFromEndpoint(trimmed);
740
- return new SourceDetectionResult({
741
- kind: "graphql",
742
- confidence: "high",
743
- endpoint: trimmed,
744
- name,
745
- namespace: name
746
- });
747
- })
748
- });
749
- const addSourceInternal = (config) => Effect5.gen(function* () {
750
- let introspectionResult;
751
- if (config.introspectionJson) {
752
- introspectionResult = yield* parseIntrospectionJson(config.introspectionJson);
753
- } else {
754
- const resolvedHeaders = {};
755
- if (config.headers) {
756
- for (const [name, value] of Object.entries(config.headers)) {
757
- if (typeof value === "string") {
758
- resolvedHeaders[name] = value;
759
- } else {
760
- const secret = yield* ctx.secrets.resolve(value.secretId, ctx.scope.id).pipe(Effect5.catchAll(() => Effect5.succeed("")));
761
- if (secret) {
762
- resolvedHeaders[name] = value.prefix ? `${value.prefix}${secret}` : secret;
763
- }
764
- }
765
- }
766
- }
767
- introspectionResult = yield* introspect(
768
- config.endpoint,
769
- Object.keys(resolvedHeaders).length > 0 ? resolvedHeaders : void 0
770
- ).pipe(Effect5.provide(httpClientLayer));
771
- }
772
- const { result, definitions } = yield* extract(introspectionResult);
773
- const namespace = config.namespace ?? namespaceFromEndpoint(config.endpoint);
774
- if (Object.keys(definitions).length > 0) {
775
- yield* ctx.tools.registerDefinitions(definitions);
776
- }
777
- const invocationConfig = new InvocationConfig({
778
- endpoint: config.endpoint,
779
- headers: config.headers ?? {}
780
- });
781
- const typeMap = /* @__PURE__ */ new Map();
782
- for (const t of introspectionResult.__schema.types) {
783
- typeMap.set(t.name, t);
784
- }
785
- const fieldMap = /* @__PURE__ */ new Map();
786
- const schema = introspectionResult.__schema;
787
- for (const rootKind of ["query", "mutation"]) {
788
- const typeName = rootKind === "query" ? schema.queryType?.name : schema.mutationType?.name;
789
- if (!typeName) continue;
790
- const rootType = typeMap.get(typeName);
791
- if (!rootType?.fields) continue;
792
- for (const f of rootType.fields) {
793
- if (!f.name.startsWith("__")) {
794
- fieldMap.set(`${rootKind}.${f.name}`, { kind: rootKind, field: f });
795
- }
796
- }
797
- }
798
- const registrations = [];
799
- yield* Effect5.forEach(
800
- result.fields,
801
- (extractedField) => {
802
- const reg = toRegistration(extractedField, namespace);
803
- registrations.push(reg);
804
- const key = `${extractedField.kind}.${extractedField.fieldName}`;
805
- const entry = fieldMap.get(key);
806
- const operationString = entry ? buildOperationStringForField(entry.kind, entry.field, typeMap) : `${extractedField.kind} { ${extractedField.fieldName} }`;
807
- const binding = new OperationBinding({
808
- kind: extractedField.kind,
809
- fieldName: extractedField.fieldName,
810
- operationString,
811
- variableNames: extractedField.arguments.map((a) => a.name)
812
- });
813
- return operationStore.put(reg.id, namespace, binding);
814
- },
815
- { discard: true }
816
- );
817
- yield* ctx.tools.register(registrations);
818
- yield* operationStore.putSource({
819
- namespace,
820
- name: config.name?.trim() || namespace,
821
- config: {
822
- endpoint: config.endpoint,
823
- introspectionJson: config.introspectionJson,
824
- namespace: config.namespace,
825
- headers: config.headers
826
- },
827
- invocationConfig
828
- });
829
- return { sourceId: namespace, toolCount: registrations.length };
830
- });
831
- const runtimeTools = yield* registerRuntimeTools({
832
- registry: ctx.tools,
833
- sources: ctx.sources,
834
- pluginKey: "graphql",
835
- source: {
836
- id: "built-in",
837
- name: "Built In",
838
- kind: "built-in"
839
- },
840
- tools: [
841
- runtimeTool({
842
- id: "graphql.addSource",
843
- name: "graphql.addSource",
844
- description: "Add a GraphQL endpoint and register its operations as tools",
845
- inputSchema: AddSourceInputSchema,
846
- outputSchema: AddSourceOutputSchema,
847
- handler: (input) => addSourceInternal(input)
848
- })
849
- ]
850
- });
851
- return {
852
- extension: {
853
- addSource: (config) => addSourceInternal(config).pipe(
854
- Effect5.map(({ toolCount }) => ({ toolCount })),
855
- Effect5.mapError(
856
- (err) => new GraphqlExtractionError({
857
- message: err instanceof Error ? err.message : String(err)
858
- })
859
- )
860
- ),
861
- removeSource: (namespace) => Effect5.gen(function* () {
862
- const toolIds = yield* operationStore.removeByNamespace(namespace);
863
- if (toolIds.length > 0) {
864
- yield* ctx.tools.unregister(toolIds);
865
- }
866
- yield* operationStore.removeSource(namespace);
867
- }),
868
- getSource: (namespace) => operationStore.getSource(namespace),
869
- updateSource: (namespace, input) => Effect5.gen(function* () {
870
- const existing = yield* operationStore.getSource(namespace);
871
- if (!existing) return;
872
- const updatedConfig = {
873
- ...existing.config,
874
- ...input.endpoint !== void 0 ? { endpoint: input.endpoint } : {},
875
- ...input.headers !== void 0 ? { headers: input.headers } : {}
876
- };
877
- const newInvocationConfig = new InvocationConfig({
878
- endpoint: updatedConfig.endpoint,
879
- headers: updatedConfig.headers ?? {}
880
- });
881
- yield* operationStore.putSource({
882
- namespace,
883
- name: input.name?.trim() || existing.name,
884
- config: updatedConfig,
885
- invocationConfig: newInvocationConfig
886
- });
887
- })
888
- },
889
- close: () => runtimeTools.close()
890
- };
891
- })
892
- });
893
- };
894
-
895
- export {
896
- GraphqlIntrospectionError,
897
- GraphqlExtractionError,
898
- GraphqlInvocationError,
899
- introspect,
900
- parseIntrospectionJson,
901
- GraphqlOperationKind,
902
- GraphqlArgument,
903
- ExtractedField,
904
- ExtractionResult,
905
- OperationBinding,
906
- HeaderValue,
907
- InvocationConfig,
908
- InvocationResult,
909
- extract,
910
- invoke,
911
- makeGraphqlInvoker,
912
- makeKvOperationStore,
913
- makeInMemoryOperationStore,
914
- graphqlPlugin
915
- };
916
- //# sourceMappingURL=chunk-AC5VPNLE.js.map