@sidecar-ai/cli 0.1.0-alpha.0 → 0.1.0-alpha.10
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/dist/index.js +1854 -425
- package/dist/index.js.map +1 -1
- package/package.json +11 -6
package/dist/index.js
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// packages/cli/src/index.ts
|
|
4
|
-
import { existsSync as
|
|
5
|
-
import { mkdir as
|
|
4
|
+
import { existsSync as existsSync7, realpathSync } from "fs";
|
|
5
|
+
import { mkdir as mkdir10, readFile as readFile8, writeFile as writeFile10 } from "fs/promises";
|
|
6
6
|
import { createServer as createServer2 } from "http";
|
|
7
|
-
import
|
|
7
|
+
import path20 from "path";
|
|
8
8
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
9
|
-
import { fileURLToPath, pathToFileURL } from "url";
|
|
9
|
+
import { fileURLToPath, pathToFileURL as pathToFileURL3 } from "url";
|
|
10
10
|
import { cwd, exit, stdin as stdin2, stdout as stdout2 } from "process";
|
|
11
|
-
import { tsImport } from "tsx/esm/api";
|
|
11
|
+
import { tsImport as tsImport2 } from "tsx/esm/api";
|
|
12
12
|
|
|
13
13
|
// packages/auth/src/core.ts
|
|
14
14
|
var authBrand = /* @__PURE__ */ Symbol.for("sidecar.auth");
|
|
15
15
|
function isSidecarAuth(value) {
|
|
16
16
|
return Boolean(
|
|
17
|
-
value && typeof value === "object" && value[authBrand]
|
|
17
|
+
value && typeof value === "object" && (value[authBrand] || value.kind === "sidecar.auth")
|
|
18
18
|
);
|
|
19
19
|
}
|
|
20
20
|
|
|
@@ -26,13 +26,19 @@ var resourceBrand = /* @__PURE__ */ Symbol.for("sidecar.resource");
|
|
|
26
26
|
var resourceResultBrand = /* @__PURE__ */ Symbol.for("sidecar.resourceResult");
|
|
27
27
|
var promptBrand = /* @__PURE__ */ Symbol.for("sidecar.prompt");
|
|
28
28
|
function isSidecarTool(value) {
|
|
29
|
-
return Boolean(
|
|
29
|
+
return Boolean(
|
|
30
|
+
value && typeof value === "object" && (value[toolBrand] || value.kind === "sidecar.tool")
|
|
31
|
+
);
|
|
30
32
|
}
|
|
31
33
|
function isSidecarResource(value) {
|
|
32
|
-
return Boolean(
|
|
34
|
+
return Boolean(
|
|
35
|
+
value && typeof value === "object" && (value[resourceBrand] || value.kind === "sidecar.resource")
|
|
36
|
+
);
|
|
33
37
|
}
|
|
34
38
|
function isSidecarPrompt(value) {
|
|
35
|
-
return Boolean(
|
|
39
|
+
return Boolean(
|
|
40
|
+
value && typeof value === "object" && (value[promptBrand] || value.kind === "sidecar.prompt")
|
|
41
|
+
);
|
|
36
42
|
}
|
|
37
43
|
var toolResult = Object.assign(
|
|
38
44
|
createToolResult,
|
|
@@ -60,9 +66,9 @@ var resourceResult = Object.assign(
|
|
|
60
66
|
);
|
|
61
67
|
function createToolResult(input) {
|
|
62
68
|
const resultEnvelope = stripUndefined({
|
|
63
|
-
structuredContent: input.structuredContent,
|
|
69
|
+
structuredContent: stripJsonUndefined(input.structuredContent),
|
|
64
70
|
content: normalizeRequiredContent(input.content),
|
|
65
|
-
_meta: input.meta,
|
|
71
|
+
_meta: stripJsonUndefined(input.meta),
|
|
66
72
|
isError: input.isError
|
|
67
73
|
});
|
|
68
74
|
Object.defineProperty(resultEnvelope, toolResultBrand, {
|
|
@@ -106,9 +112,9 @@ function normalizeToolResult(value) {
|
|
|
106
112
|
);
|
|
107
113
|
}
|
|
108
114
|
return stripUndefined({
|
|
109
|
-
structuredContent: value.structuredContent,
|
|
115
|
+
structuredContent: stripJsonUndefined(value.structuredContent),
|
|
110
116
|
content: value.content ?? [],
|
|
111
|
-
_meta: value._meta,
|
|
117
|
+
_meta: stripJsonUndefined(value._meta),
|
|
112
118
|
isError: value.isError
|
|
113
119
|
});
|
|
114
120
|
}
|
|
@@ -447,10 +453,24 @@ function normalizePromptResult(value, defaultDescription) {
|
|
|
447
453
|
function stripUndefined(value) {
|
|
448
454
|
return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== void 0));
|
|
449
455
|
}
|
|
456
|
+
function stripJsonUndefined(value) {
|
|
457
|
+
if (value === void 0) {
|
|
458
|
+
return void 0;
|
|
459
|
+
}
|
|
460
|
+
if (Array.isArray(value)) {
|
|
461
|
+
return value.map((entry) => stripJsonUndefined(entry)).filter((entry) => entry !== void 0);
|
|
462
|
+
}
|
|
463
|
+
if (!value || typeof value !== "object") {
|
|
464
|
+
return value;
|
|
465
|
+
}
|
|
466
|
+
return Object.fromEntries(
|
|
467
|
+
Object.entries(value).map(([key, entry]) => [key, stripJsonUndefined(entry)]).filter(([, entry]) => entry !== void 0)
|
|
468
|
+
);
|
|
469
|
+
}
|
|
450
470
|
|
|
451
471
|
// packages/compiler/src/analyze.ts
|
|
452
472
|
import { readdir } from "fs/promises";
|
|
453
|
-
import
|
|
473
|
+
import path5 from "path";
|
|
454
474
|
import {
|
|
455
475
|
Node as Node4,
|
|
456
476
|
SyntaxKind as SyntaxKind2
|
|
@@ -617,14 +637,7 @@ function devSidecarTypePaths() {
|
|
|
617
637
|
import {
|
|
618
638
|
Node as Node2
|
|
619
639
|
} from "ts-morph";
|
|
620
|
-
function getParamsSchema(
|
|
621
|
-
const explicitParams = definition.getProperty("params");
|
|
622
|
-
if (explicitParams && Node2.isPropertyAssignment(explicitParams)) {
|
|
623
|
-
const inferred = getSchemaFromZodProperty(explicitParams);
|
|
624
|
-
if (inferred) {
|
|
625
|
-
return inferred;
|
|
626
|
-
}
|
|
627
|
-
}
|
|
640
|
+
function getParamsSchema(_definition, execute) {
|
|
628
641
|
const [params] = execute.getParameters();
|
|
629
642
|
if (!params) {
|
|
630
643
|
return emptyObjectSchema();
|
|
@@ -645,74 +658,6 @@ function getOutputSchema(definition, execute) {
|
|
|
645
658
|
}
|
|
646
659
|
return typeToJsonSchema(returnType);
|
|
647
660
|
}
|
|
648
|
-
function getSchemaFromZodProperty(property) {
|
|
649
|
-
const initializer = property.getInitializer();
|
|
650
|
-
if (!initializer) {
|
|
651
|
-
return void 0;
|
|
652
|
-
}
|
|
653
|
-
if (Node2.isCallExpression(initializer) && initializer.getExpression().getText().endsWith(".object")) {
|
|
654
|
-
const [shape] = initializer.getArguments();
|
|
655
|
-
if (shape && Node2.isObjectLiteralExpression(shape)) {
|
|
656
|
-
return zodObjectLiteralToSchema(shape);
|
|
657
|
-
}
|
|
658
|
-
}
|
|
659
|
-
return void 0;
|
|
660
|
-
}
|
|
661
|
-
function zodObjectLiteralToSchema(shape) {
|
|
662
|
-
const properties = {};
|
|
663
|
-
const required = [];
|
|
664
|
-
for (const property of shape.getProperties()) {
|
|
665
|
-
if (!Node2.isPropertyAssignment(property)) {
|
|
666
|
-
continue;
|
|
667
|
-
}
|
|
668
|
-
const name = property.getName().replace(/^["']|["']$/g, "");
|
|
669
|
-
const initializer = property.getInitializer();
|
|
670
|
-
if (!initializer) {
|
|
671
|
-
continue;
|
|
672
|
-
}
|
|
673
|
-
const propertySchema = zodExpressionToSchema(initializer);
|
|
674
|
-
properties[name] = propertySchema.schema;
|
|
675
|
-
if (!propertySchema.optional) {
|
|
676
|
-
required.push(name);
|
|
677
|
-
}
|
|
678
|
-
}
|
|
679
|
-
return {
|
|
680
|
-
type: "object",
|
|
681
|
-
properties,
|
|
682
|
-
required,
|
|
683
|
-
additionalProperties: false
|
|
684
|
-
};
|
|
685
|
-
}
|
|
686
|
-
function zodExpressionToSchema(expression) {
|
|
687
|
-
const text = expression.getText();
|
|
688
|
-
const optional = /\.optional\(\)/.test(text) || /\.default\(/.test(text);
|
|
689
|
-
const schema = {};
|
|
690
|
-
if (/z\.string\(\)/.test(text)) {
|
|
691
|
-
schema.type = "string";
|
|
692
|
-
} else if (/z\.number\(\)/.test(text)) {
|
|
693
|
-
schema.type = "number";
|
|
694
|
-
} else if (/z\.boolean\(\)/.test(text)) {
|
|
695
|
-
schema.type = "boolean";
|
|
696
|
-
} else if (/z\.array\(/.test(text)) {
|
|
697
|
-
schema.type = "array";
|
|
698
|
-
schema.items = {};
|
|
699
|
-
} else {
|
|
700
|
-
schema.type = "object";
|
|
701
|
-
}
|
|
702
|
-
const min = text.match(/\.min\((\d+)/);
|
|
703
|
-
const max = text.match(/\.max\((\d+)/);
|
|
704
|
-
if (schema.type === "string") {
|
|
705
|
-
if (min) schema.minLength = Number(min[1]);
|
|
706
|
-
if (max) schema.maxLength = Number(max[1]);
|
|
707
|
-
} else {
|
|
708
|
-
if (min) schema.minimum = Number(min[1]);
|
|
709
|
-
if (max) schema.maximum = Number(max[1]);
|
|
710
|
-
}
|
|
711
|
-
if (/\.int\(\)/.test(text)) {
|
|
712
|
-
schema.type = "integer";
|
|
713
|
-
}
|
|
714
|
-
return { schema, optional };
|
|
715
|
-
}
|
|
716
661
|
function typeToJsonSchema(type, description) {
|
|
717
662
|
const withoutUndefined = removeUndefined(type);
|
|
718
663
|
const schema = typeToJsonSchemaInner(withoutUndefined);
|
|
@@ -755,12 +700,19 @@ function typeToJsonSchemaInner(type) {
|
|
|
755
700
|
return { anyOf: parts.map((part) => typeToJsonSchema(part)) };
|
|
756
701
|
}
|
|
757
702
|
const properties = type.getProperties();
|
|
703
|
+
const indexType = type.getStringIndexType() ?? type.getNumberIndexType();
|
|
758
704
|
if (properties.length > 0) {
|
|
759
|
-
return objectTypeToSchema(properties);
|
|
705
|
+
return objectTypeToSchema(properties, indexType);
|
|
706
|
+
}
|
|
707
|
+
if (indexType) {
|
|
708
|
+
return {
|
|
709
|
+
type: "object",
|
|
710
|
+
additionalProperties: indexTypeToAdditionalProperties(indexType)
|
|
711
|
+
};
|
|
760
712
|
}
|
|
761
713
|
return {};
|
|
762
714
|
}
|
|
763
|
-
function objectTypeToSchema(properties) {
|
|
715
|
+
function objectTypeToSchema(properties, indexType) {
|
|
764
716
|
const schemaProperties = {};
|
|
765
717
|
const required = [];
|
|
766
718
|
for (const property of properties) {
|
|
@@ -784,9 +736,16 @@ function objectTypeToSchema(properties) {
|
|
|
784
736
|
type: "object",
|
|
785
737
|
properties: schemaProperties,
|
|
786
738
|
required,
|
|
787
|
-
additionalProperties: false
|
|
739
|
+
additionalProperties: indexType ? indexTypeToAdditionalProperties(indexType) : false
|
|
788
740
|
};
|
|
789
741
|
}
|
|
742
|
+
function indexTypeToAdditionalProperties(type) {
|
|
743
|
+
if (type.isAny() || type.isUnknown()) {
|
|
744
|
+
return true;
|
|
745
|
+
}
|
|
746
|
+
const schema = typeToJsonSchema(type);
|
|
747
|
+
return Object.keys(schema).length ? schema : true;
|
|
748
|
+
}
|
|
790
749
|
function literalOrPrimitive(type, primitive) {
|
|
791
750
|
if (isLiteralType(type)) {
|
|
792
751
|
return { const: literalValue(type) };
|
|
@@ -844,39 +803,134 @@ function schemaDescription(symbol) {
|
|
|
844
803
|
}).find((comment) => comment.trim().length > 0);
|
|
845
804
|
}
|
|
846
805
|
|
|
806
|
+
// packages/compiler/src/runtime-schema.ts
|
|
807
|
+
import { existsSync as existsSync2 } from "fs";
|
|
808
|
+
import path3 from "path";
|
|
809
|
+
import { pathToFileURL } from "url";
|
|
810
|
+
import { tsImport } from "tsx/esm/api";
|
|
811
|
+
async function readRuntimeToolInputSchema(rootDir, sourcePath) {
|
|
812
|
+
let sidecarTool;
|
|
813
|
+
try {
|
|
814
|
+
sidecarTool = await importRuntimeTool(rootDir, sourcePath);
|
|
815
|
+
} catch (error) {
|
|
816
|
+
warnRuntimeSchemaFallback(sourcePath, error);
|
|
817
|
+
return void 0;
|
|
818
|
+
}
|
|
819
|
+
if (!sidecarTool.params) {
|
|
820
|
+
return void 0;
|
|
821
|
+
}
|
|
822
|
+
try {
|
|
823
|
+
return await paramsToJsonSchema(sidecarTool.params);
|
|
824
|
+
} catch (error) {
|
|
825
|
+
warnRuntimeSchemaFallback(sourcePath, error);
|
|
826
|
+
return void 0;
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
async function importRuntimeTool(rootDir, sourcePath) {
|
|
830
|
+
const module = await tsImport(pathToFileURL(sourcePath).href, {
|
|
831
|
+
parentURL: runtimeParentUrl(rootDir),
|
|
832
|
+
tsconfig: runtimeTsconfig(rootDir)
|
|
833
|
+
});
|
|
834
|
+
const defaultExport = unwrapRuntimeDefault(module.default);
|
|
835
|
+
if (!isSidecarTool(defaultExport)) {
|
|
836
|
+
throw new Error(`${path3.relative(rootDir, sourcePath)} must default-export tool({ ... }).`);
|
|
837
|
+
}
|
|
838
|
+
return defaultExport;
|
|
839
|
+
}
|
|
840
|
+
async function paramsToJsonSchema(params) {
|
|
841
|
+
const instanceConverter = readInstanceConverter(params);
|
|
842
|
+
if (instanceConverter) {
|
|
843
|
+
return ensureJsonSchema(instanceConverter({ io: "input" }));
|
|
844
|
+
}
|
|
845
|
+
if (isZodMiniSchema(params)) {
|
|
846
|
+
const zod = await import("zod/v4-mini");
|
|
847
|
+
if (typeof zod.toJSONSchema === "function") {
|
|
848
|
+
return ensureJsonSchema(zod.toJSONSchema(params, { io: "input" }));
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
return void 0;
|
|
852
|
+
}
|
|
853
|
+
function readInstanceConverter(params) {
|
|
854
|
+
if (!params || typeof params !== "object") {
|
|
855
|
+
return void 0;
|
|
856
|
+
}
|
|
857
|
+
const record = params;
|
|
858
|
+
const converter = record.toJSONSchema ?? record.toJsonSchema;
|
|
859
|
+
return typeof converter === "function" ? converter.bind(params) : void 0;
|
|
860
|
+
}
|
|
861
|
+
function isZodMiniSchema(params) {
|
|
862
|
+
return Boolean(params && typeof params === "object" && "_zod" in params);
|
|
863
|
+
}
|
|
864
|
+
function ensureJsonSchema(value) {
|
|
865
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
866
|
+
return void 0;
|
|
867
|
+
}
|
|
868
|
+
return value;
|
|
869
|
+
}
|
|
870
|
+
function runtimeParentUrl(rootDir) {
|
|
871
|
+
const configPath = path3.join(rootDir, "sidecar.config.ts");
|
|
872
|
+
return pathToFileURL(existsSync2(configPath) ? configPath : rootDir).href;
|
|
873
|
+
}
|
|
874
|
+
function runtimeTsconfig(rootDir) {
|
|
875
|
+
const projectTsconfig = path3.join(rootDir, "tsconfig.json");
|
|
876
|
+
if (existsSync2(projectTsconfig)) {
|
|
877
|
+
return projectTsconfig;
|
|
878
|
+
}
|
|
879
|
+
const repoTsconfig = path3.join(process.cwd(), "tsconfig.json");
|
|
880
|
+
const repoCore = path3.join(process.cwd(), "packages", "core", "src", "index.ts");
|
|
881
|
+
return existsSyncSafe(repoTsconfig) && existsSyncSafe(repoCore) ? repoTsconfig : false;
|
|
882
|
+
}
|
|
883
|
+
function unwrapRuntimeDefault(value) {
|
|
884
|
+
if (value && typeof value === "object" && "default" in value && Object.keys(value).every((key) => key === "default" || key === "__esModule")) {
|
|
885
|
+
return unwrapRuntimeDefault(value.default);
|
|
886
|
+
}
|
|
887
|
+
return value;
|
|
888
|
+
}
|
|
889
|
+
function warnRuntimeSchemaFallback(sourcePath, error) {
|
|
890
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
891
|
+
console.warn(
|
|
892
|
+
`[sidecar] Could not convert runtime params schema for ${sourcePath}; falling back to TypeScript parameter inference. ${message}`
|
|
893
|
+
);
|
|
894
|
+
}
|
|
895
|
+
|
|
847
896
|
// packages/compiler/src/widgets.ts
|
|
848
897
|
import { createHash } from "crypto";
|
|
849
|
-
import { existsSync as
|
|
898
|
+
import { existsSync as existsSync3 } from "fs";
|
|
850
899
|
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
851
|
-
import
|
|
900
|
+
import { createRequire } from "module";
|
|
901
|
+
import path4 from "path";
|
|
902
|
+
import { pathToFileURL as pathToFileURL2 } from "url";
|
|
852
903
|
import { build as esbuild } from "esbuild";
|
|
853
904
|
import postcss from "postcss";
|
|
854
905
|
import {
|
|
855
906
|
Node as Node3,
|
|
856
907
|
SyntaxKind
|
|
857
908
|
} from "ts-morph";
|
|
858
|
-
|
|
909
|
+
var CLAUDE_FONT_RESOURCE_DOMAIN = "https://assets.claude.ai";
|
|
910
|
+
var requireFromCompiler = createRequire(import.meta.url);
|
|
911
|
+
async function buildWidgets(rootDir, outDir, tools, config = void 0) {
|
|
859
912
|
const widgets = tools.filter(
|
|
860
913
|
(entry) => Boolean(entry.widget)
|
|
861
914
|
);
|
|
862
915
|
if (!widgets.length) {
|
|
863
916
|
return;
|
|
864
917
|
}
|
|
865
|
-
const cacheDir =
|
|
918
|
+
const cacheDir = path4.join(rootDir, ".sidecar", "cache", "widgets");
|
|
866
919
|
await mkdir(cacheDir, { recursive: true });
|
|
867
920
|
const appStyle = await prepareAppStyle(rootDir, cacheDir);
|
|
921
|
+
const configure = await loadWidgetBundlerHook(rootDir, cacheDir, config?.configure);
|
|
868
922
|
for (const entry of widgets) {
|
|
869
|
-
const sourceFile =
|
|
923
|
+
const sourceFile = path4.join(rootDir, entry.widget.sourceFile);
|
|
870
924
|
const safeId = safePathSegment(entry.id);
|
|
871
|
-
const entryFile =
|
|
872
|
-
const importPath = toImportSpecifier(
|
|
925
|
+
const entryFile = path4.join(cacheDir, `${safeId}.entry.tsx`);
|
|
926
|
+
const importPath = toImportSpecifier(path4.dirname(entryFile), sourceFile);
|
|
873
927
|
await writeFile(
|
|
874
928
|
entryFile,
|
|
875
929
|
`import React from "react";
|
|
876
930
|
import { createRoot } from "react-dom/client";
|
|
877
931
|
import { SidecarWidgetRoot } from "@sidecar-ai/react";
|
|
878
932
|
import "@sidecar-ai/native/styles.css";
|
|
879
|
-
${appStyle ? `import ${JSON.stringify(toImportSpecifier(
|
|
933
|
+
${appStyle ? `import ${JSON.stringify(toImportSpecifier(path4.dirname(entryFile), appStyle))};` : ""}
|
|
880
934
|
import Component from ${JSON.stringify(importPath)};
|
|
881
935
|
|
|
882
936
|
createRoot(document.getElementById("root")!).render(
|
|
@@ -884,64 +938,219 @@ createRoot(document.getElementById("root")!).render(
|
|
|
884
938
|
);
|
|
885
939
|
`
|
|
886
940
|
);
|
|
887
|
-
const
|
|
941
|
+
const baseOptions = enforceWidgetBuildInvariants({
|
|
888
942
|
absWorkingDir: rootDir,
|
|
889
|
-
alias:
|
|
943
|
+
alias: sidecarWidgetAliases(rootDir),
|
|
890
944
|
bundle: true,
|
|
945
|
+
define: {
|
|
946
|
+
"process.env.NODE_ENV": JSON.stringify("production")
|
|
947
|
+
},
|
|
891
948
|
entryPoints: [entryFile],
|
|
892
949
|
format: "iife",
|
|
893
950
|
jsx: "automatic",
|
|
894
|
-
minify:
|
|
951
|
+
minify: true,
|
|
895
952
|
nodePaths: [
|
|
896
|
-
|
|
897
|
-
|
|
953
|
+
path4.join(rootDir, "node_modules"),
|
|
954
|
+
path4.join(process.cwd(), "node_modules")
|
|
898
955
|
],
|
|
899
956
|
outfile: "widget.js",
|
|
900
957
|
platform: "browser",
|
|
901
958
|
sourcemap: false,
|
|
902
959
|
write: false
|
|
903
960
|
});
|
|
904
|
-
const
|
|
905
|
-
const
|
|
961
|
+
const staticOptions = widgetBuildOptionsFromConfig(rootDir, config);
|
|
962
|
+
const configuredOptions = configure ? await configureWidgetBuild(configure, {
|
|
963
|
+
rootDir,
|
|
964
|
+
outDir,
|
|
965
|
+
entryFile,
|
|
966
|
+
widget: {
|
|
967
|
+
toolId: entry.id,
|
|
968
|
+
toolName: entry.name,
|
|
969
|
+
target: entry.target,
|
|
970
|
+
sourceFile: entry.widget.sourceFile
|
|
971
|
+
},
|
|
972
|
+
esbuildOptions: mergeWidgetBuildOptions(baseOptions, staticOptions)
|
|
973
|
+
}) : void 0;
|
|
974
|
+
const bundled = await esbuild(enforceWidgetBuildInvariants(
|
|
975
|
+
mergeWidgetBuildOptions(baseOptions, staticOptions, configuredOptions),
|
|
976
|
+
{ rootDir, entryFile }
|
|
977
|
+
));
|
|
978
|
+
const outputFiles = bundled.outputFiles ?? [];
|
|
979
|
+
const javascript = outputFiles.find((file) => file.path.endsWith(".js"))?.text ?? "";
|
|
980
|
+
const css = outputFiles.filter((file) => file.path.endsWith(".css")).map((file) => file.text).join("\n");
|
|
906
981
|
const html = renderWidgetHtml(entry.name, javascript, css);
|
|
907
982
|
const hash = createHash("sha256").update(html).digest("hex").slice(0, 12);
|
|
908
|
-
const outputDir =
|
|
909
|
-
const outputFile =
|
|
983
|
+
const outputDir = path4.join(outDir, "public", "widgets", safeId);
|
|
984
|
+
const outputFile = path4.join(outputDir, `widget.${hash}.html`);
|
|
910
985
|
const resourceUri = `ui://${safeId}/widget.${hash}.html`;
|
|
911
986
|
await mkdir(outputDir, { recursive: true });
|
|
912
987
|
await writeFile(outputFile, html);
|
|
913
988
|
entry.widget.resourceUri = resourceUri;
|
|
914
989
|
entry.widget.resourceMeta = widgetResourceMeta(entry.widget.options, entry.target);
|
|
915
|
-
entry.widget.outputFile =
|
|
990
|
+
entry.widget.outputFile = path4.relative(outDir, outputFile);
|
|
916
991
|
entry.descriptor._meta = mergeWidgetMeta(entry.descriptor._meta, widgetMeta(resourceUri, entry.widget.options, entry.target));
|
|
917
992
|
}
|
|
918
993
|
}
|
|
994
|
+
function widgetBuildOptionsFromConfig(rootDir, config) {
|
|
995
|
+
const esbuildConfig = config?.esbuild;
|
|
996
|
+
if (!esbuildConfig) {
|
|
997
|
+
return void 0;
|
|
998
|
+
}
|
|
999
|
+
return {
|
|
1000
|
+
alias: normalizeAliases(rootDir, esbuildConfig.alias),
|
|
1001
|
+
conditions: esbuildConfig.conditions,
|
|
1002
|
+
define: esbuildConfig.define,
|
|
1003
|
+
external: esbuildConfig.external,
|
|
1004
|
+
jsx: esbuildConfig.jsx,
|
|
1005
|
+
jsxImportSource: esbuildConfig.jsxImportSource,
|
|
1006
|
+
loader: esbuildConfig.loader,
|
|
1007
|
+
mainFields: esbuildConfig.mainFields
|
|
1008
|
+
};
|
|
1009
|
+
}
|
|
1010
|
+
async function loadWidgetBundlerHook(rootDir, cacheDir, hookPath) {
|
|
1011
|
+
if (!hookPath) {
|
|
1012
|
+
return void 0;
|
|
1013
|
+
}
|
|
1014
|
+
const sourcePath = path4.resolve(rootDir, hookPath);
|
|
1015
|
+
const relative = path4.relative(rootDir, sourcePath);
|
|
1016
|
+
if (relative.startsWith("..") || path4.isAbsolute(relative)) {
|
|
1017
|
+
throw new Error(`Widget bundler hook must stay inside the project root: ${hookPath}`);
|
|
1018
|
+
}
|
|
1019
|
+
if (!existsSync3(sourcePath)) {
|
|
1020
|
+
throw new Error(`Widget bundler hook not found: ${hookPath}`);
|
|
1021
|
+
}
|
|
1022
|
+
const outputFile = path4.join(cacheDir, `widget-bundler.${contentHash(hookPath)}.mjs`);
|
|
1023
|
+
await esbuild({
|
|
1024
|
+
absWorkingDir: rootDir,
|
|
1025
|
+
alias: devSidecarBundleAliases(rootDir),
|
|
1026
|
+
bundle: true,
|
|
1027
|
+
entryPoints: [sourcePath],
|
|
1028
|
+
format: "esm",
|
|
1029
|
+
nodePaths: [
|
|
1030
|
+
path4.join(rootDir, "node_modules"),
|
|
1031
|
+
path4.join(process.cwd(), "node_modules")
|
|
1032
|
+
],
|
|
1033
|
+
outfile: outputFile,
|
|
1034
|
+
packages: "external",
|
|
1035
|
+
platform: "node",
|
|
1036
|
+
target: "node20"
|
|
1037
|
+
});
|
|
1038
|
+
const module = await import(`${pathToFileURL2(outputFile).href}?t=${Date.now()}`);
|
|
1039
|
+
if (typeof module.default !== "function") {
|
|
1040
|
+
throw new Error(`Widget bundler hook must default-export a function: ${hookPath}`);
|
|
1041
|
+
}
|
|
1042
|
+
return module.default;
|
|
1043
|
+
}
|
|
1044
|
+
async function configureWidgetBuild(hook, input) {
|
|
1045
|
+
const result = await hook(input);
|
|
1046
|
+
if (!result) {
|
|
1047
|
+
return void 0;
|
|
1048
|
+
}
|
|
1049
|
+
if (typeof result === "object" && "esbuildOptions" in result) {
|
|
1050
|
+
return result.esbuildOptions;
|
|
1051
|
+
}
|
|
1052
|
+
return result;
|
|
1053
|
+
}
|
|
1054
|
+
function mergeWidgetBuildOptions(...options) {
|
|
1055
|
+
const merged = {};
|
|
1056
|
+
for (const option of options) {
|
|
1057
|
+
if (!option) {
|
|
1058
|
+
continue;
|
|
1059
|
+
}
|
|
1060
|
+
const previousAlias = merged.alias;
|
|
1061
|
+
const previousDefine = merged.define;
|
|
1062
|
+
const previousLoader = merged.loader;
|
|
1063
|
+
const previousExternal = merged.external;
|
|
1064
|
+
const previousNodePaths = merged.nodePaths;
|
|
1065
|
+
const previousPlugins = merged.plugins;
|
|
1066
|
+
Object.assign(merged, option);
|
|
1067
|
+
merged.alias = { ...previousAlias ?? {}, ...option.alias ?? {} };
|
|
1068
|
+
merged.define = { ...previousDefine ?? {}, ...option.define ?? {} };
|
|
1069
|
+
merged.loader = { ...previousLoader ?? {}, ...option.loader ?? {} };
|
|
1070
|
+
merged.external = uniqueStrings([...previousExternal ?? [], ...option.external ?? []]);
|
|
1071
|
+
merged.nodePaths = uniqueStrings([...previousNodePaths ?? [], ...option.nodePaths ?? []]);
|
|
1072
|
+
merged.plugins = [...previousPlugins ?? [], ...option.plugins ?? []];
|
|
1073
|
+
}
|
|
1074
|
+
return merged;
|
|
1075
|
+
}
|
|
1076
|
+
function enforceWidgetBuildInvariants(options, required) {
|
|
1077
|
+
const aliases = required ? { ...options.alias ?? {}, ...reactSingletonAliases(required.rootDir) } : options.alias;
|
|
1078
|
+
return {
|
|
1079
|
+
...options,
|
|
1080
|
+
absWorkingDir: required?.rootDir ?? options.absWorkingDir,
|
|
1081
|
+
alias: aliases,
|
|
1082
|
+
bundle: true,
|
|
1083
|
+
entryPoints: required ? [required.entryFile] : options.entryPoints,
|
|
1084
|
+
format: "iife",
|
|
1085
|
+
outfile: "widget.js",
|
|
1086
|
+
platform: "browser",
|
|
1087
|
+
write: false
|
|
1088
|
+
};
|
|
1089
|
+
}
|
|
1090
|
+
function sidecarWidgetAliases(rootDir) {
|
|
1091
|
+
return {
|
|
1092
|
+
...devSidecarBundleAliases(rootDir) ?? {},
|
|
1093
|
+
...reactSingletonAliases(rootDir)
|
|
1094
|
+
};
|
|
1095
|
+
}
|
|
1096
|
+
function reactSingletonAliases(rootDir) {
|
|
1097
|
+
return {
|
|
1098
|
+
react: resolveProjectModule(rootDir, "react"),
|
|
1099
|
+
"react/jsx-runtime": resolveProjectModule(rootDir, "react/jsx-runtime"),
|
|
1100
|
+
"react/jsx-dev-runtime": resolveProjectModule(rootDir, "react/jsx-dev-runtime"),
|
|
1101
|
+
"react-dom": resolveProjectModule(rootDir, "react-dom"),
|
|
1102
|
+
"react-dom/client": resolveProjectModule(rootDir, "react-dom/client"),
|
|
1103
|
+
"react-dom/server": resolveProjectModule(rootDir, "react-dom/server")
|
|
1104
|
+
};
|
|
1105
|
+
}
|
|
1106
|
+
function resolveProjectModule(rootDir, specifier) {
|
|
1107
|
+
return requireFromCompiler.resolve(specifier, {
|
|
1108
|
+
paths: [rootDir, process.cwd()]
|
|
1109
|
+
});
|
|
1110
|
+
}
|
|
1111
|
+
function normalizeAliases(rootDir, aliases) {
|
|
1112
|
+
if (!aliases) {
|
|
1113
|
+
return void 0;
|
|
1114
|
+
}
|
|
1115
|
+
return Object.fromEntries(
|
|
1116
|
+
Object.entries(aliases).map(([key, value]) => [
|
|
1117
|
+
key,
|
|
1118
|
+
value.startsWith(".") ? path4.resolve(rootDir, value) : value
|
|
1119
|
+
])
|
|
1120
|
+
);
|
|
1121
|
+
}
|
|
1122
|
+
function uniqueStrings(values) {
|
|
1123
|
+
return [...new Set(values)];
|
|
1124
|
+
}
|
|
1125
|
+
function contentHash(value) {
|
|
1126
|
+
return createHash("sha256").update(value).digest("hex").slice(0, 12);
|
|
1127
|
+
}
|
|
919
1128
|
function devSidecarBundleAliases(rootDir) {
|
|
920
1129
|
const repoRoot = findSidecarRepoRoot(rootDir) ?? findSidecarRepoRoot(process.cwd());
|
|
921
1130
|
if (!repoRoot) {
|
|
922
1131
|
return void 0;
|
|
923
1132
|
}
|
|
924
|
-
const reactEntry =
|
|
925
|
-
if (!
|
|
1133
|
+
const reactEntry = path4.join(repoRoot, "packages", "react", "src", "index.ts");
|
|
1134
|
+
if (!existsSync3(reactEntry)) {
|
|
926
1135
|
return void 0;
|
|
927
1136
|
}
|
|
928
1137
|
return {
|
|
929
|
-
"sidecar-ai":
|
|
930
|
-
"@sidecar-ai/client":
|
|
931
|
-
"@sidecar-ai/core":
|
|
1138
|
+
"sidecar-ai": path4.join(repoRoot, "packages", "sidecar-ai", "src", "index.ts"),
|
|
1139
|
+
"@sidecar-ai/client": path4.join(repoRoot, "packages", "client", "src", "index.ts"),
|
|
1140
|
+
"@sidecar-ai/core": path4.join(repoRoot, "packages", "core", "src", "index.ts"),
|
|
932
1141
|
"@sidecar-ai/react": reactEntry,
|
|
933
|
-
"@sidecar-ai/native":
|
|
934
|
-
"@sidecar-ai/native/components":
|
|
935
|
-
"@sidecar-ai/native/styles.css":
|
|
1142
|
+
"@sidecar-ai/native": path4.join(repoRoot, "packages", "native", "src", "index.ts"),
|
|
1143
|
+
"@sidecar-ai/native/components": path4.join(repoRoot, "packages", "native", "src", "components", "index.tsx"),
|
|
1144
|
+
"@sidecar-ai/native/styles.css": path4.join(repoRoot, "packages", "native", "src", "styles.css")
|
|
936
1145
|
};
|
|
937
1146
|
}
|
|
938
1147
|
function findSidecarRepoRoot(startDir) {
|
|
939
|
-
let current =
|
|
1148
|
+
let current = path4.resolve(startDir);
|
|
940
1149
|
while (true) {
|
|
941
|
-
if (
|
|
1150
|
+
if (existsSync3(path4.join(current, "packages", "core", "src", "index.ts"))) {
|
|
942
1151
|
return current;
|
|
943
1152
|
}
|
|
944
|
-
const parent =
|
|
1153
|
+
const parent = path4.dirname(current);
|
|
945
1154
|
if (parent === current) {
|
|
946
1155
|
return void 0;
|
|
947
1156
|
}
|
|
@@ -949,13 +1158,13 @@ function findSidecarRepoRoot(startDir) {
|
|
|
949
1158
|
}
|
|
950
1159
|
}
|
|
951
1160
|
async function prepareAppStyle(rootDir, cacheDir) {
|
|
952
|
-
const sourceFile =
|
|
953
|
-
if (!
|
|
1161
|
+
const sourceFile = path4.join(rootDir, "style.css");
|
|
1162
|
+
if (!existsSync3(sourceFile)) {
|
|
954
1163
|
return void 0;
|
|
955
1164
|
}
|
|
956
1165
|
const source = await readFile(sourceFile, "utf8");
|
|
957
1166
|
const css = await processAppStyle(source, sourceFile);
|
|
958
|
-
const outputFile =
|
|
1167
|
+
const outputFile = path4.join(cacheDir, "style.css");
|
|
959
1168
|
await writeFile(outputFile, css);
|
|
960
1169
|
return outputFile;
|
|
961
1170
|
}
|
|
@@ -967,7 +1176,7 @@ async function processAppStyle(source, from) {
|
|
|
967
1176
|
const plugins = [];
|
|
968
1177
|
if (cssSource.includes("@tailwind")) {
|
|
969
1178
|
const tailwind = await import("@tailwindcss/postcss");
|
|
970
|
-
plugins.push(tailwind.default({ base:
|
|
1179
|
+
plugins.push(tailwind.default({ base: path4.dirname(from) }));
|
|
971
1180
|
}
|
|
972
1181
|
const autoprefixer = await import("autoprefixer");
|
|
973
1182
|
plugins.push(autoprefixer.default());
|
|
@@ -981,13 +1190,13 @@ function needsPostcss(source) {
|
|
|
981
1190
|
return /@import\s+["']tailwindcss["']|@tailwind|@source|@theme|@plugin/.test(source);
|
|
982
1191
|
}
|
|
983
1192
|
function findWidget(rootDir, toolFile, id, target = "mcp", toolVariant = "shared") {
|
|
984
|
-
const selected = selectWidgetFile(
|
|
1193
|
+
const selected = selectWidgetFile(path4.dirname(toolFile), target, toolVariant);
|
|
985
1194
|
if (!selected) {
|
|
986
1195
|
return void 0;
|
|
987
1196
|
}
|
|
988
1197
|
const safeId = safePathSegment(id);
|
|
989
1198
|
return {
|
|
990
|
-
sourceFile:
|
|
1199
|
+
sourceFile: path4.relative(rootDir, selected.filePath),
|
|
991
1200
|
variant: selected.variant,
|
|
992
1201
|
resourceUri: `ui://${safeId}/widget.html`
|
|
993
1202
|
};
|
|
@@ -1022,7 +1231,8 @@ function widgetMeta(resourceUri, options = {}, target = "mcp") {
|
|
|
1022
1231
|
const standard = {
|
|
1023
1232
|
ui: {
|
|
1024
1233
|
resourceUri
|
|
1025
|
-
}
|
|
1234
|
+
},
|
|
1235
|
+
"ui/resourceUri": resourceUri
|
|
1026
1236
|
};
|
|
1027
1237
|
if (target !== "chatgpt") {
|
|
1028
1238
|
return standard;
|
|
@@ -1038,7 +1248,7 @@ function widgetMeta(resourceUri, options = {}, target = "mcp") {
|
|
|
1038
1248
|
function widgetResourceMeta(options = {}, target = "mcp") {
|
|
1039
1249
|
const csp = stripUndefined3({
|
|
1040
1250
|
connectDomains: options.csp?.connectDomains ? [...options.csp.connectDomains] : [],
|
|
1041
|
-
resourceDomains: options
|
|
1251
|
+
resourceDomains: widgetResourceDomains(options, target),
|
|
1042
1252
|
frameDomains: options.csp?.frameDomains ? [...options.csp.frameDomains] : void 0,
|
|
1043
1253
|
baseUriDomains: options.csp?.baseUriDomains ? [...options.csp.baseUriDomains] : void 0
|
|
1044
1254
|
});
|
|
@@ -1051,6 +1261,13 @@ function widgetResourceMeta(options = {}, target = "mcp") {
|
|
|
1051
1261
|
});
|
|
1052
1262
|
return Object.keys(ui).length ? { ui } : void 0;
|
|
1053
1263
|
}
|
|
1264
|
+
function widgetResourceDomains(options, target) {
|
|
1265
|
+
const domains = [...options.csp?.resourceDomains ?? []];
|
|
1266
|
+
if (target === "claude" && !domains.includes(CLAUDE_FONT_RESOURCE_DOMAIN)) {
|
|
1267
|
+
domains.push(CLAUDE_FONT_RESOURCE_DOMAIN);
|
|
1268
|
+
}
|
|
1269
|
+
return domains;
|
|
1270
|
+
}
|
|
1054
1271
|
function findWidgetDefinition(sourceFile) {
|
|
1055
1272
|
const call = resolveDefaultExportCall(sourceFile, "widget");
|
|
1056
1273
|
if (!call) {
|
|
@@ -1162,17 +1379,17 @@ function readStringArrayProperty(definition, propertyName) {
|
|
|
1162
1379
|
}
|
|
1163
1380
|
function selectWidgetFile(directory, target, toolVariant) {
|
|
1164
1381
|
if (toolVariant === "openai") {
|
|
1165
|
-
const filePath =
|
|
1166
|
-
return
|
|
1382
|
+
const filePath = path4.join(directory, "widget.openai.tsx");
|
|
1383
|
+
return existsSync3(filePath) ? { filePath, variant: "openai" } : void 0;
|
|
1167
1384
|
}
|
|
1168
1385
|
if (toolVariant === "anthropic") {
|
|
1169
|
-
const filePath =
|
|
1170
|
-
return
|
|
1386
|
+
const filePath = path4.join(directory, "widget.anthropic.tsx");
|
|
1387
|
+
return existsSync3(filePath) ? { filePath, variant: "anthropic" } : void 0;
|
|
1171
1388
|
}
|
|
1172
1389
|
const candidates = target === "chatgpt" || target === "claude" || target === "mcp" ? [{ name: "widget.tsx", variant: "shared" }] : [];
|
|
1173
1390
|
for (const candidate of candidates) {
|
|
1174
|
-
const filePath =
|
|
1175
|
-
if (
|
|
1391
|
+
const filePath = path4.join(directory, candidate.name);
|
|
1392
|
+
if (existsSync3(filePath)) {
|
|
1176
1393
|
return { filePath, variant: candidate.variant };
|
|
1177
1394
|
}
|
|
1178
1395
|
}
|
|
@@ -1223,17 +1440,23 @@ function stripUndefined3(value) {
|
|
|
1223
1440
|
// packages/compiler/src/analyze.ts
|
|
1224
1441
|
async function analyzeProjectTools(rootDir, options = {}) {
|
|
1225
1442
|
const target = options.target ?? "mcp";
|
|
1226
|
-
const toolFiles = await findToolFiles(
|
|
1443
|
+
const toolFiles = await findToolFiles(path5.join(rootDir, "server"), target);
|
|
1227
1444
|
if (toolFiles.length === 0) {
|
|
1228
1445
|
return [];
|
|
1229
1446
|
}
|
|
1230
1447
|
const project = createProject(rootDir);
|
|
1231
1448
|
const authScopes = readAuthScopeCatalog(project, rootDir);
|
|
1232
|
-
|
|
1233
|
-
|
|
1449
|
+
const sources = toolFiles.map((candidate) => ({
|
|
1450
|
+
candidate,
|
|
1451
|
+
sourceFile: project.addSourceFileAtPath(candidate.filePath)
|
|
1452
|
+
}));
|
|
1453
|
+
const runtimeInputSchemas = await readRuntimeInputSchemas(rootDir, sources);
|
|
1454
|
+
return sources.map(
|
|
1455
|
+
({ candidate, sourceFile }) => analyzeToolFile(sourceFile, rootDir, {
|
|
1234
1456
|
target,
|
|
1235
1457
|
variant: candidate.variant,
|
|
1236
|
-
authScopes
|
|
1458
|
+
authScopes,
|
|
1459
|
+
inputSchema: runtimeInputSchemas.get(candidate.filePath)
|
|
1237
1460
|
})
|
|
1238
1461
|
);
|
|
1239
1462
|
}
|
|
@@ -1242,7 +1465,7 @@ function analyzeToolFile(sourceFile, rootDir, options = {}) {
|
|
|
1242
1465
|
const variant = options.variant ?? "shared";
|
|
1243
1466
|
const definition = findToolDefinition(sourceFile);
|
|
1244
1467
|
const absoluteFile = sourceFile.getFilePath();
|
|
1245
|
-
const directory =
|
|
1468
|
+
const directory = path5.basename(path5.dirname(absoluteFile));
|
|
1246
1469
|
const name = getRequiredStringProperty(definition, "name", sourceFile);
|
|
1247
1470
|
const id = getOptionalStringProperty(definition, "id") ?? toMachineName(directory);
|
|
1248
1471
|
const description = getRequiredStringProperty(
|
|
@@ -1255,7 +1478,7 @@ function analyzeToolFile(sourceFile, rootDir, options = {}) {
|
|
|
1255
1478
|
const hosts = readHosts(definition);
|
|
1256
1479
|
const auth = readAuthPolicy(definition, options.authScopes ?? {});
|
|
1257
1480
|
const execute = getExecuteFunction(definition, sourceFile);
|
|
1258
|
-
const inputSchema = getParamsSchema(definition, execute);
|
|
1481
|
+
const inputSchema = options.inputSchema ?? getParamsSchema(definition, execute);
|
|
1259
1482
|
const outputSchema = getOutputSchema(definition, execute);
|
|
1260
1483
|
const descriptor = createToolDescriptor({
|
|
1261
1484
|
name,
|
|
@@ -1273,7 +1496,7 @@ function analyzeToolFile(sourceFile, rootDir, options = {}) {
|
|
|
1273
1496
|
validateWidgetHierarchy(sourceFile, absoluteFile, target, variant);
|
|
1274
1497
|
const widget = findWidget(rootDir, absoluteFile, id, target, variant);
|
|
1275
1498
|
if (widget) {
|
|
1276
|
-
const widgetFile =
|
|
1499
|
+
const widgetFile = path5.join(rootDir, widget.sourceFile);
|
|
1277
1500
|
const project = sourceFile.getProject();
|
|
1278
1501
|
const widgetSourceFile = project.getSourceFile(widgetFile) ?? project.addSourceFileAtPath(widgetFile);
|
|
1279
1502
|
widget.options = {
|
|
@@ -1284,7 +1507,7 @@ function analyzeToolFile(sourceFile, rootDir, options = {}) {
|
|
|
1284
1507
|
descriptor._meta = mergeWidgetMeta(descriptor._meta, widgetMeta(widget.resourceUri, widget.options, target));
|
|
1285
1508
|
}
|
|
1286
1509
|
return {
|
|
1287
|
-
sourceFile:
|
|
1510
|
+
sourceFile: path5.relative(rootDir, absoluteFile),
|
|
1288
1511
|
variant,
|
|
1289
1512
|
target,
|
|
1290
1513
|
directory,
|
|
@@ -1299,6 +1522,26 @@ function analyzeToolFile(sourceFile, rootDir, options = {}) {
|
|
|
1299
1522
|
descriptor
|
|
1300
1523
|
};
|
|
1301
1524
|
}
|
|
1525
|
+
async function readRuntimeInputSchemas(rootDir, sources) {
|
|
1526
|
+
const schemas = /* @__PURE__ */ new Map();
|
|
1527
|
+
await Promise.all(sources.map(async ({ candidate, sourceFile }) => {
|
|
1528
|
+
const definition = findToolDefinition(sourceFile);
|
|
1529
|
+
if (!definitionHasRuntimeParams(definition)) {
|
|
1530
|
+
return;
|
|
1531
|
+
}
|
|
1532
|
+
const schema = await readRuntimeToolInputSchema(rootDir, candidate.filePath);
|
|
1533
|
+
if (schema) {
|
|
1534
|
+
schemas.set(candidate.filePath, schema);
|
|
1535
|
+
}
|
|
1536
|
+
}));
|
|
1537
|
+
return schemas;
|
|
1538
|
+
}
|
|
1539
|
+
function definitionHasRuntimeParams(definition) {
|
|
1540
|
+
if (definition.getProperty("params")) {
|
|
1541
|
+
return true;
|
|
1542
|
+
}
|
|
1543
|
+
return Boolean(getWithParamsCall(definition));
|
|
1544
|
+
}
|
|
1302
1545
|
function readAuthPolicy(definition, authScopes) {
|
|
1303
1546
|
const initializer = readObjectProperty2(definition, "auth");
|
|
1304
1547
|
if (!initializer) {
|
|
@@ -1315,7 +1558,7 @@ function readAuthPolicy(definition, authScopes) {
|
|
|
1315
1558
|
return { authenticated: true };
|
|
1316
1559
|
}
|
|
1317
1560
|
function readAuthScopeCatalog(project, rootDir) {
|
|
1318
|
-
const authPath =
|
|
1561
|
+
const authPath = path5.join(rootDir, "auth.ts");
|
|
1319
1562
|
if (!existsSyncSafe(authPath)) {
|
|
1320
1563
|
return {};
|
|
1321
1564
|
}
|
|
@@ -1397,12 +1640,12 @@ function isNamedCall(call, name) {
|
|
|
1397
1640
|
return callee === name || callee.endsWith(`.${name}`);
|
|
1398
1641
|
}
|
|
1399
1642
|
function validateWidgetHierarchy(sourceFile, toolFile, target, variant) {
|
|
1400
|
-
const directory =
|
|
1643
|
+
const directory = path5.dirname(toolFile);
|
|
1401
1644
|
const platformWidget = target === "chatgpt" ? "widget.openai.tsx" : target === "claude" ? "widget.anthropic.tsx" : void 0;
|
|
1402
1645
|
if (!platformWidget || variant !== "shared") {
|
|
1403
1646
|
return;
|
|
1404
1647
|
}
|
|
1405
|
-
if (existsSyncSafe(
|
|
1648
|
+
if (existsSyncSafe(path5.join(directory, platformWidget))) {
|
|
1406
1649
|
const expectedTool = target === "chatgpt" ? "tool.openai.ts" : "tool.anthropic.ts";
|
|
1407
1650
|
throw new CompilerError(
|
|
1408
1651
|
sourceFile,
|
|
@@ -1417,7 +1660,7 @@ async function findToolFiles(serverDir, target) {
|
|
|
1417
1660
|
const entries = await readdir(serverDir, { withFileTypes: true });
|
|
1418
1661
|
const files = [];
|
|
1419
1662
|
for (const entry of entries) {
|
|
1420
|
-
const entryPath =
|
|
1663
|
+
const entryPath = path5.join(serverDir, entry.name);
|
|
1421
1664
|
if (entry.isDirectory()) {
|
|
1422
1665
|
const candidate = selectToolFile(entryPath, target);
|
|
1423
1666
|
if (candidate) {
|
|
@@ -1436,7 +1679,7 @@ function selectToolFile(directory, target) {
|
|
|
1436
1679
|
{ name: "tool.ts", variant: "shared" }
|
|
1437
1680
|
] : [{ name: "tool.ts", variant: "shared" }];
|
|
1438
1681
|
for (const candidate of candidates) {
|
|
1439
|
-
const filePath =
|
|
1682
|
+
const filePath = path5.join(directory, candidate.name);
|
|
1440
1683
|
if (existsSyncSafe(filePath)) {
|
|
1441
1684
|
return { filePath, variant: candidate.variant };
|
|
1442
1685
|
}
|
|
@@ -1472,16 +1715,32 @@ function getExecuteFunction(definition, sourceFile) {
|
|
|
1472
1715
|
return property;
|
|
1473
1716
|
}
|
|
1474
1717
|
if (Node4.isPropertyAssignment(property)) {
|
|
1475
|
-
const initializer = property.getInitializer();
|
|
1718
|
+
const initializer = unwrapExpression(property.getInitializer());
|
|
1476
1719
|
if (initializer && (Node4.isArrowFunction(initializer) || Node4.isFunctionExpression(initializer))) {
|
|
1477
1720
|
return initializer;
|
|
1478
1721
|
}
|
|
1722
|
+
const withParamsCall = getWithParamsCall(definition);
|
|
1723
|
+
const wrappedExecute = unwrapExpression(withParamsCall?.getArguments()[1]);
|
|
1724
|
+
if (wrappedExecute && (Node4.isArrowFunction(wrappedExecute) || Node4.isFunctionExpression(wrappedExecute))) {
|
|
1725
|
+
return wrappedExecute;
|
|
1726
|
+
}
|
|
1479
1727
|
}
|
|
1480
1728
|
throw new CompilerError(
|
|
1481
1729
|
sourceFile,
|
|
1482
1730
|
"execute must be a method, function expression, or arrow function."
|
|
1483
1731
|
);
|
|
1484
1732
|
}
|
|
1733
|
+
function getWithParamsCall(definition) {
|
|
1734
|
+
const property = definition.getProperty("execute");
|
|
1735
|
+
if (!property || !Node4.isPropertyAssignment(property)) {
|
|
1736
|
+
return void 0;
|
|
1737
|
+
}
|
|
1738
|
+
const initializer = unwrapExpression(property.getInitializer());
|
|
1739
|
+
if (!initializer || !Node4.isCallExpression(initializer)) {
|
|
1740
|
+
return void 0;
|
|
1741
|
+
}
|
|
1742
|
+
return isNamedCall(initializer, "withParams") ? initializer : void 0;
|
|
1743
|
+
}
|
|
1485
1744
|
function getRequiredStringProperty(definition, propertyName, sourceFile) {
|
|
1486
1745
|
const value = getOptionalStringProperty(definition, propertyName);
|
|
1487
1746
|
if (value === void 0 || !value.trim()) {
|
|
@@ -1636,17 +1895,17 @@ function readStringArrayProperty2(definition, propertyName) {
|
|
|
1636
1895
|
}
|
|
1637
1896
|
|
|
1638
1897
|
// packages/compiler/src/build.ts
|
|
1639
|
-
import { mkdir as
|
|
1640
|
-
import
|
|
1898
|
+
import { mkdir as mkdir9, writeFile as writeFile9 } from "fs/promises";
|
|
1899
|
+
import path19 from "path";
|
|
1641
1900
|
|
|
1642
1901
|
// packages/compiler/src/config.ts
|
|
1643
|
-
import
|
|
1902
|
+
import path6 from "path";
|
|
1644
1903
|
import {
|
|
1645
1904
|
Node as Node5,
|
|
1646
1905
|
SyntaxKind as SyntaxKind3
|
|
1647
1906
|
} from "ts-morph";
|
|
1648
1907
|
function analyzeProjectConfig(rootDir) {
|
|
1649
|
-
const configPath =
|
|
1908
|
+
const configPath = path6.join(rootDir, "sidecar.config.ts");
|
|
1650
1909
|
if (!existsSyncSafe(configPath)) {
|
|
1651
1910
|
return defaultCompilerConfig();
|
|
1652
1911
|
}
|
|
@@ -1658,6 +1917,13 @@ function analyzeProjectConfig(rootDir) {
|
|
|
1658
1917
|
return defaultCompilerConfig();
|
|
1659
1918
|
}
|
|
1660
1919
|
return {
|
|
1920
|
+
build: {
|
|
1921
|
+
target: readTargetNested(definition, "build", "target"),
|
|
1922
|
+
host: readHostNested(definition, "build", "host"),
|
|
1923
|
+
outDir: readStringNested(definition, "build", "outDir"),
|
|
1924
|
+
plugins: readBooleanNested(definition, "build", "plugins"),
|
|
1925
|
+
widgets: readWidgetBuildConfig(definition)
|
|
1926
|
+
},
|
|
1661
1927
|
resources: {
|
|
1662
1928
|
subscribe: readBooleanNested(definition, "resources", "subscribe") ?? false,
|
|
1663
1929
|
listChanged: readBooleanNested(definition, "resources", "listChanged") ?? false
|
|
@@ -1669,13 +1935,52 @@ function analyzeProjectConfig(rootDir) {
|
|
|
1669
1935
|
listChanged: readBooleanNested(definition, "tools", "listChanged") ?? false
|
|
1670
1936
|
},
|
|
1671
1937
|
pagination: {
|
|
1672
|
-
pageSize: readNumberNested(definition, "pagination", "pageSize") ??
|
|
1938
|
+
pageSize: readNumberNested(definition, "pagination", "pageSize") ?? 50,
|
|
1673
1939
|
hasOverride: hasProperty(readObjectProperty3(definition, "pagination"), "override")
|
|
1674
1940
|
}
|
|
1675
1941
|
};
|
|
1676
1942
|
}
|
|
1943
|
+
function readWidgetBuildConfig(definition) {
|
|
1944
|
+
const widgets = readObjectProperty3(readObjectProperty3(definition, "build"), "widgets");
|
|
1945
|
+
if (!widgets) {
|
|
1946
|
+
return void 0;
|
|
1947
|
+
}
|
|
1948
|
+
const config = {};
|
|
1949
|
+
const configure = readStringProperty3(widgets, "configure");
|
|
1950
|
+
const esbuild3 = readWidgetEsbuildConfig(widgets);
|
|
1951
|
+
if (configure) config.configure = configure;
|
|
1952
|
+
if (esbuild3) config.esbuild = esbuild3;
|
|
1953
|
+
return Object.keys(config).length ? config : void 0;
|
|
1954
|
+
}
|
|
1955
|
+
function readWidgetEsbuildConfig(widgets) {
|
|
1956
|
+
const esbuild3 = readObjectProperty3(widgets, "esbuild");
|
|
1957
|
+
if (!esbuild3) {
|
|
1958
|
+
return void 0;
|
|
1959
|
+
}
|
|
1960
|
+
const config = {};
|
|
1961
|
+
const alias = readStringRecordProperty(esbuild3, "alias");
|
|
1962
|
+
const define = readStringRecordProperty(esbuild3, "define");
|
|
1963
|
+
const external = readStringArrayProperty3(esbuild3, "external");
|
|
1964
|
+
const loader = readStringRecordProperty(esbuild3, "loader");
|
|
1965
|
+
const conditions = readStringArrayProperty3(esbuild3, "conditions");
|
|
1966
|
+
const mainFields = readStringArrayProperty3(esbuild3, "mainFields");
|
|
1967
|
+
const jsx = readStringProperty3(esbuild3, "jsx");
|
|
1968
|
+
const jsxImportSource = readStringProperty3(esbuild3, "jsxImportSource");
|
|
1969
|
+
if (alias) config.alias = alias;
|
|
1970
|
+
if (define) config.define = define;
|
|
1971
|
+
if (external) config.external = external;
|
|
1972
|
+
if (loader) config.loader = loader;
|
|
1973
|
+
if (conditions) config.conditions = conditions;
|
|
1974
|
+
if (mainFields) config.mainFields = mainFields;
|
|
1975
|
+
if (jsx === "automatic" || jsx === "transform" || jsx === "preserve") {
|
|
1976
|
+
config.jsx = jsx;
|
|
1977
|
+
}
|
|
1978
|
+
if (jsxImportSource) config.jsxImportSource = jsxImportSource;
|
|
1979
|
+
return Object.keys(config).length ? config : void 0;
|
|
1980
|
+
}
|
|
1677
1981
|
function defaultCompilerConfig() {
|
|
1678
1982
|
return {
|
|
1983
|
+
build: {},
|
|
1679
1984
|
resources: {
|
|
1680
1985
|
subscribe: false,
|
|
1681
1986
|
listChanged: false
|
|
@@ -1687,11 +1992,26 @@ function defaultCompilerConfig() {
|
|
|
1687
1992
|
listChanged: false
|
|
1688
1993
|
},
|
|
1689
1994
|
pagination: {
|
|
1690
|
-
pageSize:
|
|
1995
|
+
pageSize: 50,
|
|
1691
1996
|
hasOverride: false
|
|
1692
1997
|
}
|
|
1693
1998
|
};
|
|
1694
1999
|
}
|
|
2000
|
+
function readTargetNested(definition, section, propertyName) {
|
|
2001
|
+
const value = readStringNested(definition, section, propertyName);
|
|
2002
|
+
return value === "mcp" || value === "chatgpt" || value === "claude" ? value : void 0;
|
|
2003
|
+
}
|
|
2004
|
+
function readHostNested(definition, section, propertyName) {
|
|
2005
|
+
const value = readStringNested(definition, section, propertyName);
|
|
2006
|
+
return value === "node" || value === "vercel" ? value : void 0;
|
|
2007
|
+
}
|
|
2008
|
+
function readStringNested(definition, section, propertyName) {
|
|
2009
|
+
const object = readObjectProperty3(definition, section);
|
|
2010
|
+
if (!object) {
|
|
2011
|
+
return void 0;
|
|
2012
|
+
}
|
|
2013
|
+
return readStringProperty3(object, propertyName);
|
|
2014
|
+
}
|
|
1695
2015
|
function readBooleanNested(definition, section, propertyName) {
|
|
1696
2016
|
const object = readObjectProperty3(definition, section);
|
|
1697
2017
|
if (!object) {
|
|
@@ -1722,6 +2042,9 @@ function readNumberNested(definition, section, propertyName) {
|
|
|
1722
2042
|
return initializer && Node5.isNumericLiteral(initializer) ? Number(initializer.getLiteralText()) : void 0;
|
|
1723
2043
|
}
|
|
1724
2044
|
function readObjectProperty3(definition, propertyName) {
|
|
2045
|
+
if (!definition) {
|
|
2046
|
+
return void 0;
|
|
2047
|
+
}
|
|
1725
2048
|
const property = definition.getProperty(propertyName);
|
|
1726
2049
|
if (!property || !Node5.isPropertyAssignment(property)) {
|
|
1727
2050
|
return void 0;
|
|
@@ -1729,40 +2052,79 @@ function readObjectProperty3(definition, propertyName) {
|
|
|
1729
2052
|
const initializer = unwrapExpression(property.getInitializer());
|
|
1730
2053
|
return initializer && Node5.isObjectLiteralExpression(initializer) ? initializer : void 0;
|
|
1731
2054
|
}
|
|
2055
|
+
function readStringProperty3(definition, propertyName) {
|
|
2056
|
+
const property = definition.getProperty(propertyName);
|
|
2057
|
+
if (!property || !Node5.isPropertyAssignment(property)) {
|
|
2058
|
+
return void 0;
|
|
2059
|
+
}
|
|
2060
|
+
const initializer = unwrapExpression(property.getInitializer());
|
|
2061
|
+
return initializer && Node5.isStringLiteral(initializer) ? initializer.getLiteralText() : void 0;
|
|
2062
|
+
}
|
|
2063
|
+
function readStringArrayProperty3(definition, propertyName) {
|
|
2064
|
+
const property = definition.getProperty(propertyName);
|
|
2065
|
+
if (!property || !Node5.isPropertyAssignment(property)) {
|
|
2066
|
+
return void 0;
|
|
2067
|
+
}
|
|
2068
|
+
const initializer = unwrapExpression(property.getInitializer());
|
|
2069
|
+
if (!initializer || !Node5.isArrayLiteralExpression(initializer)) {
|
|
2070
|
+
return void 0;
|
|
2071
|
+
}
|
|
2072
|
+
const values = initializer.getElements().map((element) => {
|
|
2073
|
+
const unwrapped = unwrapExpression(element);
|
|
2074
|
+
return unwrapped && Node5.isStringLiteral(unwrapped) ? unwrapped.getLiteralText() : void 0;
|
|
2075
|
+
});
|
|
2076
|
+
return values.every((value) => value !== void 0) ? values : void 0;
|
|
2077
|
+
}
|
|
2078
|
+
function readStringRecordProperty(definition, propertyName) {
|
|
2079
|
+
const object = readObjectProperty3(definition, propertyName);
|
|
2080
|
+
if (!object) {
|
|
2081
|
+
return void 0;
|
|
2082
|
+
}
|
|
2083
|
+
const record = {};
|
|
2084
|
+
for (const property of object.getProperties()) {
|
|
2085
|
+
if (!Node5.isPropertyAssignment(property)) {
|
|
2086
|
+
return void 0;
|
|
2087
|
+
}
|
|
2088
|
+
const initializer = unwrapExpression(property.getInitializer());
|
|
2089
|
+
if (!initializer || !Node5.isStringLiteral(initializer)) {
|
|
2090
|
+
return void 0;
|
|
2091
|
+
}
|
|
2092
|
+
record[property.getName().replace(/^["']|["']$/g, "")] = initializer.getLiteralText();
|
|
2093
|
+
}
|
|
2094
|
+
return record;
|
|
2095
|
+
}
|
|
1732
2096
|
function hasProperty(definition, propertyName) {
|
|
1733
2097
|
return Boolean(definition?.getProperty(propertyName));
|
|
1734
2098
|
}
|
|
1735
2099
|
|
|
1736
2100
|
// packages/compiler/src/diagnostics.ts
|
|
1737
|
-
import { existsSync as
|
|
2101
|
+
import { existsSync as existsSync4 } from "fs";
|
|
1738
2102
|
import { readFile as readFile2 } from "fs/promises";
|
|
1739
|
-
import
|
|
1740
|
-
import path6 from "path";
|
|
2103
|
+
import path7 from "path";
|
|
1741
2104
|
async function collectProjectDiagnostics(rootDir, input) {
|
|
1742
2105
|
const diagnostics = [];
|
|
1743
2106
|
const tools = Array.isArray(input) ? input : input.tools;
|
|
1744
2107
|
const resources = Array.isArray(input) ? [] : input.resources ?? [];
|
|
1745
2108
|
const prompts = Array.isArray(input) ? [] : input.prompts ?? [];
|
|
1746
2109
|
const config = Array.isArray(input) ? void 0 : input.config;
|
|
1747
|
-
const hasAuthConfig =
|
|
1748
|
-
const hasOpenAiAppsSdkUi = canResolveOpenAiAppsSdkUi(rootDir);
|
|
2110
|
+
const hasAuthConfig = existsSync4(path7.join(rootDir, "auth.ts"));
|
|
1749
2111
|
for (const entry of tools) {
|
|
1750
|
-
const toolPath =
|
|
2112
|
+
const toolPath = path7.join(rootDir, entry.sourceFile);
|
|
1751
2113
|
const toolSource = await readFile2(toolPath, "utf8");
|
|
1752
|
-
diagnostics.push(...diagnoseToolSource(
|
|
2114
|
+
diagnostics.push(...diagnoseToolSource(entry, toolSource, hasAuthConfig));
|
|
1753
2115
|
if (entry.widget) {
|
|
1754
|
-
const widgetPath =
|
|
2116
|
+
const widgetPath = path7.join(rootDir, entry.widget.sourceFile);
|
|
1755
2117
|
const widgetSource = await readFile2(widgetPath, "utf8");
|
|
1756
|
-
diagnostics.push(...diagnoseWidgetSource(rootDir, entry, widgetSource
|
|
2118
|
+
diagnostics.push(...diagnoseWidgetSource(rootDir, entry, widgetSource));
|
|
1757
2119
|
}
|
|
1758
2120
|
}
|
|
1759
2121
|
for (const entry of resources) {
|
|
1760
|
-
const resourcePath =
|
|
2122
|
+
const resourcePath = path7.join(rootDir, entry.sourceFile);
|
|
1761
2123
|
const resourceSource = await readFile2(resourcePath, "utf8");
|
|
1762
2124
|
diagnostics.push(...diagnoseResourceSource(entry, resourceSource));
|
|
1763
2125
|
}
|
|
1764
2126
|
for (const entry of prompts) {
|
|
1765
|
-
const promptPath =
|
|
2127
|
+
const promptPath = path7.join(rootDir, entry.sourceFile);
|
|
1766
2128
|
const promptSource = await readFile2(promptPath, "utf8");
|
|
1767
2129
|
diagnostics.push(...diagnosePromptSource(entry, promptSource));
|
|
1768
2130
|
}
|
|
@@ -1777,7 +2139,7 @@ function formatDiagnostic(diagnostic) {
|
|
|
1777
2139
|
hint: ${diagnostic.hint}` : "";
|
|
1778
2140
|
return `${location} - ${diagnostic.severity} ${diagnostic.code}: ${diagnostic.message}${hint}`;
|
|
1779
2141
|
}
|
|
1780
|
-
function diagnoseToolSource(
|
|
2142
|
+
function diagnoseToolSource(entry, source, hasAuthConfig) {
|
|
1781
2143
|
const diagnostics = [];
|
|
1782
2144
|
const toolLocation = locate(source, "tool({");
|
|
1783
2145
|
if (!entry.description.trim().startsWith("Use this when")) {
|
|
@@ -1854,7 +2216,7 @@ function diagnoseToolSource(rootDir, entry, source, hasAuthConfig) {
|
|
|
1854
2216
|
}
|
|
1855
2217
|
return diagnostics.filter((diagnostic) => !isIgnored(source, diagnostic.code));
|
|
1856
2218
|
}
|
|
1857
|
-
function diagnoseWidgetSource(_rootDir, entry, source
|
|
2219
|
+
function diagnoseWidgetSource(_rootDir, entry, source) {
|
|
1858
2220
|
const diagnostics = [];
|
|
1859
2221
|
const widgetFile = entry.widget?.sourceFile;
|
|
1860
2222
|
if (!widgetFile) {
|
|
@@ -1890,16 +2252,6 @@ function diagnoseWidgetSource(_rootDir, entry, source, hasOpenAiAppsSdkUi) {
|
|
|
1890
2252
|
hint: "Use @sidecar-ai/native for portable primitives. Keep @sidecar-ai/openai/components for widgets intentionally targeted to ChatGPT."
|
|
1891
2253
|
});
|
|
1892
2254
|
}
|
|
1893
|
-
if (/@sidecar-ai\/openai\/components/.test(source) && !hasOpenAiAppsSdkUi && !isIgnored(source, "SIDECAR_OPENAI_UI_SDK_MISSING")) {
|
|
1894
|
-
diagnostics.push({
|
|
1895
|
-
severity: "error",
|
|
1896
|
-
code: "SIDECAR_OPENAI_UI_SDK_MISSING",
|
|
1897
|
-
message: `Widget for "${entry.id}" imports @sidecar-ai/openai/components but @openai/apps-sdk-ui is not installed.`,
|
|
1898
|
-
filePath: widgetFile,
|
|
1899
|
-
...locate(source, "@sidecar-ai/openai/components"),
|
|
1900
|
-
hint: "Install it with: npm install @openai/apps-sdk-ui"
|
|
1901
|
-
});
|
|
1902
|
-
}
|
|
1903
2255
|
if (entry.widget?.variant !== "anthropic" && /@sidecar-ai\/anthropic\/components/.test(source) && !isIgnored(source, "SIDECAR_ANTHROPIC_COMPONENT_CROSS_HOST")) {
|
|
1904
2256
|
diagnostics.push({
|
|
1905
2257
|
severity: "warning",
|
|
@@ -1989,14 +2341,6 @@ function hasCompanyKnowledgeShape(schema) {
|
|
|
1989
2341
|
const id = properties.id;
|
|
1990
2342
|
return query?.type === "string" || url?.type === "string" || id?.type === "string";
|
|
1991
2343
|
}
|
|
1992
|
-
function canResolveOpenAiAppsSdkUi(rootDir) {
|
|
1993
|
-
try {
|
|
1994
|
-
createRequire(path6.join(rootDir, "package.json")).resolve("@openai/apps-sdk-ui/css");
|
|
1995
|
-
return true;
|
|
1996
|
-
} catch {
|
|
1997
|
-
return false;
|
|
1998
|
-
}
|
|
1999
|
-
}
|
|
2000
2344
|
function locate(source, needle) {
|
|
2001
2345
|
const index = Math.max(0, source.indexOf(needle));
|
|
2002
2346
|
const prefix = source.slice(0, index);
|
|
@@ -2012,21 +2356,21 @@ function isIgnored(source, code) {
|
|
|
2012
2356
|
|
|
2013
2357
|
// packages/compiler/src/generated.ts
|
|
2014
2358
|
import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
|
|
2015
|
-
import
|
|
2359
|
+
import path8 from "path";
|
|
2016
2360
|
async function writeGeneratedTypes(rootDir, tools) {
|
|
2017
|
-
const generatedDir =
|
|
2361
|
+
const generatedDir = path8.join(rootDir, ".sidecar", "generated");
|
|
2018
2362
|
await mkdir2(generatedDir, { recursive: true });
|
|
2019
2363
|
const appCallableTools = tools.filter(isAppCallable);
|
|
2020
2364
|
const methodNames = uniqueMethodNames(appCallableTools.map((entry) => toIdentifier(entry.id)));
|
|
2021
2365
|
const imports = appCallableTools.map(
|
|
2022
|
-
(entry, index) => `import type tool${index} from ${JSON.stringify(toImportSpecifier(generatedDir,
|
|
2366
|
+
(entry, index) => `import type tool${index} from ${JSON.stringify(toImportSpecifier(generatedDir, path8.join(rootDir, entry.sourceFile), { extension: "js" }))};`
|
|
2023
2367
|
).join("\n");
|
|
2024
2368
|
const ids = appCallableTools.map((entry, index) => ` ${methodNames[index]}: ${JSON.stringify(entry.id)}`).join(",\n");
|
|
2025
2369
|
const toolTypes = appCallableTools.map(
|
|
2026
|
-
(
|
|
2370
|
+
(_entry, index) => ` ${methodNames[index]}(params: ToolParams<typeof tool${index}>): Promise<ToolOutput<typeof tool${index}>>;`
|
|
2027
2371
|
).join("\n");
|
|
2028
2372
|
await writeFile2(
|
|
2029
|
-
|
|
2373
|
+
path8.join(generatedDir, "tools.ts"),
|
|
2030
2374
|
`/**
|
|
2031
2375
|
* Generated Sidecar widget tool client.
|
|
2032
2376
|
*
|
|
@@ -2074,19 +2418,15 @@ function uniqueMethodNames(names) {
|
|
|
2074
2418
|
});
|
|
2075
2419
|
}
|
|
2076
2420
|
|
|
2077
|
-
// packages/compiler/src/plugins.ts
|
|
2078
|
-
import { mkdir as mkdir7, writeFile as writeFile7 } from "fs/promises";
|
|
2079
|
-
import path14 from "path";
|
|
2080
|
-
|
|
2081
2421
|
// packages/compiler/src/identity.ts
|
|
2082
2422
|
import { readFile as readFile3 } from "fs/promises";
|
|
2083
|
-
import
|
|
2423
|
+
import path9 from "path";
|
|
2084
2424
|
async function loadProjectIdentity(rootDir) {
|
|
2085
|
-
const packageJson = await readOptionalJson(
|
|
2425
|
+
const packageJson = await readOptionalJson(path9.join(rootDir, "package.json"));
|
|
2086
2426
|
const configText = await readOptionalText(
|
|
2087
|
-
|
|
2427
|
+
path9.join(rootDir, "sidecar.config.ts")
|
|
2088
2428
|
);
|
|
2089
|
-
const name = readConfigString(configText, "name") ?? packageJson?.name ??
|
|
2429
|
+
const name = readConfigString(configText, "name") ?? packageJson?.name ?? path9.basename(rootDir);
|
|
2090
2430
|
const version = readConfigString(configText, "version") ?? packageJson?.version ?? "0.0.0-dev";
|
|
2091
2431
|
const description = readConfigString(configText, "description") ?? packageJson?.description ?? `${name} Sidecar app.`;
|
|
2092
2432
|
return {
|
|
@@ -2118,30 +2458,34 @@ function readConfigString(configText, key) {
|
|
|
2118
2458
|
return match?.[1];
|
|
2119
2459
|
}
|
|
2120
2460
|
|
|
2461
|
+
// packages/compiler/src/plugins.ts
|
|
2462
|
+
import { mkdir as mkdir7, writeFile as writeFile7 } from "fs/promises";
|
|
2463
|
+
import path15 from "path";
|
|
2464
|
+
|
|
2121
2465
|
// packages/compiler/src/plugin-components/agents.ts
|
|
2122
2466
|
import { mkdir as mkdir3, readdir as readdir2, readFile as readFile4, writeFile as writeFile3 } from "fs/promises";
|
|
2123
|
-
import
|
|
2467
|
+
import path10 from "path";
|
|
2124
2468
|
async function emitClaudeAgents(rootDir, destination) {
|
|
2125
|
-
const source =
|
|
2469
|
+
const source = path10.join(rootDir, "agents");
|
|
2126
2470
|
if (!existsSyncSafe(source)) {
|
|
2127
2471
|
return;
|
|
2128
2472
|
}
|
|
2129
2473
|
const entries = await readdir2(source, { withFileTypes: true });
|
|
2130
2474
|
const agentDirs = entries.filter(
|
|
2131
|
-
(entry) => entry.isDirectory() && existsSyncSafe(
|
|
2475
|
+
(entry) => entry.isDirectory() && existsSyncSafe(path10.join(source, entry.name, "agent.ts"))
|
|
2132
2476
|
);
|
|
2133
2477
|
if (!agentDirs.length) {
|
|
2134
2478
|
return;
|
|
2135
2479
|
}
|
|
2136
2480
|
await mkdir3(destination, { recursive: true });
|
|
2137
2481
|
for (const entry of agentDirs) {
|
|
2138
|
-
const sourceText = await readFile4(
|
|
2482
|
+
const sourceText = await readFile4(path10.join(source, entry.name, "agent.ts"), "utf8");
|
|
2139
2483
|
const markdown = parseClaudeAgent(
|
|
2140
2484
|
sourceText,
|
|
2141
2485
|
entry.name
|
|
2142
2486
|
);
|
|
2143
2487
|
const agentName = readObjectString(sourceText, "name") ?? entry.name;
|
|
2144
|
-
await writeFile3(
|
|
2488
|
+
await writeFile3(path10.join(destination, `${safeFileStem(agentName)}.md`), markdown);
|
|
2145
2489
|
}
|
|
2146
2490
|
}
|
|
2147
2491
|
function parseClaudeAgent(source, fallbackName) {
|
|
@@ -2170,41 +2514,41 @@ ${prompt.trim()}
|
|
|
2170
2514
|
|
|
2171
2515
|
// packages/compiler/src/plugin-components/commands.ts
|
|
2172
2516
|
import { cp, lstat, mkdir as mkdir4, readdir as readdir3, readFile as readFile5, writeFile as writeFile4 } from "fs/promises";
|
|
2173
|
-
import
|
|
2517
|
+
import path11 from "path";
|
|
2174
2518
|
async function copyCommands(rootDir, destination) {
|
|
2175
|
-
const source =
|
|
2519
|
+
const source = path11.join(rootDir, "commands");
|
|
2176
2520
|
if (!existsSyncSafe(source)) {
|
|
2177
2521
|
return;
|
|
2178
2522
|
}
|
|
2179
2523
|
await mkdir4(destination, { recursive: true });
|
|
2180
2524
|
const entries = await readdir3(source, { withFileTypes: true });
|
|
2181
2525
|
for (const entry of entries) {
|
|
2182
|
-
const sourcePath =
|
|
2526
|
+
const sourcePath = path11.join(source, entry.name);
|
|
2183
2527
|
if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
2184
2528
|
if (await safeCommandCopyFilter(sourcePath)) {
|
|
2185
|
-
await cp(sourcePath,
|
|
2529
|
+
await cp(sourcePath, path11.join(destination, safeFileStem(entry.name.replace(/\.md$/, "")) + ".md"));
|
|
2186
2530
|
}
|
|
2187
2531
|
continue;
|
|
2188
2532
|
}
|
|
2189
2533
|
if (!entry.isDirectory()) {
|
|
2190
2534
|
continue;
|
|
2191
2535
|
}
|
|
2192
|
-
const dynamicCommand =
|
|
2536
|
+
const dynamicCommand = path11.join(sourcePath, "command.ts");
|
|
2193
2537
|
if (existsSyncSafe(dynamicCommand)) {
|
|
2194
2538
|
const sourceText = await readFile5(dynamicCommand, "utf8");
|
|
2195
2539
|
const markdown = parseDynamicCommand(sourceText, entry.name);
|
|
2196
2540
|
const commandName = readObjectString(sourceText, "name") ?? entry.name;
|
|
2197
|
-
await writeFile4(
|
|
2541
|
+
await writeFile4(path11.join(destination, `${safeFileStem(commandName)}.md`), markdown);
|
|
2198
2542
|
continue;
|
|
2199
2543
|
}
|
|
2200
|
-
await cp(sourcePath,
|
|
2544
|
+
await cp(sourcePath, path11.join(destination, entry.name), {
|
|
2201
2545
|
recursive: true,
|
|
2202
2546
|
filter: safeCommandCopyFilter
|
|
2203
2547
|
});
|
|
2204
2548
|
}
|
|
2205
2549
|
}
|
|
2206
2550
|
async function safeCommandCopyFilter(sourcePath) {
|
|
2207
|
-
const basename =
|
|
2551
|
+
const basename = path11.basename(sourcePath);
|
|
2208
2552
|
if (basename === ".env" || basename.startsWith(".env.") || basename === "node_modules" || basename === ".git") {
|
|
2209
2553
|
return false;
|
|
2210
2554
|
}
|
|
@@ -2233,32 +2577,32 @@ ${Object.entries(frontmatter).map(([key, value]) => `${key}: ${yamlScalar(value)
|
|
|
2233
2577
|
|
|
2234
2578
|
// packages/compiler/src/plugin-components/hooks.ts
|
|
2235
2579
|
import { mkdir as mkdir5, readdir as readdir4, readFile as readFile6, writeFile as writeFile5 } from "fs/promises";
|
|
2236
|
-
import
|
|
2580
|
+
import path12 from "path";
|
|
2237
2581
|
import {
|
|
2238
2582
|
Node as Node6,
|
|
2239
2583
|
Project as Project2,
|
|
2240
2584
|
SyntaxKind as SyntaxKind4
|
|
2241
2585
|
} from "ts-morph";
|
|
2242
2586
|
async function copyHooks(rootDir, destination) {
|
|
2243
|
-
const source =
|
|
2587
|
+
const source = path12.join(rootDir, "hooks");
|
|
2244
2588
|
if (!existsSyncSafe(source)) {
|
|
2245
2589
|
return;
|
|
2246
2590
|
}
|
|
2247
2591
|
const entries = await readdir4(source, { withFileTypes: true });
|
|
2248
2592
|
const hookDirs = entries.filter(
|
|
2249
|
-
(entry) => entry.isDirectory() && existsSyncSafe(
|
|
2593
|
+
(entry) => entry.isDirectory() && existsSyncSafe(path12.join(source, entry.name, "hook.ts"))
|
|
2250
2594
|
);
|
|
2251
2595
|
if (!hookDirs.length) {
|
|
2252
2596
|
return;
|
|
2253
2597
|
}
|
|
2254
2598
|
const config = {};
|
|
2255
2599
|
for (const entry of hookDirs) {
|
|
2256
|
-
const filePath =
|
|
2600
|
+
const filePath = path12.join(source, entry.name, "hook.ts");
|
|
2257
2601
|
mergeHooks(config, parseHookFile(await readFile6(filePath, "utf8"), entry.name));
|
|
2258
2602
|
}
|
|
2259
2603
|
await mkdir5(destination, { recursive: true });
|
|
2260
2604
|
await writeFile5(
|
|
2261
|
-
|
|
2605
|
+
path12.join(destination, "hooks.json"),
|
|
2262
2606
|
`${JSON.stringify(config, null, 2)}
|
|
2263
2607
|
`
|
|
2264
2608
|
);
|
|
@@ -2285,7 +2629,7 @@ function parseHookFile(source, fallbackName) {
|
|
|
2285
2629
|
throw new Error(`hooks/${fallbackName}/hook.ts must default-export hook({ ... }) or hooks({ ... }).`);
|
|
2286
2630
|
}
|
|
2287
2631
|
function parseHookDefinition(definition, fallbackName) {
|
|
2288
|
-
const event =
|
|
2632
|
+
const event = readStringProperty4(definition, "event");
|
|
2289
2633
|
if (!event) {
|
|
2290
2634
|
throw new Error(`hooks/${fallbackName}/hook.ts must declare an event string.`);
|
|
2291
2635
|
}
|
|
@@ -2295,7 +2639,7 @@ function parseHookDefinition(definition, fallbackName) {
|
|
|
2295
2639
|
}
|
|
2296
2640
|
return {
|
|
2297
2641
|
event,
|
|
2298
|
-
matcher:
|
|
2642
|
+
matcher: readStringProperty4(definition, "matcher"),
|
|
2299
2643
|
run: hooks
|
|
2300
2644
|
};
|
|
2301
2645
|
}
|
|
@@ -2315,7 +2659,7 @@ function parseHooksDefinition(definition, fallbackName) {
|
|
|
2315
2659
|
throw new Error(`hooks/${fallbackName}/hook.ts event "${event}" entries must be objects.`);
|
|
2316
2660
|
}
|
|
2317
2661
|
return stripUndefined2({
|
|
2318
|
-
matcher:
|
|
2662
|
+
matcher: readStringProperty4(entry, "matcher"),
|
|
2319
2663
|
hooks: readHookArray(entry, "run", fallbackName)
|
|
2320
2664
|
});
|
|
2321
2665
|
});
|
|
@@ -2338,7 +2682,7 @@ function parseHookHandlers(handlers, fallbackName) {
|
|
|
2338
2682
|
}
|
|
2339
2683
|
function parseHookHandler(handler, fallbackName) {
|
|
2340
2684
|
if (Node6.isObjectLiteralExpression(handler)) {
|
|
2341
|
-
const type =
|
|
2685
|
+
const type = readStringProperty4(handler, "type");
|
|
2342
2686
|
if (type !== "command" && type !== "http") {
|
|
2343
2687
|
throw new Error(`hooks/${fallbackName}/hook.ts hook handlers need type "command" or "http".`);
|
|
2344
2688
|
}
|
|
@@ -2388,7 +2732,7 @@ function objectLiteralToRecord(object) {
|
|
|
2388
2732
|
}
|
|
2389
2733
|
return stripUndefined2(record);
|
|
2390
2734
|
}
|
|
2391
|
-
function
|
|
2735
|
+
function readStringProperty4(object, propertyName) {
|
|
2392
2736
|
const property = object.getProperty(propertyName);
|
|
2393
2737
|
if (!property || !Node6.isPropertyAssignment(property)) {
|
|
2394
2738
|
return void 0;
|
|
@@ -2432,7 +2776,7 @@ function mergeHooks(target, source) {
|
|
|
2432
2776
|
|
|
2433
2777
|
// packages/compiler/src/plugin-components/passthrough.ts
|
|
2434
2778
|
import { cp as cp2, lstat as lstat2 } from "fs/promises";
|
|
2435
|
-
import
|
|
2779
|
+
import path13 from "path";
|
|
2436
2780
|
async function copyClaudePassthroughDirectories(rootDir, pluginDir) {
|
|
2437
2781
|
for (const directory of [
|
|
2438
2782
|
"assets",
|
|
@@ -2441,18 +2785,18 @@ async function copyClaudePassthroughDirectories(rootDir, pluginDir) {
|
|
|
2441
2785
|
"output-styles",
|
|
2442
2786
|
"themes"
|
|
2443
2787
|
]) {
|
|
2444
|
-
const source =
|
|
2788
|
+
const source = path13.join(rootDir, directory);
|
|
2445
2789
|
if (!existsSyncSafe(source)) {
|
|
2446
2790
|
continue;
|
|
2447
2791
|
}
|
|
2448
|
-
await cp2(source,
|
|
2792
|
+
await cp2(source, path13.join(pluginDir, directory), {
|
|
2449
2793
|
recursive: true,
|
|
2450
2794
|
filter: safePluginCopyFilter
|
|
2451
2795
|
});
|
|
2452
2796
|
}
|
|
2453
2797
|
}
|
|
2454
2798
|
async function safePluginCopyFilter(sourcePath) {
|
|
2455
|
-
const basename =
|
|
2799
|
+
const basename = path13.basename(sourcePath);
|
|
2456
2800
|
if (basename === "node_modules" || basename === ".git" || basename === ".env" || basename.startsWith(".env.")) {
|
|
2457
2801
|
return false;
|
|
2458
2802
|
}
|
|
@@ -2461,11 +2805,11 @@ async function safePluginCopyFilter(sourcePath) {
|
|
|
2461
2805
|
}
|
|
2462
2806
|
|
|
2463
2807
|
// packages/compiler/src/plugin-components/skills.ts
|
|
2464
|
-
import { existsSync as
|
|
2808
|
+
import { existsSync as existsSync5 } from "fs";
|
|
2465
2809
|
import { cp as cp3, lstat as lstat3, mkdir as mkdir6, readdir as readdir5, readFile as readFile7, writeFile as writeFile6 } from "fs/promises";
|
|
2466
|
-
import
|
|
2810
|
+
import path14 from "path";
|
|
2467
2811
|
async function copySkills(rootDir, destination) {
|
|
2468
|
-
const source =
|
|
2812
|
+
const source = path14.join(rootDir, "skills");
|
|
2469
2813
|
if (!existsSyncSafe(source)) {
|
|
2470
2814
|
return;
|
|
2471
2815
|
}
|
|
@@ -2475,27 +2819,27 @@ async function copySkills(rootDir, destination) {
|
|
|
2475
2819
|
if (!entry.isDirectory()) {
|
|
2476
2820
|
continue;
|
|
2477
2821
|
}
|
|
2478
|
-
const sourceDir =
|
|
2479
|
-
const destinationDir =
|
|
2480
|
-
const staticSkill =
|
|
2481
|
-
const dynamicSkill =
|
|
2822
|
+
const sourceDir = path14.join(source, entry.name);
|
|
2823
|
+
const destinationDir = path14.join(destination, entry.name);
|
|
2824
|
+
const staticSkill = path14.join(sourceDir, "SKILL.md");
|
|
2825
|
+
const dynamicSkill = path14.join(sourceDir, "skill.ts");
|
|
2482
2826
|
await mkdir6(destinationDir, { recursive: true });
|
|
2483
|
-
if (
|
|
2827
|
+
if (existsSync5(staticSkill)) {
|
|
2484
2828
|
await cp3(sourceDir, destinationDir, {
|
|
2485
2829
|
recursive: true,
|
|
2486
|
-
filter: async (sourcePath) => !sourcePath.endsWith(`${
|
|
2830
|
+
filter: async (sourcePath) => !sourcePath.endsWith(`${path14.sep}skill.ts`) && await safeSkillCopyFilter(sourcePath)
|
|
2487
2831
|
});
|
|
2488
|
-
} else if (
|
|
2832
|
+
} else if (existsSync5(dynamicSkill)) {
|
|
2489
2833
|
const generated = parseDynamicSkill(
|
|
2490
2834
|
await readFile7(dynamicSkill, "utf8"),
|
|
2491
2835
|
entry.name
|
|
2492
2836
|
);
|
|
2493
|
-
await writeFile6(
|
|
2837
|
+
await writeFile6(path14.join(destinationDir, "SKILL.md"), generated);
|
|
2494
2838
|
}
|
|
2495
2839
|
}
|
|
2496
2840
|
}
|
|
2497
2841
|
async function safeSkillCopyFilter(sourcePath) {
|
|
2498
|
-
const basename =
|
|
2842
|
+
const basename = path14.basename(sourcePath);
|
|
2499
2843
|
if (basename === ".env" || basename.startsWith(".env.") || basename === "node_modules" || basename === ".git") {
|
|
2500
2844
|
return false;
|
|
2501
2845
|
}
|
|
@@ -2521,25 +2865,25 @@ async function buildPluginPackages(rootDir, outRoot, manifest) {
|
|
|
2521
2865
|
await buildClaudePlugin(rootDir, outRoot, identity, manifest);
|
|
2522
2866
|
}
|
|
2523
2867
|
async function buildClaudePlugin(rootDir, outRoot, identity, manifest) {
|
|
2524
|
-
const pluginDir =
|
|
2525
|
-
await mkdir7(
|
|
2526
|
-
await copySkills(rootDir,
|
|
2527
|
-
await copyCommands(rootDir,
|
|
2528
|
-
await copyHooks(rootDir,
|
|
2529
|
-
await emitClaudeAgents(rootDir,
|
|
2868
|
+
const pluginDir = path15.join(outRoot, "claude-plugin");
|
|
2869
|
+
await mkdir7(path15.join(pluginDir, ".claude-plugin"), { recursive: true });
|
|
2870
|
+
await copySkills(rootDir, path15.join(pluginDir, "skills"));
|
|
2871
|
+
await copyCommands(rootDir, path15.join(pluginDir, "commands"));
|
|
2872
|
+
await copyHooks(rootDir, path15.join(pluginDir, "hooks"));
|
|
2873
|
+
await emitClaudeAgents(rootDir, path15.join(pluginDir, "agents"));
|
|
2530
2874
|
await copyClaudePassthroughDirectories(rootDir, pluginDir);
|
|
2531
|
-
await writeJson(
|
|
2875
|
+
await writeJson(path15.join(pluginDir, ".claude-plugin", "plugin.json"), {
|
|
2532
2876
|
name: identity.slug,
|
|
2533
2877
|
version: identity.version,
|
|
2534
2878
|
description: identity.description,
|
|
2535
2879
|
displayName: identity.name,
|
|
2536
2880
|
installationPreference: "available"
|
|
2537
2881
|
});
|
|
2538
|
-
await writeJson(
|
|
2882
|
+
await writeJson(path15.join(pluginDir, "version.json"), {
|
|
2539
2883
|
version: identity.version,
|
|
2540
2884
|
generatedBy: "sidecar"
|
|
2541
2885
|
});
|
|
2542
|
-
await writeJson(
|
|
2886
|
+
await writeJson(path15.join(pluginDir, ".mcp.json"), {
|
|
2543
2887
|
mcpServers: {
|
|
2544
2888
|
[identity.slug]: {
|
|
2545
2889
|
type: "http",
|
|
@@ -2547,7 +2891,7 @@ async function buildClaudePlugin(rootDir, outRoot, identity, manifest) {
|
|
|
2547
2891
|
}
|
|
2548
2892
|
}
|
|
2549
2893
|
});
|
|
2550
|
-
await writeJson(
|
|
2894
|
+
await writeJson(path15.join(pluginDir, "settings.json"), {
|
|
2551
2895
|
sidecar: {
|
|
2552
2896
|
tools: manifest.tools.map((entry) => entry.id),
|
|
2553
2897
|
resources: manifest.resources.map((entry) => entry.uri),
|
|
@@ -2555,21 +2899,21 @@ async function buildClaudePlugin(rootDir, outRoot, identity, manifest) {
|
|
|
2555
2899
|
}
|
|
2556
2900
|
});
|
|
2557
2901
|
await writeFile7(
|
|
2558
|
-
|
|
2902
|
+
path15.join(pluginDir, "README.md"),
|
|
2559
2903
|
`# ${identity.name} Claude Plugin
|
|
2560
2904
|
|
|
2561
2905
|
This package was generated by Sidecar.
|
|
2562
2906
|
|
|
2563
2907
|
Set \`SIDECAR_MCP_URL\` to the hosted MCP endpoint for this app.
|
|
2564
2908
|
|
|
2565
|
-
For local testing, run \`sidecar dev --tunnel\` and use the
|
|
2909
|
+
For local testing, run \`sidecar dev --tunnel\` and use the validated HTTPS MCP URL it prints. Temporary quick tunnels are public and best-effort; use a configured tunnel/domain or deployed preview for repeatable testing.
|
|
2566
2910
|
|
|
2567
2911
|
Claude/Cowork plugin-specific components such as skills, slash commands, agents, hooks, output styles, themes, monitors, assets, and bin scripts will be emitted here when the source project defines them.
|
|
2568
2912
|
`
|
|
2569
2913
|
);
|
|
2570
2914
|
}
|
|
2571
2915
|
async function writeJson(filePath, value) {
|
|
2572
|
-
await mkdir7(
|
|
2916
|
+
await mkdir7(path15.dirname(filePath), { recursive: true });
|
|
2573
2917
|
await writeFile7(
|
|
2574
2918
|
filePath,
|
|
2575
2919
|
`${JSON.stringify(stripUndefined2(value), null, 2)}
|
|
@@ -2579,13 +2923,13 @@ async function writeJson(filePath, value) {
|
|
|
2579
2923
|
|
|
2580
2924
|
// packages/compiler/src/prompts.ts
|
|
2581
2925
|
import { readdir as readdir6 } from "fs/promises";
|
|
2582
|
-
import
|
|
2926
|
+
import path16 from "path";
|
|
2583
2927
|
import {
|
|
2584
2928
|
Node as Node7,
|
|
2585
2929
|
SyntaxKind as SyntaxKind5
|
|
2586
2930
|
} from "ts-morph";
|
|
2587
2931
|
async function analyzeProjectPrompts(rootDir) {
|
|
2588
|
-
const promptFiles = await findPromptFiles(
|
|
2932
|
+
const promptFiles = await findPromptFiles(path16.join(rootDir, "prompts"));
|
|
2589
2933
|
if (promptFiles.length === 0) {
|
|
2590
2934
|
return [];
|
|
2591
2935
|
}
|
|
@@ -2597,7 +2941,7 @@ async function analyzeProjectPrompts(rootDir) {
|
|
|
2597
2941
|
function analyzePromptFile(sourceFile, rootDir) {
|
|
2598
2942
|
const definition = findPromptDefinition(sourceFile);
|
|
2599
2943
|
const absoluteFile = sourceFile.getFilePath();
|
|
2600
|
-
const directory =
|
|
2944
|
+
const directory = path16.basename(path16.dirname(absoluteFile));
|
|
2601
2945
|
const name = getOptionalStringProperty2(definition, "name") ?? safePathSegment(directory);
|
|
2602
2946
|
const title = getRequiredStringProperty2(definition, "title", sourceFile);
|
|
2603
2947
|
const description = getOptionalStringProperty2(definition, "description");
|
|
@@ -2613,7 +2957,7 @@ function analyzePromptFile(sourceFile, rootDir) {
|
|
|
2613
2957
|
throw new CompilerError(sourceFile, "prompt({ ... }) must include a run method.");
|
|
2614
2958
|
}
|
|
2615
2959
|
return {
|
|
2616
|
-
sourceFile:
|
|
2960
|
+
sourceFile: path16.relative(rootDir, absoluteFile),
|
|
2617
2961
|
directory,
|
|
2618
2962
|
name,
|
|
2619
2963
|
title,
|
|
@@ -2632,7 +2976,7 @@ async function findPromptFiles(promptsDir) {
|
|
|
2632
2976
|
if (!entry.isDirectory()) {
|
|
2633
2977
|
continue;
|
|
2634
2978
|
}
|
|
2635
|
-
const filePath =
|
|
2979
|
+
const filePath = path16.join(promptsDir, entry.name, "prompt.ts");
|
|
2636
2980
|
if (existsSyncSafe(filePath)) {
|
|
2637
2981
|
files.push(filePath);
|
|
2638
2982
|
}
|
|
@@ -2754,7 +3098,7 @@ function readIcons(definition) {
|
|
|
2754
3098
|
return [{
|
|
2755
3099
|
src,
|
|
2756
3100
|
mimeType: getOptionalStringProperty2(object, "mimeType"),
|
|
2757
|
-
sizes:
|
|
3101
|
+
sizes: readStringArrayProperty4(object, "sizes")
|
|
2758
3102
|
}];
|
|
2759
3103
|
});
|
|
2760
3104
|
return icons.length ? icons : void 0;
|
|
@@ -2767,7 +3111,7 @@ function readObjectProperty4(definition, propertyName) {
|
|
|
2767
3111
|
const initializer = unwrapExpression(property.getInitializer());
|
|
2768
3112
|
return initializer && Node7.isObjectLiteralExpression(initializer) ? initializer : void 0;
|
|
2769
3113
|
}
|
|
2770
|
-
function
|
|
3114
|
+
function readStringArrayProperty4(definition, propertyName) {
|
|
2771
3115
|
const property = definition.getProperty(propertyName);
|
|
2772
3116
|
if (!property || !Node7.isPropertyAssignment(property)) {
|
|
2773
3117
|
return void 0;
|
|
@@ -2781,13 +3125,13 @@ function readStringArrayProperty3(definition, propertyName) {
|
|
|
2781
3125
|
|
|
2782
3126
|
// packages/compiler/src/resources.ts
|
|
2783
3127
|
import { readdir as readdir7 } from "fs/promises";
|
|
2784
|
-
import
|
|
3128
|
+
import path17 from "path";
|
|
2785
3129
|
import {
|
|
2786
3130
|
Node as Node8,
|
|
2787
3131
|
SyntaxKind as SyntaxKind6
|
|
2788
3132
|
} from "ts-morph";
|
|
2789
3133
|
async function analyzeProjectResources(rootDir) {
|
|
2790
|
-
const resourceFiles = await findResourceFiles(
|
|
3134
|
+
const resourceFiles = await findResourceFiles(path17.join(rootDir, "resources"));
|
|
2791
3135
|
if (resourceFiles.length === 0) {
|
|
2792
3136
|
return [];
|
|
2793
3137
|
}
|
|
@@ -2799,7 +3143,7 @@ async function analyzeProjectResources(rootDir) {
|
|
|
2799
3143
|
function analyzeResourceFile(sourceFile, rootDir) {
|
|
2800
3144
|
const definition = findResourceDefinition(sourceFile);
|
|
2801
3145
|
const absoluteFile = sourceFile.getFilePath();
|
|
2802
|
-
const directory =
|
|
3146
|
+
const directory = path17.basename(path17.dirname(absoluteFile));
|
|
2803
3147
|
const defaultUri = `sidecar://resources/${safePathSegment(directory)}`;
|
|
2804
3148
|
const name = getRequiredStringProperty3(definition, "name", sourceFile);
|
|
2805
3149
|
const uri = getOptionalStringProperty3(definition, "uri") ?? defaultUri;
|
|
@@ -2817,7 +3161,7 @@ function analyzeResourceFile(sourceFile, rootDir) {
|
|
|
2817
3161
|
throw new CompilerError(sourceFile, "resource({ ... }) must include a read method.");
|
|
2818
3162
|
}
|
|
2819
3163
|
return {
|
|
2820
|
-
sourceFile:
|
|
3164
|
+
sourceFile: path17.relative(rootDir, absoluteFile),
|
|
2821
3165
|
directory,
|
|
2822
3166
|
uri,
|
|
2823
3167
|
name,
|
|
@@ -2840,7 +3184,7 @@ async function findResourceFiles(resourcesDir) {
|
|
|
2840
3184
|
if (!entry.isDirectory()) {
|
|
2841
3185
|
continue;
|
|
2842
3186
|
}
|
|
2843
|
-
const filePath =
|
|
3187
|
+
const filePath = path17.join(resourcesDir, entry.name, "resource.ts");
|
|
2844
3188
|
if (existsSyncSafe(filePath)) {
|
|
2845
3189
|
files.push(filePath);
|
|
2846
3190
|
}
|
|
@@ -2919,7 +3263,7 @@ function readAnnotations2(definition) {
|
|
|
2919
3263
|
return void 0;
|
|
2920
3264
|
}
|
|
2921
3265
|
const annotations = {};
|
|
2922
|
-
const audience =
|
|
3266
|
+
const audience = readStringArrayProperty5(initializer, "audience")?.filter((value) => value === "user" || value === "assistant");
|
|
2923
3267
|
const priority = getOptionalNumberProperty(initializer, "priority");
|
|
2924
3268
|
const lastModified = getOptionalStringProperty3(initializer, "lastModified");
|
|
2925
3269
|
if (audience?.length) annotations.audience = audience;
|
|
@@ -2948,7 +3292,7 @@ function readIcons2(definition) {
|
|
|
2948
3292
|
return [{
|
|
2949
3293
|
src,
|
|
2950
3294
|
mimeType: getOptionalStringProperty3(object, "mimeType"),
|
|
2951
|
-
sizes:
|
|
3295
|
+
sizes: readStringArrayProperty5(object, "sizes")
|
|
2952
3296
|
}];
|
|
2953
3297
|
});
|
|
2954
3298
|
return icons.length ? icons : void 0;
|
|
@@ -2961,7 +3305,7 @@ function readObjectProperty5(definition, propertyName) {
|
|
|
2961
3305
|
const initializer = unwrapExpression(property.getInitializer());
|
|
2962
3306
|
return initializer && Node8.isObjectLiteralExpression(initializer) ? initializer : void 0;
|
|
2963
3307
|
}
|
|
2964
|
-
function
|
|
3308
|
+
function readStringArrayProperty5(definition, propertyName) {
|
|
2965
3309
|
const property = definition.getProperty(propertyName);
|
|
2966
3310
|
if (!property || !Node8.isPropertyAssignment(property)) {
|
|
2967
3311
|
return void 0;
|
|
@@ -2973,15 +3317,332 @@ function readStringArrayProperty4(definition, propertyName) {
|
|
|
2973
3317
|
return initializer.getElements().filter(Node8.isStringLiteral).map((element) => element.getLiteralText());
|
|
2974
3318
|
}
|
|
2975
3319
|
|
|
3320
|
+
// packages/compiler/src/server-output.ts
|
|
3321
|
+
import { existsSync as existsSync6 } from "fs";
|
|
3322
|
+
import { mkdir as mkdir8, rm, writeFile as writeFile8 } from "fs/promises";
|
|
3323
|
+
import path18 from "path";
|
|
3324
|
+
import { build as esbuild2 } from "esbuild";
|
|
3325
|
+
var SERVER_ENTRYPOINT = "server/index.js";
|
|
3326
|
+
var VERCEL_FUNCTION_DIR = "functions/api/sidecar.func";
|
|
3327
|
+
var VERCEL_ENTRYPOINT = `${VERCEL_FUNCTION_DIR}/index.js`;
|
|
3328
|
+
var VERCEL_HANDLER_FILE = "index.js";
|
|
3329
|
+
async function buildServerOutput(rootDir, outDir, manifest, identity, host = "node", options = {}) {
|
|
3330
|
+
const cacheDir = path18.join(rootDir, ".sidecar", "cache", "server");
|
|
3331
|
+
await mkdir8(cacheDir, { recursive: true });
|
|
3332
|
+
const entryFile = path18.join(cacheDir, "index.ts");
|
|
3333
|
+
await writeFile8(entryFile, renderServerEntry(rootDir, entryFile, manifest, identity));
|
|
3334
|
+
const serverFile = path18.join(outDir, SERVER_ENTRYPOINT);
|
|
3335
|
+
await mkdir8(path18.dirname(serverFile), { recursive: true });
|
|
3336
|
+
await esbuild2({
|
|
3337
|
+
absWorkingDir: rootDir,
|
|
3338
|
+
alias: sidecarBundleAliases(rootDir),
|
|
3339
|
+
banner: {
|
|
3340
|
+
js: `import { createRequire as __sidecarCreateRequire } from "node:module";
|
|
3341
|
+
const require = __sidecarCreateRequire(import.meta.url);`
|
|
3342
|
+
},
|
|
3343
|
+
bundle: true,
|
|
3344
|
+
entryPoints: [entryFile],
|
|
3345
|
+
format: "esm",
|
|
3346
|
+
legalComments: "none",
|
|
3347
|
+
minify: false,
|
|
3348
|
+
nodePaths: [
|
|
3349
|
+
path18.join(rootDir, "node_modules"),
|
|
3350
|
+
path18.join(process.cwd(), "node_modules")
|
|
3351
|
+
],
|
|
3352
|
+
outfile: serverFile,
|
|
3353
|
+
platform: "node",
|
|
3354
|
+
sourcemap: false,
|
|
3355
|
+
target: "node20"
|
|
3356
|
+
});
|
|
3357
|
+
await writeFile8(path18.join(outDir, "package.json"), renderServerPackage(identity));
|
|
3358
|
+
if (host === "vercel") {
|
|
3359
|
+
const vercelOutputDir = options.vercelOutputDir ?? outDir;
|
|
3360
|
+
await rm(path18.join(vercelOutputDir, "api"), { recursive: true, force: true });
|
|
3361
|
+
await rm(path18.join(vercelOutputDir, "vercel.json"), { force: true });
|
|
3362
|
+
await writeFile8(path18.join(outDir, VERCEL_HANDLER_FILE), renderVercelEntrypoint());
|
|
3363
|
+
await writeFile8(path18.join(outDir, ".vc-config.json"), renderVercelFunctionConfig());
|
|
3364
|
+
await mkdir8(vercelOutputDir, { recursive: true });
|
|
3365
|
+
await writeFile8(path18.join(vercelOutputDir, "config.json"), renderVercelOutputConfig());
|
|
3366
|
+
} else {
|
|
3367
|
+
await rm(path18.join(outDir, "api"), { recursive: true, force: true });
|
|
3368
|
+
await rm(path18.join(outDir, "vercel.json"), { force: true });
|
|
3369
|
+
}
|
|
3370
|
+
}
|
|
3371
|
+
function renderServerEntry(rootDir, entryFile, manifest, identity) {
|
|
3372
|
+
const entryDir = path18.dirname(entryFile);
|
|
3373
|
+
const tools = manifest.tools;
|
|
3374
|
+
const resources = manifest.resources;
|
|
3375
|
+
const prompts = manifest.prompts;
|
|
3376
|
+
const imports = [
|
|
3377
|
+
`import { readFileSync, realpathSync } from "node:fs";`,
|
|
3378
|
+
`import { createServer } from "node:http";`,
|
|
3379
|
+
`import { pathToFileURL } from "node:url";`,
|
|
3380
|
+
`import { createSidecarHttpHandler } from "@sidecar-ai/server";`,
|
|
3381
|
+
`import { isSidecarAuth } from "@sidecar-ai/auth";`,
|
|
3382
|
+
`import { isSidecarPrompt, isSidecarResource, isSidecarTool } from "@sidecar-ai/core";`,
|
|
3383
|
+
`import { isSidecarProxy } from "@sidecar-ai/server/proxy";`,
|
|
3384
|
+
...tools.map(
|
|
3385
|
+
(entry, index) => `import tool${index} from ${JSON.stringify(toImportSpecifier(entryDir, path18.join(rootDir, entry.sourceFile)))};`
|
|
3386
|
+
),
|
|
3387
|
+
...resources.map(
|
|
3388
|
+
(entry, index) => `import resource${index} from ${JSON.stringify(toImportSpecifier(entryDir, path18.join(rootDir, entry.sourceFile)))};`
|
|
3389
|
+
),
|
|
3390
|
+
...prompts.map(
|
|
3391
|
+
(entry, index) => `import prompt${index} from ${JSON.stringify(toImportSpecifier(entryDir, path18.join(rootDir, entry.sourceFile)))};`
|
|
3392
|
+
),
|
|
3393
|
+
existsSync6(path18.join(rootDir, "auth.ts")) ? `import authExport from ${JSON.stringify(toImportSpecifier(entryDir, path18.join(rootDir, "auth.ts")))};` : `const authExport = undefined;`,
|
|
3394
|
+
existsSync6(path18.join(rootDir, "proxy.ts")) ? `import proxyExport from ${JSON.stringify(toImportSpecifier(entryDir, path18.join(rootDir, "proxy.ts")))};` : `const proxyExport = undefined;`,
|
|
3395
|
+
existsSync6(path18.join(rootDir, "sidecar.config.ts")) ? `import configExport from ${JSON.stringify(toImportSpecifier(entryDir, path18.join(rootDir, "sidecar.config.ts")))};` : `const configExport = undefined;`
|
|
3396
|
+
].join("\n");
|
|
3397
|
+
return `${imports}
|
|
3398
|
+
|
|
3399
|
+
const manifest = ${JSON.stringify(manifest, null, 2)};
|
|
3400
|
+
const identity = ${JSON.stringify(identity, null, 2)};
|
|
3401
|
+
|
|
3402
|
+
const loadedAuth = authExport === undefined ? undefined : assertAuth(authExport);
|
|
3403
|
+
const auth = loadedAuth && process.env.SIDECAR_MCP_URL
|
|
3404
|
+
? loadedAuth.withResource(process.env.SIDECAR_MCP_URL)
|
|
3405
|
+
: loadedAuth;
|
|
3406
|
+
const proxy = proxyExport === undefined ? undefined : assertProxy(proxyExport);
|
|
3407
|
+
const runtimeConfig = configExport && typeof configExport === "object" ? configExport : undefined;
|
|
3408
|
+
|
|
3409
|
+
if (auth && !process.env.SIDECAR_MCP_URL) {
|
|
3410
|
+
console.warn("Sidecar auth is enabled. Set SIDECAR_MCP_URL to the public https://.../mcp URL before hosting.");
|
|
3411
|
+
}
|
|
3412
|
+
|
|
3413
|
+
export const handler = createSidecarHttpHandler({
|
|
3414
|
+
name: identity.name,
|
|
3415
|
+
version: identity.version,
|
|
3416
|
+
path: process.env.SIDECAR_MCP_PATH ?? "/mcp",
|
|
3417
|
+
publicUrl: process.env.SIDECAR_PUBLIC_URL ?? process.env.SIDECAR_MCP_URL,
|
|
3418
|
+
auth,
|
|
3419
|
+
proxy,
|
|
3420
|
+
tools: [
|
|
3421
|
+
${tools.map((entry, index) => ` loadTool(${JSON.stringify(entry.sourceFile)}, tool${index}, manifest.tools[${index}].descriptor),`).join("\n")}
|
|
3422
|
+
],
|
|
3423
|
+
resources: [
|
|
3424
|
+
${renderWidgetResources(manifest)}
|
|
3425
|
+
${resources.map((entry, index) => ` loadResource(${JSON.stringify(entry.sourceFile)}, resource${index}, manifest.resources[${index}]),`).join("\n")}
|
|
3426
|
+
],
|
|
3427
|
+
resourceTemplates: manifest.resourceTemplates.map((entry) => ({ descriptor: entry.descriptor })),
|
|
3428
|
+
prompts: [
|
|
3429
|
+
${prompts.map((entry, index) => ` loadPrompt(${JSON.stringify(entry.sourceFile)}, prompt${index}, manifest.prompts[${index}].descriptor),`).join("\n")}
|
|
3430
|
+
],
|
|
3431
|
+
capabilities: {
|
|
3432
|
+
tools: runtimeConfig?.tools ?? manifest.config.tools,
|
|
3433
|
+
resources: runtimeConfig?.resources ?? manifest.config.resources,
|
|
3434
|
+
prompts: runtimeConfig?.prompts ?? manifest.config.prompts,
|
|
3435
|
+
},
|
|
3436
|
+
pagination: runtimeConfig?.pagination ?? {
|
|
3437
|
+
pageSize: manifest.config.pagination.pageSize,
|
|
3438
|
+
},
|
|
3439
|
+
});
|
|
3440
|
+
|
|
3441
|
+
export default handler;
|
|
3442
|
+
|
|
3443
|
+
export const server = createServer(handler);
|
|
3444
|
+
|
|
3445
|
+
if (isDirectRun()) {
|
|
3446
|
+
const port = readPort();
|
|
3447
|
+
const host = process.env.SIDECAR_HOST ?? process.env.HOST ?? "0.0.0.0";
|
|
3448
|
+
server.listen(port, host, () => {
|
|
3449
|
+
const localHost = host === "0.0.0.0" ? "127.0.0.1" : host;
|
|
3450
|
+
console.log(\`MCP running on Streamable HTTP at http://\${localHost}:\${port}\${process.env.SIDECAR_MCP_PATH ?? "/mcp"}\`);
|
|
3451
|
+
});
|
|
3452
|
+
|
|
3453
|
+
process.on("SIGTERM", () => shutdown());
|
|
3454
|
+
process.on("SIGINT", () => shutdown());
|
|
3455
|
+
}
|
|
3456
|
+
|
|
3457
|
+
function loadTool(sourceFile, value, descriptor) {
|
|
3458
|
+
const tool = unwrapRuntimeDefault(value);
|
|
3459
|
+
if (!isSidecarTool(tool)) {
|
|
3460
|
+
throw new Error(\`\${sourceFile} did not default-export a Sidecar tool.\`);
|
|
3461
|
+
}
|
|
3462
|
+
return { tool, descriptor };
|
|
3463
|
+
}
|
|
3464
|
+
|
|
3465
|
+
function loadResource(sourceFile, value, entry) {
|
|
3466
|
+
const resource = unwrapRuntimeDefault(value);
|
|
3467
|
+
if (!isSidecarResource(resource)) {
|
|
3468
|
+
throw new Error(\`\${sourceFile} did not default-export a Sidecar resource.\`);
|
|
3469
|
+
}
|
|
3470
|
+
return {
|
|
3471
|
+
uri: entry.uri,
|
|
3472
|
+
descriptor: entry.descriptor,
|
|
3473
|
+
resource,
|
|
3474
|
+
};
|
|
3475
|
+
}
|
|
3476
|
+
|
|
3477
|
+
function loadPrompt(sourceFile, value, descriptor) {
|
|
3478
|
+
const prompt = unwrapRuntimeDefault(value);
|
|
3479
|
+
if (!isSidecarPrompt(prompt)) {
|
|
3480
|
+
throw new Error(\`\${sourceFile} did not default-export a Sidecar prompt.\`);
|
|
3481
|
+
}
|
|
3482
|
+
return { prompt, descriptor };
|
|
3483
|
+
}
|
|
3484
|
+
|
|
3485
|
+
function assertAuth(value) {
|
|
3486
|
+
const authValue = unwrapRuntimeDefault(value);
|
|
3487
|
+
if (!isSidecarAuth(authValue)) {
|
|
3488
|
+
throw new Error("auth.ts must default-export auth({ ... }) from sidecar-ai.");
|
|
3489
|
+
}
|
|
3490
|
+
return authValue;
|
|
3491
|
+
}
|
|
3492
|
+
|
|
3493
|
+
function assertProxy(value) {
|
|
3494
|
+
const proxyValue = unwrapRuntimeDefault(value);
|
|
3495
|
+
if (!isSidecarProxy(proxyValue)) {
|
|
3496
|
+
throw new Error("proxy.ts must default-export proxy({ ... }) from @sidecar-ai/server/proxy.");
|
|
3497
|
+
}
|
|
3498
|
+
return proxyValue;
|
|
3499
|
+
}
|
|
3500
|
+
|
|
3501
|
+
function unwrapRuntimeDefault(value) {
|
|
3502
|
+
if (
|
|
3503
|
+
value &&
|
|
3504
|
+
typeof value === "object" &&
|
|
3505
|
+
"default" in value &&
|
|
3506
|
+
Object.keys(value).every((key) => key === "default" || key === "__esModule")
|
|
3507
|
+
) {
|
|
3508
|
+
return unwrapRuntimeDefault(value.default);
|
|
3509
|
+
}
|
|
3510
|
+
return value;
|
|
3511
|
+
}
|
|
3512
|
+
|
|
3513
|
+
function readWidget(outputFile) {
|
|
3514
|
+
return readFileSync(new URL(\`../\${outputFile}\`, import.meta.url), "utf8");
|
|
3515
|
+
}
|
|
3516
|
+
|
|
3517
|
+
function readPort() {
|
|
3518
|
+
const raw = process.env.PORT ?? process.env.SIDECAR_PORT ?? "3001";
|
|
3519
|
+
const port = Number(raw);
|
|
3520
|
+
if (!Number.isInteger(port) || port < 0 || port > 65535) {
|
|
3521
|
+
throw new Error(\`Invalid PORT/SIDECAR_PORT value: \${raw}\`);
|
|
3522
|
+
}
|
|
3523
|
+
return port;
|
|
3524
|
+
}
|
|
3525
|
+
|
|
3526
|
+
function isDirectRun() {
|
|
3527
|
+
const entry = process.argv[1];
|
|
3528
|
+
if (!entry) {
|
|
3529
|
+
return false;
|
|
3530
|
+
}
|
|
3531
|
+
|
|
3532
|
+
return import.meta.url === pathToFileURL(realpathSync(entry)).href;
|
|
3533
|
+
}
|
|
3534
|
+
|
|
3535
|
+
function shutdown() {
|
|
3536
|
+
server.close(() => process.exit(0));
|
|
3537
|
+
}
|
|
3538
|
+
`;
|
|
3539
|
+
}
|
|
3540
|
+
function renderWidgetResources(manifest) {
|
|
3541
|
+
return manifest.tools.filter((entry) => entry.widget?.outputFile).map((entry) => ` {
|
|
3542
|
+
uri: ${JSON.stringify(entry.widget?.resourceUri)},
|
|
3543
|
+
name: ${JSON.stringify(entry.name)},
|
|
3544
|
+
description: ${JSON.stringify(entry.widget?.options?.description)},
|
|
3545
|
+
mimeType: "text/html;profile=mcp-app",
|
|
3546
|
+
text: readWidget(${JSON.stringify(entry.widget?.outputFile)}),
|
|
3547
|
+
_meta: ${JSON.stringify(entry.widget?.resourceMeta ?? void 0)},
|
|
3548
|
+
},`).join("\n");
|
|
3549
|
+
}
|
|
3550
|
+
function renderServerPackage(identity) {
|
|
3551
|
+
return `${JSON.stringify({
|
|
3552
|
+
name: `${identity.slug}-sidecar-server`,
|
|
3553
|
+
version: identity.version,
|
|
3554
|
+
private: true,
|
|
3555
|
+
type: "module",
|
|
3556
|
+
scripts: {
|
|
3557
|
+
start: "node server/index.js"
|
|
3558
|
+
},
|
|
3559
|
+
engines: {
|
|
3560
|
+
node: ">=20"
|
|
3561
|
+
}
|
|
3562
|
+
}, null, 2)}
|
|
3563
|
+
`;
|
|
3564
|
+
}
|
|
3565
|
+
function renderVercelEntrypoint() {
|
|
3566
|
+
return `export { default } from "./server/index.js";
|
|
3567
|
+
`;
|
|
3568
|
+
}
|
|
3569
|
+
function renderVercelFunctionConfig() {
|
|
3570
|
+
return `${JSON.stringify({
|
|
3571
|
+
runtime: "nodejs22.x",
|
|
3572
|
+
handler: VERCEL_HANDLER_FILE,
|
|
3573
|
+
launcherType: "Nodejs",
|
|
3574
|
+
shouldAddHelpers: true,
|
|
3575
|
+
supportsResponseStreaming: true,
|
|
3576
|
+
maxDuration: 300
|
|
3577
|
+
}, null, 2)}
|
|
3578
|
+
`;
|
|
3579
|
+
}
|
|
3580
|
+
function renderVercelOutputConfig() {
|
|
3581
|
+
return `${JSON.stringify({
|
|
3582
|
+
version: 3,
|
|
3583
|
+
routes: [
|
|
3584
|
+
{
|
|
3585
|
+
src: "/(.*)",
|
|
3586
|
+
dest: "/api/sidecar"
|
|
3587
|
+
}
|
|
3588
|
+
]
|
|
3589
|
+
}, null, 2)}
|
|
3590
|
+
`;
|
|
3591
|
+
}
|
|
3592
|
+
function sidecarBundleAliases(rootDir) {
|
|
3593
|
+
const repoRoot = findSidecarRepoRoot2(rootDir) ?? findSidecarRepoRoot2(process.cwd());
|
|
3594
|
+
if (!repoRoot) {
|
|
3595
|
+
return void 0;
|
|
3596
|
+
}
|
|
3597
|
+
return {
|
|
3598
|
+
"sidecar-ai": path18.join(repoRoot, "packages", "sidecar-ai", "src", "index.ts"),
|
|
3599
|
+
"@sidecar-ai/auth": path18.join(repoRoot, "packages", "auth", "src", "index.ts"),
|
|
3600
|
+
"@sidecar-ai/core": path18.join(repoRoot, "packages", "core", "src", "index.ts"),
|
|
3601
|
+
"@sidecar-ai/server": path18.join(repoRoot, "packages", "server", "src", "index.ts"),
|
|
3602
|
+
"@sidecar-ai/server/proxy": path18.join(repoRoot, "packages", "server", "src", "proxy.ts"),
|
|
3603
|
+
"@sidecar-ai/client": path18.join(repoRoot, "packages", "client", "src", "index.ts"),
|
|
3604
|
+
"@sidecar-ai/react": path18.join(repoRoot, "packages", "react", "src", "index.ts"),
|
|
3605
|
+
"@sidecar-ai/native": path18.join(repoRoot, "packages", "native", "src", "index.ts"),
|
|
3606
|
+
"@sidecar-ai/native/components": path18.join(repoRoot, "packages", "native", "src", "components", "index.tsx"),
|
|
3607
|
+
"@sidecar-ai/openai": path18.join(repoRoot, "packages", "openai", "src", "index.ts"),
|
|
3608
|
+
"@sidecar-ai/openai/components": path18.join(repoRoot, "packages", "openai", "src", "components.tsx"),
|
|
3609
|
+
"@sidecar-ai/openai/official": path18.join(repoRoot, "packages", "openai", "src", "official.ts"),
|
|
3610
|
+
"@sidecar-ai/anthropic": path18.join(repoRoot, "packages", "anthropic", "src", "index.ts"),
|
|
3611
|
+
"@sidecar-ai/anthropic/agent": path18.join(repoRoot, "packages", "anthropic", "src", "agent.ts"),
|
|
3612
|
+
"@sidecar-ai/anthropic/command": path18.join(repoRoot, "packages", "anthropic", "src", "command.ts"),
|
|
3613
|
+
"@sidecar-ai/anthropic/components": path18.join(repoRoot, "packages", "anthropic", "src", "components.tsx"),
|
|
3614
|
+
"@sidecar-ai/anthropic/hooks": path18.join(repoRoot, "packages", "anthropic", "src", "hooks.ts"),
|
|
3615
|
+
"@sidecar-ai/anthropic/mcp": path18.join(repoRoot, "packages", "anthropic", "src", "mcp.ts"),
|
|
3616
|
+
"@sidecar-ai/anthropic/plugin": path18.join(repoRoot, "packages", "anthropic", "src", "plugin.ts"),
|
|
3617
|
+
"@sidecar-ai/anthropic/skill": path18.join(repoRoot, "packages", "anthropic", "src", "skill.ts")
|
|
3618
|
+
};
|
|
3619
|
+
}
|
|
3620
|
+
function findSidecarRepoRoot2(startDir) {
|
|
3621
|
+
let current = path18.resolve(startDir);
|
|
3622
|
+
while (true) {
|
|
3623
|
+
if (existsSync6(path18.join(current, "packages", "core", "src", "index.ts"))) {
|
|
3624
|
+
return current;
|
|
3625
|
+
}
|
|
3626
|
+
const parent = path18.dirname(current);
|
|
3627
|
+
if (parent === current) {
|
|
3628
|
+
return void 0;
|
|
3629
|
+
}
|
|
3630
|
+
current = parent;
|
|
3631
|
+
}
|
|
3632
|
+
}
|
|
3633
|
+
|
|
2976
3634
|
// packages/compiler/src/build.ts
|
|
2977
3635
|
async function buildProject(options) {
|
|
2978
|
-
const rootDir =
|
|
2979
|
-
const target = options.target ?? "mcp";
|
|
3636
|
+
const rootDir = path19.resolve(options.rootDir);
|
|
2980
3637
|
const config = analyzeProjectConfig(rootDir);
|
|
3638
|
+
const target = options.target ?? config.build.target ?? "mcp";
|
|
3639
|
+
const host = options.host ?? config.build.host ?? "node";
|
|
3640
|
+
const plugins = options.plugins ?? config.build.plugins ?? false;
|
|
2981
3641
|
const tools = await analyzeProjectTools(rootDir, { target });
|
|
2982
3642
|
const resources = await analyzeProjectResources(rootDir);
|
|
2983
3643
|
const resourceTemplates = [];
|
|
2984
3644
|
const prompts = await analyzeProjectPrompts(rootDir);
|
|
3645
|
+
const identity = await loadProjectIdentity(rootDir);
|
|
2985
3646
|
const diagnostics = await collectProjectDiagnostics(rootDir, {
|
|
2986
3647
|
tools,
|
|
2987
3648
|
resources,
|
|
@@ -2992,11 +3653,13 @@ async function buildProject(options) {
|
|
|
2992
3653
|
if (errors.length) {
|
|
2993
3654
|
throw new Error(errors.map(formatDiagnostic).join("\n"));
|
|
2994
3655
|
}
|
|
2995
|
-
const outDir = resolveInsideRoot(rootDir, options.outDir ??
|
|
2996
|
-
|
|
3656
|
+
const outDir = resolveInsideRoot(rootDir, options.outDir ?? config.build.outDir ?? defaultBuildOutDir(host, target));
|
|
3657
|
+
const runtimeOutDir = resolveRuntimeOutputDir(outDir, host);
|
|
3658
|
+
await buildWidgets(rootDir, runtimeOutDir, tools, config.build.widgets);
|
|
2997
3659
|
const manifest = {
|
|
2998
3660
|
version: 1,
|
|
2999
3661
|
target,
|
|
3662
|
+
host,
|
|
3000
3663
|
rootDir: ".",
|
|
3001
3664
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3002
3665
|
config,
|
|
@@ -3006,23 +3669,47 @@ async function buildProject(options) {
|
|
|
3006
3669
|
prompts,
|
|
3007
3670
|
diagnostics
|
|
3008
3671
|
};
|
|
3009
|
-
await
|
|
3010
|
-
await
|
|
3011
|
-
|
|
3672
|
+
await mkdir9(runtimeOutDir, { recursive: true });
|
|
3673
|
+
await writeFile9(
|
|
3674
|
+
path19.join(runtimeOutDir, "manifest.sidecar.json"),
|
|
3012
3675
|
`${JSON.stringify(manifest, null, 2)}
|
|
3013
3676
|
`
|
|
3014
3677
|
);
|
|
3015
|
-
await
|
|
3678
|
+
await writeFile9(path19.join(runtimeOutDir, "README.md"), renderMcpReadme(manifest));
|
|
3016
3679
|
await writeGeneratedTypes(rootDir, tools);
|
|
3017
|
-
|
|
3018
|
-
|
|
3680
|
+
await buildServerOutput(rootDir, runtimeOutDir, manifest, identity, host, {
|
|
3681
|
+
vercelOutputDir: outDir
|
|
3682
|
+
});
|
|
3683
|
+
if (plugins) {
|
|
3684
|
+
await buildPluginPackages(rootDir, resolvePluginOutputBase(rootDir, outDir, host), manifest);
|
|
3019
3685
|
}
|
|
3020
3686
|
return manifest;
|
|
3021
3687
|
}
|
|
3688
|
+
function defaultBuildOutDir(host, target) {
|
|
3689
|
+
if (host === "vercel") {
|
|
3690
|
+
return ".vercel/output";
|
|
3691
|
+
}
|
|
3692
|
+
return `out/${target}`;
|
|
3693
|
+
}
|
|
3694
|
+
function resolveRuntimeOutputDir(outDir, host) {
|
|
3695
|
+
if (host === "vercel") {
|
|
3696
|
+
return path19.join(outDir, VERCEL_FUNCTION_DIR);
|
|
3697
|
+
}
|
|
3698
|
+
return outDir;
|
|
3699
|
+
}
|
|
3700
|
+
function resolvePluginOutputBase(rootDir, outDir, host) {
|
|
3701
|
+
if (host === "vercel" && isVercelBuildOutputDir(outDir)) {
|
|
3702
|
+
return path19.join(rootDir, "out");
|
|
3703
|
+
}
|
|
3704
|
+
return path19.dirname(outDir);
|
|
3705
|
+
}
|
|
3706
|
+
function isVercelBuildOutputDir(outDir) {
|
|
3707
|
+
return path19.basename(outDir) === "output" && path19.basename(path19.dirname(outDir)) === ".vercel";
|
|
3708
|
+
}
|
|
3022
3709
|
function resolveInsideRoot(rootDir, output) {
|
|
3023
|
-
const resolved =
|
|
3024
|
-
const relative =
|
|
3025
|
-
if (relative.startsWith("..") ||
|
|
3710
|
+
const resolved = path19.resolve(rootDir, output);
|
|
3711
|
+
const relative = path19.relative(rootDir, resolved);
|
|
3712
|
+
if (relative.startsWith("..") || path19.isAbsolute(relative)) {
|
|
3026
3713
|
throw new Error(`Build output must stay inside the project root: ${output}`);
|
|
3027
3714
|
}
|
|
3028
3715
|
return resolved;
|
|
@@ -3047,9 +3734,35 @@ ${resources || "No resources detected."}
|
|
|
3047
3734
|
|
|
3048
3735
|
${prompts || "No prompts detected."}
|
|
3049
3736
|
|
|
3737
|
+
${manifest.host === "vercel" ? renderVercelReadmeSection() : renderNodeReadmeSection()}
|
|
3738
|
+
|
|
3050
3739
|
## Local HTTPS Testing
|
|
3051
3740
|
|
|
3052
|
-
Run \`sidecar dev --tunnel\` from the project root to start the local MCP server on Streamable HTTP and print
|
|
3741
|
+
Run \`sidecar dev --tunnel\` from the project root to start the local MCP server on Streamable HTTP and print a validated HTTPS MCP URL that can be added to ChatGPT, Claude, or a Claude plugin install. Temporary quick tunnels are public and best-effort; use a configured tunnel/domain or deployed preview for repeatable testing.
|
|
3742
|
+
`;
|
|
3743
|
+
}
|
|
3744
|
+
function renderNodeReadmeSection() {
|
|
3745
|
+
return `## Run The Server
|
|
3746
|
+
|
|
3747
|
+
This output includes a standalone Node MCP server. Start it with:
|
|
3748
|
+
|
|
3749
|
+
\`\`\`sh
|
|
3750
|
+
npm start
|
|
3751
|
+
\`\`\`
|
|
3752
|
+
|
|
3753
|
+
or:
|
|
3754
|
+
|
|
3755
|
+
\`\`\`sh
|
|
3756
|
+
node server/index.js
|
|
3757
|
+
\`\`\`
|
|
3758
|
+
|
|
3759
|
+
Set \`PORT\` or \`SIDECAR_PORT\` to choose the listen port. Hosted/authenticated MCPs should set \`SIDECAR_MCP_URL\` to the public \`https://.../mcp\` URL before starting.
|
|
3760
|
+
`;
|
|
3761
|
+
}
|
|
3762
|
+
function renderVercelReadmeSection() {
|
|
3763
|
+
return `## Deploy To Vercel
|
|
3764
|
+
|
|
3765
|
+
This output uses Vercel's Build Output API. The MCP function is emitted at \`${VERCEL_FUNCTION_DIR}\`, and \`config.json\` routes Streamable HTTP traffic to it. Set \`SIDECAR_MCP_URL\` to the public \`https://.../mcp\` URL in Vercel.
|
|
3053
3766
|
`;
|
|
3054
3767
|
}
|
|
3055
3768
|
|
|
@@ -3062,7 +3775,7 @@ import { randomUUID } from "crypto";
|
|
|
3062
3775
|
var proxyBrand = /* @__PURE__ */ Symbol.for("sidecar.proxy");
|
|
3063
3776
|
function isSidecarProxy(value) {
|
|
3064
3777
|
return Boolean(
|
|
3065
|
-
value && typeof value === "object" && value[proxyBrand] === true
|
|
3778
|
+
value && typeof value === "object" && (value[proxyBrand] === true || value.kind === "sidecar.proxy")
|
|
3066
3779
|
);
|
|
3067
3780
|
}
|
|
3068
3781
|
async function runProxy(proxyConfig, request) {
|
|
@@ -3075,8 +3788,90 @@ async function runProxy(proxyConfig, request) {
|
|
|
3075
3788
|
return void 0;
|
|
3076
3789
|
}
|
|
3077
3790
|
|
|
3078
|
-
// packages/server/src/
|
|
3791
|
+
// packages/server/src/sse.ts
|
|
3079
3792
|
import { JSONRPC_VERSION } from "@modelcontextprotocol/sdk/types.js";
|
|
3793
|
+
var sseEventCounter = 0;
|
|
3794
|
+
function createSseHub() {
|
|
3795
|
+
const streams = /* @__PURE__ */ new Set();
|
|
3796
|
+
return {
|
|
3797
|
+
open(response) {
|
|
3798
|
+
const stream = createSseStream(response);
|
|
3799
|
+
streams.add(stream);
|
|
3800
|
+
response.on("close", () => {
|
|
3801
|
+
streams.delete(stream);
|
|
3802
|
+
});
|
|
3803
|
+
return stream;
|
|
3804
|
+
},
|
|
3805
|
+
send(method, params) {
|
|
3806
|
+
const stream = streams.values().next().value;
|
|
3807
|
+
stream?.send(method, params);
|
|
3808
|
+
}
|
|
3809
|
+
};
|
|
3810
|
+
}
|
|
3811
|
+
function createSseStream(response, options = {}) {
|
|
3812
|
+
let closed = false;
|
|
3813
|
+
response.writeHead(200, {
|
|
3814
|
+
"content-type": "text/event-stream",
|
|
3815
|
+
"cache-control": "no-cache, no-transform",
|
|
3816
|
+
"connection": "keep-alive",
|
|
3817
|
+
"x-accel-buffering": "no"
|
|
3818
|
+
});
|
|
3819
|
+
response.flushHeaders?.();
|
|
3820
|
+
const keepAlive = setInterval(() => {
|
|
3821
|
+
writeRaw(": keepalive\n\n");
|
|
3822
|
+
}, 3e4);
|
|
3823
|
+
keepAlive.unref?.();
|
|
3824
|
+
response.on("close", () => {
|
|
3825
|
+
closed = true;
|
|
3826
|
+
clearInterval(keepAlive);
|
|
3827
|
+
});
|
|
3828
|
+
writeRaw(`id: ${nextSseEventId()}
|
|
3829
|
+
data:
|
|
3830
|
+
|
|
3831
|
+
`);
|
|
3832
|
+
function writeRaw(frame) {
|
|
3833
|
+
if (!closed && !response.destroyed) {
|
|
3834
|
+
response.write(frame);
|
|
3835
|
+
}
|
|
3836
|
+
}
|
|
3837
|
+
function sendJson(value) {
|
|
3838
|
+
writeRaw([
|
|
3839
|
+
`id: ${nextSseEventId()}`,
|
|
3840
|
+
"event: message",
|
|
3841
|
+
`data: ${JSON.stringify(value)}`,
|
|
3842
|
+
"",
|
|
3843
|
+
""
|
|
3844
|
+
].join("\n"));
|
|
3845
|
+
}
|
|
3846
|
+
return {
|
|
3847
|
+
supportsRequestProgress: options.supportsRequestProgress,
|
|
3848
|
+
send(method, params) {
|
|
3849
|
+
sendJson(omitUndefined({
|
|
3850
|
+
jsonrpc: JSONRPC_VERSION,
|
|
3851
|
+
method,
|
|
3852
|
+
params
|
|
3853
|
+
}));
|
|
3854
|
+
},
|
|
3855
|
+
sendJson,
|
|
3856
|
+
end() {
|
|
3857
|
+
closed = true;
|
|
3858
|
+
clearInterval(keepAlive);
|
|
3859
|
+
response.end();
|
|
3860
|
+
}
|
|
3861
|
+
};
|
|
3862
|
+
}
|
|
3863
|
+
function nextSseEventId() {
|
|
3864
|
+
sseEventCounter += 1;
|
|
3865
|
+
return `sidecar-${Date.now()}-${sseEventCounter}`;
|
|
3866
|
+
}
|
|
3867
|
+
function omitUndefined(value) {
|
|
3868
|
+
return Object.fromEntries(
|
|
3869
|
+
Object.entries(value).filter(([, entry]) => entry !== void 0)
|
|
3870
|
+
);
|
|
3871
|
+
}
|
|
3872
|
+
|
|
3873
|
+
// packages/server/src/index.ts
|
|
3874
|
+
import { JSONRPC_VERSION as JSONRPC_VERSION2 } from "@modelcontextprotocol/sdk/types.js";
|
|
3080
3875
|
var SIDECAR_MCP_PROTOCOL_VERSION = "2025-11-25";
|
|
3081
3876
|
var SidecarMcpServer = class {
|
|
3082
3877
|
constructor(options) {
|
|
@@ -3120,6 +3915,8 @@ var SidecarMcpServer = class {
|
|
|
3120
3915
|
resources = /* @__PURE__ */ new Map();
|
|
3121
3916
|
prompts = /* @__PURE__ */ new Map();
|
|
3122
3917
|
resourceTemplates;
|
|
3918
|
+
activeRequests = /* @__PURE__ */ new Map();
|
|
3919
|
+
subscribedResourceUris = /* @__PURE__ */ new Set();
|
|
3123
3920
|
/** Returns all tool descriptors exposed through `tools/list`. */
|
|
3124
3921
|
descriptors() {
|
|
3125
3922
|
return [...this.tools.values()].map((loaded) => loaded.descriptor);
|
|
@@ -3144,13 +3941,16 @@ var SidecarMcpServer = class {
|
|
|
3144
3941
|
const authorizedContext = { ...context, authSession };
|
|
3145
3942
|
const result = await this.dispatch(request, authorizedContext);
|
|
3146
3943
|
return {
|
|
3147
|
-
jsonrpc:
|
|
3944
|
+
jsonrpc: JSONRPC_VERSION2,
|
|
3148
3945
|
id: request.id,
|
|
3149
3946
|
result
|
|
3150
3947
|
};
|
|
3151
3948
|
} catch (error) {
|
|
3949
|
+
if (error instanceof RequestCancelledError) {
|
|
3950
|
+
return void 0;
|
|
3951
|
+
}
|
|
3152
3952
|
return {
|
|
3153
|
-
jsonrpc:
|
|
3953
|
+
jsonrpc: JSONRPC_VERSION2,
|
|
3154
3954
|
id: request.id,
|
|
3155
3955
|
error: normalizeError(error)
|
|
3156
3956
|
};
|
|
@@ -3168,6 +3968,8 @@ var SidecarMcpServer = class {
|
|
|
3168
3968
|
version: this.options.version ?? "0.0.0-dev"
|
|
3169
3969
|
}
|
|
3170
3970
|
};
|
|
3971
|
+
case "ping":
|
|
3972
|
+
return {};
|
|
3171
3973
|
case "tools/list":
|
|
3172
3974
|
return this.listTools(request, context);
|
|
3173
3975
|
case "tools/call":
|
|
@@ -3262,7 +4064,7 @@ var SidecarMcpServer = class {
|
|
|
3262
4064
|
/** Returns the server-chosen page size for all built-in list pagination. */
|
|
3263
4065
|
pageSize() {
|
|
3264
4066
|
const configured = this.options.pagination?.pageSize;
|
|
3265
|
-
return configured && configured > 0 ? Math.floor(configured) :
|
|
4067
|
+
return configured && configured > 0 ? Math.floor(configured) : 50;
|
|
3266
4068
|
}
|
|
3267
4069
|
/** Executes a tool after request-level and tool-level auth checks. */
|
|
3268
4070
|
async callTool(request, context) {
|
|
@@ -3280,29 +4082,39 @@ var SidecarMcpServer = class {
|
|
|
3280
4082
|
if (authSession !== void 0) {
|
|
3281
4083
|
ctx.auth = authSession;
|
|
3282
4084
|
}
|
|
4085
|
+
ctx.notify = this.createNotifications(context, request);
|
|
3283
4086
|
const controller = new AbortController();
|
|
4087
|
+
if (request.id !== null && request.id !== void 0) {
|
|
4088
|
+
this.activeRequests.set(request.id, controller);
|
|
4089
|
+
}
|
|
3284
4090
|
ctx.request = {
|
|
3285
4091
|
...ctx.request,
|
|
3286
4092
|
signal: controller.signal
|
|
3287
4093
|
};
|
|
3288
|
-
|
|
3289
|
-
loaded.
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
if (loaded.descriptorProvided) {
|
|
3299
|
-
validateAgainstSchema(
|
|
3300
|
-
loaded.descriptor.outputSchema,
|
|
3301
|
-
result.structuredContent,
|
|
3302
|
-
`Invalid structuredContent returned by tool "${name}".`
|
|
4094
|
+
try {
|
|
4095
|
+
const args = loaded.descriptorProvided ? validateAgainstSchema(
|
|
4096
|
+
loaded.descriptor.inputSchema,
|
|
4097
|
+
params?.arguments ?? {},
|
|
4098
|
+
`Invalid parameters for tool "${name}".`
|
|
4099
|
+
) : params?.arguments ?? {};
|
|
4100
|
+
const result = await withTimeout(
|
|
4101
|
+
executeTool(loaded.tool, args, ctx),
|
|
4102
|
+
this.options.toolTimeoutMs,
|
|
4103
|
+
controller
|
|
3303
4104
|
);
|
|
4105
|
+
if (loaded.descriptorProvided) {
|
|
4106
|
+
validateAgainstSchema(
|
|
4107
|
+
loaded.descriptor.outputSchema,
|
|
4108
|
+
result.structuredContent,
|
|
4109
|
+
`Invalid structuredContent returned by tool "${name}".`
|
|
4110
|
+
);
|
|
4111
|
+
}
|
|
4112
|
+
return result;
|
|
4113
|
+
} finally {
|
|
4114
|
+
if (request.id !== null && request.id !== void 0) {
|
|
4115
|
+
this.activeRequests.delete(request.id);
|
|
4116
|
+
}
|
|
3304
4117
|
}
|
|
3305
|
-
return result;
|
|
3306
4118
|
}
|
|
3307
4119
|
/** Authenticates the whole HTTP MCP request when auth.ts is configured. */
|
|
3308
4120
|
async authorizeEndpoint(context) {
|
|
@@ -3354,12 +4166,14 @@ var SidecarMcpServer = class {
|
|
|
3354
4166
|
if (context.authSession !== void 0) {
|
|
3355
4167
|
toolContext.auth = context.authSession;
|
|
3356
4168
|
}
|
|
4169
|
+
toolContext.notify = this.createNotifications(context, request);
|
|
3357
4170
|
return executeResource(resource.resource, toResourceContext(toolContext), {
|
|
3358
4171
|
uri,
|
|
3359
4172
|
mimeType: resource.descriptor.mimeType
|
|
3360
4173
|
});
|
|
3361
4174
|
}
|
|
3362
4175
|
return {
|
|
4176
|
+
_meta: resource._meta,
|
|
3363
4177
|
contents: [
|
|
3364
4178
|
{
|
|
3365
4179
|
uri,
|
|
@@ -3379,6 +4193,7 @@ var SidecarMcpServer = class {
|
|
|
3379
4193
|
if (!this.resources.has(uri)) {
|
|
3380
4194
|
throw new JsonRpcError(-32002, `Resource not found: "${uri}".`, { uri });
|
|
3381
4195
|
}
|
|
4196
|
+
this.subscribedResourceUris.add(uri);
|
|
3382
4197
|
return {};
|
|
3383
4198
|
}
|
|
3384
4199
|
/** Accepts a resource unsubscription when server-level support is enabled. */
|
|
@@ -3390,6 +4205,7 @@ var SidecarMcpServer = class {
|
|
|
3390
4205
|
if (!this.resources.has(uri)) {
|
|
3391
4206
|
throw new JsonRpcError(-32002, `Resource not found: "${uri}".`, { uri });
|
|
3392
4207
|
}
|
|
4208
|
+
this.subscribedResourceUris.delete(uri);
|
|
3393
4209
|
return {};
|
|
3394
4210
|
}
|
|
3395
4211
|
/** Renders one named prompt with validated arguments. */
|
|
@@ -3407,20 +4223,61 @@ var SidecarMcpServer = class {
|
|
|
3407
4223
|
if (context.authSession !== void 0) {
|
|
3408
4224
|
toolContext.auth = context.authSession;
|
|
3409
4225
|
}
|
|
4226
|
+
toolContext.notify = this.createNotifications(context, request);
|
|
3410
4227
|
return executePrompt(loaded.prompt, params?.arguments ?? {}, toPromptContext(toolContext));
|
|
3411
4228
|
}
|
|
4229
|
+
/** Creates notification helpers scoped by advertised server capabilities. */
|
|
4230
|
+
createNotifications(context, request) {
|
|
4231
|
+
const configured = this.options.capabilities ?? {};
|
|
4232
|
+
return createRuntimeNotifications(
|
|
4233
|
+
context.notifications,
|
|
4234
|
+
readProgressToken(request),
|
|
4235
|
+
{
|
|
4236
|
+
toolsListChanged: Boolean(configured.tools?.listChanged),
|
|
4237
|
+
resourcesListChanged: Boolean(configured.resources?.listChanged),
|
|
4238
|
+
promptsListChanged: Boolean(configured.prompts?.listChanged),
|
|
4239
|
+
isResourceSubscribed: (uri) => this.subscribedResourceUris.has(uri)
|
|
4240
|
+
}
|
|
4241
|
+
);
|
|
4242
|
+
}
|
|
3412
4243
|
/** Accepts notifications without side effects for client compatibility. */
|
|
3413
|
-
async handleNotification(
|
|
4244
|
+
async handleNotification(request) {
|
|
4245
|
+
if (request.method === "notifications/cancelled") {
|
|
4246
|
+
this.cancelRequest(request);
|
|
4247
|
+
}
|
|
4248
|
+
}
|
|
4249
|
+
/** Applies a client cancellation notification to an active request. */
|
|
4250
|
+
cancelRequest(request) {
|
|
4251
|
+
const params = request.params;
|
|
4252
|
+
const requestId = params?.requestId;
|
|
4253
|
+
if (typeof requestId !== "string" && typeof requestId !== "number") {
|
|
4254
|
+
return;
|
|
4255
|
+
}
|
|
4256
|
+
const controller = this.activeRequests.get(requestId);
|
|
4257
|
+
if (!controller || controller.signal.aborted) {
|
|
4258
|
+
return;
|
|
4259
|
+
}
|
|
4260
|
+
const reason = typeof params?.reason === "string" ? params.reason : "Request cancelled.";
|
|
4261
|
+
controller.abort(new RequestCancelledError(reason));
|
|
3414
4262
|
}
|
|
3415
4263
|
};
|
|
3416
4264
|
function createSidecarMcpServer(options) {
|
|
3417
4265
|
return new SidecarMcpServer(options);
|
|
3418
4266
|
}
|
|
3419
4267
|
function createSidecarHttpServer(options) {
|
|
3420
|
-
|
|
4268
|
+
return createServer(createSidecarHttpHandler(options));
|
|
4269
|
+
}
|
|
4270
|
+
function createSidecarHttpHandler(options) {
|
|
3421
4271
|
const endpoint = options.path ?? "/mcp";
|
|
3422
4272
|
const maxBodyBytes = options.maxBodyBytes ?? 1e6;
|
|
3423
|
-
|
|
4273
|
+
const streamHub = createSseHub();
|
|
4274
|
+
const mcp = createSidecarMcpServer(options);
|
|
4275
|
+
return async (request, response) => {
|
|
4276
|
+
const pathname = request.url?.split("?")[0];
|
|
4277
|
+
const requestLog = createHttpRequestLog(request, pathname);
|
|
4278
|
+
response.once("finish", () => {
|
|
4279
|
+
logHttpRequest(requestLog, response.statusCode);
|
|
4280
|
+
});
|
|
3424
4281
|
if (isRejectedOrigin(request, options.allowedOrigins)) {
|
|
3425
4282
|
response.writeHead(403, { "content-type": "application/json" });
|
|
3426
4283
|
response.end(JSON.stringify({ error: "forbidden_origin" }));
|
|
@@ -3432,23 +4289,59 @@ function createSidecarHttpServer(options) {
|
|
|
3432
4289
|
response.end(proxyResult.body ?? "");
|
|
3433
4290
|
return;
|
|
3434
4291
|
}
|
|
3435
|
-
const pathname = request.url?.split("?")[0];
|
|
3436
4292
|
if (options.auth && request.method === "GET" && isProtectedResourceMetadataPath(pathname, endpoint)) {
|
|
3437
4293
|
response.writeHead(200, { "content-type": "application/json" });
|
|
3438
4294
|
response.end(JSON.stringify(options.auth.metadata()));
|
|
3439
4295
|
return;
|
|
3440
4296
|
}
|
|
4297
|
+
if (options.auth && request.method === "GET" && pathname === "/.well-known/oauth-authorization-server") {
|
|
4298
|
+
await proxyAuthorizationServerMetadata(options.auth, response);
|
|
4299
|
+
return;
|
|
4300
|
+
}
|
|
3441
4301
|
if (request.method === "GET" && pathname === endpoint) {
|
|
3442
|
-
|
|
3443
|
-
|
|
4302
|
+
try {
|
|
4303
|
+
const fetchRequest = toFetchRequest(request, options.publicUrl ?? options.auth?.resource);
|
|
4304
|
+
if (options.auth) {
|
|
4305
|
+
const authSession = await authorizeHttpRequest(options.auth, fetchRequest, response);
|
|
4306
|
+
if (authSession === AUTH_RESPONSE_SENT) {
|
|
4307
|
+
return;
|
|
4308
|
+
}
|
|
4309
|
+
}
|
|
4310
|
+
validateProtocolVersion(request);
|
|
4311
|
+
validateGetHeaders(request);
|
|
4312
|
+
streamHub.open(response);
|
|
4313
|
+
} catch (error) {
|
|
4314
|
+
const status = error instanceof JsonRpcHttpError ? error.status : 400;
|
|
4315
|
+
response.writeHead(status, { "content-type": "application/json" });
|
|
4316
|
+
response.end(
|
|
4317
|
+
JSON.stringify({
|
|
4318
|
+
jsonrpc: JSONRPC_VERSION2,
|
|
4319
|
+
id: null,
|
|
4320
|
+
error: normalizeHttpError(error)
|
|
4321
|
+
})
|
|
4322
|
+
);
|
|
4323
|
+
}
|
|
3444
4324
|
return;
|
|
3445
4325
|
}
|
|
3446
|
-
if (request.method !== "POST"
|
|
4326
|
+
if (pathname === endpoint && request.method !== "POST") {
|
|
4327
|
+
response.writeHead(405, {
|
|
4328
|
+
"allow": "GET, POST",
|
|
4329
|
+
"content-type": "application/json"
|
|
4330
|
+
});
|
|
4331
|
+
response.end(JSON.stringify({ error: "method_not_allowed" }));
|
|
4332
|
+
return;
|
|
4333
|
+
}
|
|
4334
|
+
if (pathname !== endpoint) {
|
|
3447
4335
|
response.writeHead(404, { "content-type": "application/json" });
|
|
3448
4336
|
response.end(JSON.stringify({ error: "not_found" }));
|
|
3449
4337
|
return;
|
|
3450
4338
|
}
|
|
3451
4339
|
try {
|
|
4340
|
+
const fetchRequest = toFetchRequest(request, options.publicUrl ?? options.auth?.resource);
|
|
4341
|
+
const authSession = options.auth ? await authorizeHttpRequest(options.auth, fetchRequest, response) : void 0;
|
|
4342
|
+
if (options.auth && authSession === AUTH_RESPONSE_SENT) {
|
|
4343
|
+
return;
|
|
4344
|
+
}
|
|
3452
4345
|
validateProtocolVersion(request);
|
|
3453
4346
|
validatePostHeaders(request);
|
|
3454
4347
|
const body = await readJson(request, maxBodyBytes);
|
|
@@ -3461,14 +4354,23 @@ function createSidecarHttpServer(options) {
|
|
|
3461
4354
|
return;
|
|
3462
4355
|
}
|
|
3463
4356
|
const rpcRequest = assertJsonRpcRequest(body);
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
4357
|
+
if (rpcRequest.id !== void 0 && hasProgressToken(rpcRequest)) {
|
|
4358
|
+
const stream = createSseStream(response, { supportsRequestProgress: true });
|
|
4359
|
+
const payload2 = await mcp.handle(rpcRequest, {
|
|
4360
|
+
request: fetchRequest,
|
|
4361
|
+
authSession,
|
|
4362
|
+
notifications: stream
|
|
4363
|
+
});
|
|
4364
|
+
if (payload2) {
|
|
4365
|
+
stream.sendJson(payload2);
|
|
4366
|
+
}
|
|
4367
|
+
stream.end();
|
|
3467
4368
|
return;
|
|
3468
4369
|
}
|
|
3469
4370
|
const payload = await mcp.handle(rpcRequest, {
|
|
3470
4371
|
request: fetchRequest,
|
|
3471
|
-
authSession
|
|
4372
|
+
authSession,
|
|
4373
|
+
notifications: streamHub
|
|
3472
4374
|
}) ?? null;
|
|
3473
4375
|
const responses = payload === null ? [] : [payload];
|
|
3474
4376
|
if (!responses.length) {
|
|
@@ -3487,13 +4389,13 @@ function createSidecarHttpServer(options) {
|
|
|
3487
4389
|
response.writeHead(status, { "content-type": "application/json" });
|
|
3488
4390
|
response.end(
|
|
3489
4391
|
JSON.stringify({
|
|
3490
|
-
jsonrpc:
|
|
4392
|
+
jsonrpc: JSONRPC_VERSION2,
|
|
3491
4393
|
id: null,
|
|
3492
4394
|
error: normalizeHttpError(error)
|
|
3493
4395
|
})
|
|
3494
4396
|
);
|
|
3495
4397
|
}
|
|
3496
|
-
}
|
|
4398
|
+
};
|
|
3497
4399
|
}
|
|
3498
4400
|
var DEFAULT_ALLOWED_ORIGINS = [
|
|
3499
4401
|
"https://chatgpt.com",
|
|
@@ -3543,6 +4445,73 @@ function escapeRegExp(value) {
|
|
|
3543
4445
|
function isProtectedResourceMetadataPath(pathname, endpoint) {
|
|
3544
4446
|
return pathname === "/.well-known/oauth-protected-resource" || pathname === `/.well-known/oauth-protected-resource${endpoint}`;
|
|
3545
4447
|
}
|
|
4448
|
+
function createHttpRequestLog(request, pathname) {
|
|
4449
|
+
return {
|
|
4450
|
+
method: request.method,
|
|
4451
|
+
path: pathname,
|
|
4452
|
+
host: truncateHeader(singleHeader(request.headers.host)),
|
|
4453
|
+
accept: truncateHeader(singleHeader(request.headers.accept)),
|
|
4454
|
+
contentType: truncateHeader(singleHeader(request.headers["content-type"])),
|
|
4455
|
+
contentLength: truncateHeader(singleHeader(request.headers["content-length"])),
|
|
4456
|
+
mcpProtocolVersion: truncateHeader(singleHeader(request.headers["mcp-protocol-version"])),
|
|
4457
|
+
origin: truncateHeader(singleHeader(request.headers.origin)),
|
|
4458
|
+
userAgent: truncateHeader(singleHeader(request.headers["user-agent"])),
|
|
4459
|
+
authorization: request.headers.authorization ? "present" : "absent",
|
|
4460
|
+
cookie: request.headers.cookie ? "present" : "absent"
|
|
4461
|
+
};
|
|
4462
|
+
}
|
|
4463
|
+
function logHttpRequest(metadata, status) {
|
|
4464
|
+
const debug = process.env.SIDECAR_DEBUG === "1" || process.env.SIDECAR_LOG_LEVEL === "debug";
|
|
4465
|
+
if (!debug && status < 400) {
|
|
4466
|
+
return;
|
|
4467
|
+
}
|
|
4468
|
+
const message = JSON.stringify({
|
|
4469
|
+
event: "sidecar.mcp.http",
|
|
4470
|
+
status,
|
|
4471
|
+
...stripUndefined4(metadata)
|
|
4472
|
+
});
|
|
4473
|
+
if (status >= 500) {
|
|
4474
|
+
console.error(message);
|
|
4475
|
+
} else if (status >= 400) {
|
|
4476
|
+
console.warn(message);
|
|
4477
|
+
} else {
|
|
4478
|
+
console.info(message);
|
|
4479
|
+
}
|
|
4480
|
+
}
|
|
4481
|
+
function singleHeader(value) {
|
|
4482
|
+
return Array.isArray(value) ? value.join(", ") : value;
|
|
4483
|
+
}
|
|
4484
|
+
function truncateHeader(value) {
|
|
4485
|
+
if (!value) {
|
|
4486
|
+
return void 0;
|
|
4487
|
+
}
|
|
4488
|
+
return value.length > 240 ? `${value.slice(0, 237)}...` : value;
|
|
4489
|
+
}
|
|
4490
|
+
async function proxyAuthorizationServerMetadata(auth, response) {
|
|
4491
|
+
const [authorizationServer] = auth.authorizationServers;
|
|
4492
|
+
if (!authorizationServer) {
|
|
4493
|
+
response.writeHead(404, { "content-type": "application/json" });
|
|
4494
|
+
response.end(JSON.stringify({ error: "authorization_server_not_configured" }));
|
|
4495
|
+
return;
|
|
4496
|
+
}
|
|
4497
|
+
try {
|
|
4498
|
+
const url = new URL("/.well-known/oauth-authorization-server", authorizationServer);
|
|
4499
|
+
const upstream = await fetch(url, { headers: { accept: "application/json" } });
|
|
4500
|
+
const body = await upstream.text();
|
|
4501
|
+
response.writeHead(upstream.ok ? 200 : 502, {
|
|
4502
|
+
"content-type": upstream.headers.get("content-type") ?? "application/json"
|
|
4503
|
+
});
|
|
4504
|
+
response.end(body);
|
|
4505
|
+
} catch (error) {
|
|
4506
|
+
console.warn(JSON.stringify({
|
|
4507
|
+
event: "sidecar.mcp.authorization_metadata_proxy_failed",
|
|
4508
|
+
authorizationServer,
|
|
4509
|
+
message: error instanceof Error ? error.message : "Unknown error"
|
|
4510
|
+
}));
|
|
4511
|
+
response.writeHead(502, { "content-type": "application/json" });
|
|
4512
|
+
response.end(JSON.stringify({ error: "authorization_metadata_unavailable" }));
|
|
4513
|
+
}
|
|
4514
|
+
}
|
|
3546
4515
|
var JsonRpcError = class extends Error {
|
|
3547
4516
|
constructor(code, message, data) {
|
|
3548
4517
|
super(message);
|
|
@@ -3561,6 +4530,12 @@ var JsonRpcHttpError = class extends JsonRpcError {
|
|
|
3561
4530
|
}
|
|
3562
4531
|
status;
|
|
3563
4532
|
};
|
|
4533
|
+
var RequestCancelledError = class extends Error {
|
|
4534
|
+
constructor(message = "Request cancelled.") {
|
|
4535
|
+
super(message);
|
|
4536
|
+
this.name = "RequestCancelledError";
|
|
4537
|
+
}
|
|
4538
|
+
};
|
|
3564
4539
|
var AUTH_RESPONSE_SENT = /* @__PURE__ */ Symbol("sidecar.auth.response-sent");
|
|
3565
4540
|
async function authorizeHttpRequest(auth, request, response) {
|
|
3566
4541
|
const result = await auth.authorizeRequest(request);
|
|
@@ -3573,7 +4548,7 @@ async function authorizeHttpRequest(auth, request, response) {
|
|
|
3573
4548
|
});
|
|
3574
4549
|
response.end(
|
|
3575
4550
|
JSON.stringify({
|
|
3576
|
-
jsonrpc:
|
|
4551
|
+
jsonrpc: JSONRPC_VERSION2,
|
|
3577
4552
|
id: null,
|
|
3578
4553
|
error: {
|
|
3579
4554
|
code: result.status === 401 ? -32001 : -32003,
|
|
@@ -3616,9 +4591,57 @@ function createDefaultContext(request) {
|
|
|
3616
4591
|
memory.delete(key);
|
|
3617
4592
|
}
|
|
3618
4593
|
},
|
|
4594
|
+
notify: noopNotifications,
|
|
3619
4595
|
env: process.env
|
|
3620
4596
|
};
|
|
3621
4597
|
}
|
|
4598
|
+
var noopNotifications = {
|
|
4599
|
+
async progress() {
|
|
4600
|
+
},
|
|
4601
|
+
async toolsChanged() {
|
|
4602
|
+
},
|
|
4603
|
+
async resourcesChanged() {
|
|
4604
|
+
},
|
|
4605
|
+
async promptsChanged() {
|
|
4606
|
+
},
|
|
4607
|
+
async resourceUpdated() {
|
|
4608
|
+
}
|
|
4609
|
+
};
|
|
4610
|
+
function createRuntimeNotifications(sink, progressToken, options) {
|
|
4611
|
+
return {
|
|
4612
|
+
async progress(update) {
|
|
4613
|
+
if (!sink?.supportsRequestProgress || progressToken === void 0) {
|
|
4614
|
+
return;
|
|
4615
|
+
}
|
|
4616
|
+
sink.send("notifications/progress", stripUndefined4({
|
|
4617
|
+
progressToken,
|
|
4618
|
+
progress: update.progress,
|
|
4619
|
+
total: update.total,
|
|
4620
|
+
message: update.message
|
|
4621
|
+
}));
|
|
4622
|
+
},
|
|
4623
|
+
async toolsChanged() {
|
|
4624
|
+
if (options.toolsListChanged) {
|
|
4625
|
+
sink?.send("notifications/tools/list_changed");
|
|
4626
|
+
}
|
|
4627
|
+
},
|
|
4628
|
+
async resourcesChanged() {
|
|
4629
|
+
if (options.resourcesListChanged) {
|
|
4630
|
+
sink?.send("notifications/resources/list_changed");
|
|
4631
|
+
}
|
|
4632
|
+
},
|
|
4633
|
+
async promptsChanged() {
|
|
4634
|
+
if (options.promptsListChanged) {
|
|
4635
|
+
sink?.send("notifications/prompts/list_changed");
|
|
4636
|
+
}
|
|
4637
|
+
},
|
|
4638
|
+
async resourceUpdated(uri) {
|
|
4639
|
+
if (options.isResourceSubscribed(uri)) {
|
|
4640
|
+
sink?.send("notifications/resources/updated", { uri });
|
|
4641
|
+
}
|
|
4642
|
+
}
|
|
4643
|
+
};
|
|
4644
|
+
}
|
|
3622
4645
|
function toResourceContext(ctx) {
|
|
3623
4646
|
return {
|
|
3624
4647
|
auth: ctx.auth,
|
|
@@ -3626,6 +4649,7 @@ function toResourceContext(ctx) {
|
|
|
3626
4649
|
services: ctx.services,
|
|
3627
4650
|
log: ctx.log,
|
|
3628
4651
|
storage: ctx.storage,
|
|
4652
|
+
notify: ctx.notify,
|
|
3629
4653
|
env: ctx.env
|
|
3630
4654
|
};
|
|
3631
4655
|
}
|
|
@@ -3636,6 +4660,7 @@ function toPromptContext(ctx) {
|
|
|
3636
4660
|
services: ctx.services,
|
|
3637
4661
|
log: ctx.log,
|
|
3638
4662
|
storage: ctx.storage,
|
|
4663
|
+
notify: ctx.notify,
|
|
3639
4664
|
env: ctx.env
|
|
3640
4665
|
};
|
|
3641
4666
|
}
|
|
@@ -3657,6 +4682,27 @@ function readUriParam(request, method) {
|
|
|
3657
4682
|
}
|
|
3658
4683
|
return uri;
|
|
3659
4684
|
}
|
|
4685
|
+
function readProgressToken(request) {
|
|
4686
|
+
const params = request.params;
|
|
4687
|
+
if (!params || typeof params !== "object") {
|
|
4688
|
+
return void 0;
|
|
4689
|
+
}
|
|
4690
|
+
const meta = params._meta;
|
|
4691
|
+
if (!meta || typeof meta !== "object") {
|
|
4692
|
+
return void 0;
|
|
4693
|
+
}
|
|
4694
|
+
const progressToken = meta.progressToken;
|
|
4695
|
+
if (typeof progressToken === "string") {
|
|
4696
|
+
return progressToken;
|
|
4697
|
+
}
|
|
4698
|
+
if (typeof progressToken === "number" && Number.isInteger(progressToken)) {
|
|
4699
|
+
return progressToken;
|
|
4700
|
+
}
|
|
4701
|
+
return void 0;
|
|
4702
|
+
}
|
|
4703
|
+
function hasProgressToken(request) {
|
|
4704
|
+
return readProgressToken(request) !== void 0;
|
|
4705
|
+
}
|
|
3660
4706
|
function selectPaginationOverride(override, operation) {
|
|
3661
4707
|
if (!override) {
|
|
3662
4708
|
return void 0;
|
|
@@ -3745,18 +4791,48 @@ function validatePostHeaders(request) {
|
|
|
3745
4791
|
);
|
|
3746
4792
|
}
|
|
3747
4793
|
}
|
|
4794
|
+
function validateGetHeaders(request) {
|
|
4795
|
+
const accept = request.headers.accept;
|
|
4796
|
+
const acceptValue = Array.isArray(accept) ? accept.join(",") : accept;
|
|
4797
|
+
if (!acceptValue || !acceptValue.toLowerCase().includes("text/event-stream")) {
|
|
4798
|
+
throw new JsonRpcHttpError(
|
|
4799
|
+
406,
|
|
4800
|
+
-32600,
|
|
4801
|
+
"GET Accept must include text/event-stream."
|
|
4802
|
+
);
|
|
4803
|
+
}
|
|
4804
|
+
}
|
|
3748
4805
|
async function withTimeout(promise, timeoutMs, controller) {
|
|
4806
|
+
let abortListener;
|
|
4807
|
+
const abortPromise = new Promise((_resolve, reject) => {
|
|
4808
|
+
abortListener = () => {
|
|
4809
|
+
reject(abortReason(controller.signal.reason));
|
|
4810
|
+
};
|
|
4811
|
+
if (controller.signal.aborted) {
|
|
4812
|
+
abortListener();
|
|
4813
|
+
return;
|
|
4814
|
+
}
|
|
4815
|
+
controller.signal.addEventListener("abort", abortListener, { once: true });
|
|
4816
|
+
});
|
|
3749
4817
|
if (!timeoutMs || timeoutMs <= 0) {
|
|
3750
|
-
|
|
4818
|
+
try {
|
|
4819
|
+
return await Promise.race([promise, abortPromise]);
|
|
4820
|
+
} finally {
|
|
4821
|
+
if (abortListener) {
|
|
4822
|
+
controller.signal.removeEventListener("abort", abortListener);
|
|
4823
|
+
}
|
|
4824
|
+
}
|
|
3751
4825
|
}
|
|
3752
4826
|
let timeout;
|
|
3753
4827
|
try {
|
|
3754
4828
|
return await Promise.race([
|
|
3755
4829
|
promise,
|
|
4830
|
+
abortPromise,
|
|
3756
4831
|
new Promise((_resolve, reject) => {
|
|
3757
4832
|
timeout = setTimeout(() => {
|
|
3758
|
-
|
|
3759
|
-
|
|
4833
|
+
const error = new JsonRpcError(-32e3, "Tool execution timed out.");
|
|
4834
|
+
controller.abort(error);
|
|
4835
|
+
reject(error);
|
|
3760
4836
|
}, timeoutMs);
|
|
3761
4837
|
})
|
|
3762
4838
|
]);
|
|
@@ -3764,8 +4840,17 @@ async function withTimeout(promise, timeoutMs, controller) {
|
|
|
3764
4840
|
if (timeout) {
|
|
3765
4841
|
clearTimeout(timeout);
|
|
3766
4842
|
}
|
|
4843
|
+
if (abortListener) {
|
|
4844
|
+
controller.signal.removeEventListener("abort", abortListener);
|
|
4845
|
+
}
|
|
3767
4846
|
}
|
|
3768
4847
|
}
|
|
4848
|
+
function abortReason(reason) {
|
|
4849
|
+
if (reason instanceof Error) {
|
|
4850
|
+
return reason;
|
|
4851
|
+
}
|
|
4852
|
+
return new RequestCancelledError();
|
|
4853
|
+
}
|
|
3769
4854
|
function validateAgainstSchema(schema, value, message, options = {}) {
|
|
3770
4855
|
if (!schema || value === void 0 && options.optional) {
|
|
3771
4856
|
return value;
|
|
@@ -3776,80 +4861,96 @@ function validateAgainstSchema(schema, value, message, options = {}) {
|
|
|
3776
4861
|
}
|
|
3777
4862
|
return value;
|
|
3778
4863
|
}
|
|
3779
|
-
function schemaFailure(schema, value,
|
|
3780
|
-
if (schema.anyOf?.length && !schema.anyOf.some((entry) => !schemaFailure(entry, value,
|
|
3781
|
-
return `${
|
|
4864
|
+
function schemaFailure(schema, value, path21) {
|
|
4865
|
+
if (schema.anyOf?.length && !schema.anyOf.some((entry) => !schemaFailure(entry, value, path21))) {
|
|
4866
|
+
return `${path21} must match one anyOf schema.`;
|
|
3782
4867
|
}
|
|
3783
|
-
if (schema.oneOf?.length && schema.oneOf.filter((entry) => !schemaFailure(entry, value,
|
|
3784
|
-
return `${
|
|
4868
|
+
if (schema.oneOf?.length && schema.oneOf.filter((entry) => !schemaFailure(entry, value, path21)).length !== 1) {
|
|
4869
|
+
return `${path21} must match exactly one oneOf schema.`;
|
|
3785
4870
|
}
|
|
3786
4871
|
if (schema.allOf?.length) {
|
|
3787
4872
|
for (const entry of schema.allOf) {
|
|
3788
|
-
const failure = schemaFailure(entry, value,
|
|
4873
|
+
const failure = schemaFailure(entry, value, path21);
|
|
3789
4874
|
if (failure) return failure;
|
|
3790
4875
|
}
|
|
3791
4876
|
}
|
|
3792
4877
|
if (schema.const !== void 0 && value !== schema.const) {
|
|
3793
|
-
return `${
|
|
4878
|
+
return `${path21} must equal ${JSON.stringify(schema.const)}.`;
|
|
3794
4879
|
}
|
|
3795
4880
|
if (schema.enum && !schema.enum.some((entry) => JSON.stringify(entry) === JSON.stringify(value))) {
|
|
3796
|
-
return `${
|
|
4881
|
+
return `${path21} must be one of the declared enum values.`;
|
|
3797
4882
|
}
|
|
3798
4883
|
if (schema.type) {
|
|
3799
4884
|
const types = Array.isArray(schema.type) ? schema.type : [schema.type];
|
|
3800
4885
|
if (!types.some((type) => matchesJsonSchemaType(type, value))) {
|
|
3801
|
-
return `${
|
|
4886
|
+
return `${path21} must be ${types.join(" or ")}.`;
|
|
3802
4887
|
}
|
|
3803
4888
|
}
|
|
3804
4889
|
if (schema.type === "object" || schema.properties || schema.required) {
|
|
3805
4890
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
3806
|
-
return `${
|
|
4891
|
+
return `${path21} must be an object.`;
|
|
3807
4892
|
}
|
|
3808
4893
|
const record = value;
|
|
3809
4894
|
for (const required of schema.required ?? []) {
|
|
3810
4895
|
if (!(required in record)) {
|
|
3811
|
-
return `${
|
|
4896
|
+
return `${path21}.${required} is required.`;
|
|
3812
4897
|
}
|
|
3813
4898
|
}
|
|
3814
4899
|
for (const [key, propertySchema] of Object.entries(schema.properties ?? {})) {
|
|
3815
4900
|
if (key in record) {
|
|
3816
|
-
const failure = schemaFailure(propertySchema, record[key], `${
|
|
4901
|
+
const failure = schemaFailure(propertySchema, record[key], `${path21}.${key}`);
|
|
3817
4902
|
if (failure) return failure;
|
|
3818
4903
|
}
|
|
3819
4904
|
}
|
|
4905
|
+
const allowed = new Set(Object.keys(schema.properties ?? {}));
|
|
3820
4906
|
if (schema.additionalProperties === false) {
|
|
3821
|
-
const allowed = new Set(Object.keys(schema.properties ?? {}));
|
|
3822
4907
|
const extra = Object.keys(record).find((key) => !allowed.has(key));
|
|
3823
4908
|
if (extra) {
|
|
3824
|
-
return `${
|
|
4909
|
+
return `${path21}.${extra} is not allowed.`;
|
|
4910
|
+
}
|
|
4911
|
+
} else if (schema.additionalProperties && typeof schema.additionalProperties === "object") {
|
|
4912
|
+
for (const [key, entry] of Object.entries(record)) {
|
|
4913
|
+
if (!allowed.has(key)) {
|
|
4914
|
+
const failure = schemaFailure(schema.additionalProperties, entry, `${path21}.${key}`);
|
|
4915
|
+
if (failure) return failure;
|
|
4916
|
+
}
|
|
3825
4917
|
}
|
|
3826
4918
|
}
|
|
3827
4919
|
}
|
|
3828
4920
|
if (schema.type === "array" || schema.items) {
|
|
3829
4921
|
if (!Array.isArray(value)) {
|
|
3830
|
-
return `${
|
|
4922
|
+
return `${path21} must be an array.`;
|
|
4923
|
+
}
|
|
4924
|
+
if (schema.minItems !== void 0 && value.length < schema.minItems) {
|
|
4925
|
+
return `${path21} must contain at least ${schema.minItems} items.`;
|
|
4926
|
+
}
|
|
4927
|
+
if (schema.maxItems !== void 0 && value.length > schema.maxItems) {
|
|
4928
|
+
return `${path21} must contain at most ${schema.maxItems} items.`;
|
|
3831
4929
|
}
|
|
3832
4930
|
if (schema.items) {
|
|
3833
4931
|
for (const [index, entry] of value.entries()) {
|
|
3834
|
-
const failure = schemaFailure(schema.items, entry, `${
|
|
4932
|
+
const failure = schemaFailure(schema.items, entry, `${path21}[${index}]`);
|
|
3835
4933
|
if (failure) return failure;
|
|
3836
4934
|
}
|
|
3837
4935
|
}
|
|
3838
4936
|
}
|
|
3839
4937
|
if (typeof value === "string") {
|
|
3840
4938
|
if (schema.minLength !== void 0 && value.length < schema.minLength) {
|
|
3841
|
-
return `${
|
|
4939
|
+
return `${path21} is shorter than ${schema.minLength}.`;
|
|
3842
4940
|
}
|
|
3843
4941
|
if (schema.maxLength !== void 0 && value.length > schema.maxLength) {
|
|
3844
|
-
return `${
|
|
4942
|
+
return `${path21} is longer than ${schema.maxLength}.`;
|
|
4943
|
+
}
|
|
4944
|
+
if (schema.pattern !== void 0 && !new RegExp(schema.pattern).test(value)) {
|
|
4945
|
+
return `${path21} must match pattern ${schema.pattern}.`;
|
|
3845
4946
|
}
|
|
3846
4947
|
}
|
|
3847
4948
|
if (typeof value === "number") {
|
|
3848
4949
|
if (schema.minimum !== void 0 && value < schema.minimum) {
|
|
3849
|
-
return `${
|
|
4950
|
+
return `${path21} is less than ${schema.minimum}.`;
|
|
3850
4951
|
}
|
|
3851
4952
|
if (schema.maximum !== void 0 && value > schema.maximum) {
|
|
3852
|
-
return `${
|
|
4953
|
+
return `${path21} is greater than ${schema.maximum}.`;
|
|
3853
4954
|
}
|
|
3854
4955
|
}
|
|
3855
4956
|
return void 0;
|
|
@@ -3957,14 +5058,14 @@ function assertJsonRpcRequest(value) {
|
|
|
3957
5058
|
throw new JsonRpcError(-32600, "Request must be a JSON object.");
|
|
3958
5059
|
}
|
|
3959
5060
|
const record = value;
|
|
3960
|
-
if (record.jsonrpc !==
|
|
5061
|
+
if (record.jsonrpc !== JSONRPC_VERSION2 || typeof record.method !== "string") {
|
|
3961
5062
|
throw new JsonRpcError(-32600, "Invalid JSON-RPC request.");
|
|
3962
5063
|
}
|
|
3963
5064
|
if ("id" in record && record.id !== void 0 && record.id !== null && typeof record.id !== "string" && typeof record.id !== "number") {
|
|
3964
5065
|
throw new JsonRpcError(-32600, "JSON-RPC id must be a string, number, or null.");
|
|
3965
5066
|
}
|
|
3966
5067
|
return {
|
|
3967
|
-
jsonrpc:
|
|
5068
|
+
jsonrpc: JSONRPC_VERSION2,
|
|
3968
5069
|
id: typeof record.id === "string" || typeof record.id === "number" || record.id === null ? record.id : void 0,
|
|
3969
5070
|
method: record.method,
|
|
3970
5071
|
params: record.params
|
|
@@ -3975,7 +5076,7 @@ function isJsonRpcResponseMessage(value) {
|
|
|
3975
5076
|
return false;
|
|
3976
5077
|
}
|
|
3977
5078
|
const record = value;
|
|
3978
|
-
if (record.jsonrpc !==
|
|
5079
|
+
if (record.jsonrpc !== JSONRPC_VERSION2 || !("id" in record) || !("result" in record || "error" in record)) {
|
|
3979
5080
|
return false;
|
|
3980
5081
|
}
|
|
3981
5082
|
return record.id === null || typeof record.id === "string" || typeof record.id === "number";
|
|
@@ -4027,11 +5128,31 @@ import { stdin, stdout } from "process";
|
|
|
4027
5128
|
async function startTunnel(options) {
|
|
4028
5129
|
const provider = await resolveProvider(options.provider);
|
|
4029
5130
|
const localUrl = `http://127.0.0.1:${options.port}`;
|
|
4030
|
-
const
|
|
5131
|
+
const path21 = options.path ?? "/mcp";
|
|
4031
5132
|
if (provider === "cloudflared") {
|
|
4032
|
-
return startCloudflared(localUrl,
|
|
4033
|
-
}
|
|
4034
|
-
return startWrangler(localUrl,
|
|
5133
|
+
return startCloudflared(localUrl, path21, options.timeoutMs);
|
|
5134
|
+
}
|
|
5135
|
+
return startWrangler(localUrl, path21, options.timeoutMs);
|
|
5136
|
+
}
|
|
5137
|
+
async function validateTunnelEndpoint(options) {
|
|
5138
|
+
const timeoutMs = options.timeoutMs ?? 1e4;
|
|
5139
|
+
const mcpUrl = new URL(options.mcpUrl);
|
|
5140
|
+
if ((options.requireHttps ?? true) && mcpUrl.protocol !== "https:") {
|
|
5141
|
+
throw new Error(`Tunnel URL must use https://, received ${options.mcpUrl}.`);
|
|
5142
|
+
}
|
|
5143
|
+
await retryTunnelValidation(async () => {
|
|
5144
|
+
if (options.auth) {
|
|
5145
|
+
await validateProtectedResourceMetadata({
|
|
5146
|
+
mcpUrl,
|
|
5147
|
+
expectedResource: options.expectedResource ?? options.mcpUrl,
|
|
5148
|
+
timeoutMs
|
|
5149
|
+
});
|
|
5150
|
+
await validateAuthChallenge({ mcpUrl, timeoutMs });
|
|
5151
|
+
return;
|
|
5152
|
+
}
|
|
5153
|
+
await validateInitialize({ mcpUrl, timeoutMs });
|
|
5154
|
+
await validateToolsList({ mcpUrl, timeoutMs });
|
|
5155
|
+
}, timeoutMs);
|
|
4035
5156
|
}
|
|
4036
5157
|
function tunnelInstallMessage(provider = "auto") {
|
|
4037
5158
|
if (provider === "wrangler") {
|
|
@@ -4135,7 +5256,7 @@ function assertNpxAvailable() {
|
|
|
4135
5256
|
throw new Error(tunnelInstallMessage("wrangler"));
|
|
4136
5257
|
}
|
|
4137
5258
|
}
|
|
4138
|
-
async function startCloudflared(localUrl,
|
|
5259
|
+
async function startCloudflared(localUrl, path21, timeoutMs = 2e4) {
|
|
4139
5260
|
const child = spawn("cloudflared", ["tunnel", "--url", localUrl, "--no-autoupdate"], {
|
|
4140
5261
|
stdio: ["ignore", "pipe", "pipe"]
|
|
4141
5262
|
});
|
|
@@ -4143,13 +5264,13 @@ async function startCloudflared(localUrl, path19, timeoutMs = 2e4) {
|
|
|
4143
5264
|
return {
|
|
4144
5265
|
provider: "cloudflared",
|
|
4145
5266
|
publicUrl,
|
|
4146
|
-
mcpUrl: appendPath(publicUrl,
|
|
5267
|
+
mcpUrl: appendPath(publicUrl, path21),
|
|
4147
5268
|
close() {
|
|
4148
5269
|
child.kill("SIGTERM");
|
|
4149
5270
|
}
|
|
4150
5271
|
};
|
|
4151
5272
|
}
|
|
4152
|
-
async function startWrangler(localUrl,
|
|
5273
|
+
async function startWrangler(localUrl, path21, timeoutMs = 2e4) {
|
|
4153
5274
|
const child = spawn("npx", ["--yes", "wrangler", "tunnel", "quick-start", localUrl], {
|
|
4154
5275
|
stdio: ["ignore", "pipe", "pipe"]
|
|
4155
5276
|
});
|
|
@@ -4157,7 +5278,7 @@ async function startWrangler(localUrl, path19, timeoutMs = 2e4) {
|
|
|
4157
5278
|
return {
|
|
4158
5279
|
provider: "wrangler",
|
|
4159
5280
|
publicUrl,
|
|
4160
|
-
mcpUrl: appendPath(publicUrl,
|
|
5281
|
+
mcpUrl: appendPath(publicUrl, path21),
|
|
4161
5282
|
close() {
|
|
4162
5283
|
child.kill("SIGTERM");
|
|
4163
5284
|
}
|
|
@@ -4166,15 +5287,20 @@ async function startWrangler(localUrl, path19, timeoutMs = 2e4) {
|
|
|
4166
5287
|
function waitForTunnelUrl(child, pattern, timeoutMs) {
|
|
4167
5288
|
return new Promise((resolve, reject) => {
|
|
4168
5289
|
let settled = false;
|
|
5290
|
+
let output = "";
|
|
4169
5291
|
const timeout = setTimeout(() => {
|
|
4170
5292
|
if (!settled) {
|
|
4171
5293
|
settled = true;
|
|
4172
5294
|
child.kill("SIGTERM");
|
|
4173
|
-
reject(new Error(
|
|
5295
|
+
reject(new Error([
|
|
5296
|
+
"Timed out waiting for HTTPS tunnel URL.",
|
|
5297
|
+
tunnelOutputHint(output)
|
|
5298
|
+
].filter(Boolean).join("\n")));
|
|
4174
5299
|
}
|
|
4175
5300
|
}, timeoutMs);
|
|
4176
5301
|
const inspect = (chunk) => {
|
|
4177
|
-
|
|
5302
|
+
output = tail(`${output}${chunk.toString("utf8")}`, 4e3);
|
|
5303
|
+
const match = output.match(pattern);
|
|
4178
5304
|
if (match?.[0] && !settled) {
|
|
4179
5305
|
settled = true;
|
|
4180
5306
|
clearTimeout(timeout);
|
|
@@ -4194,11 +5320,209 @@ function waitForTunnelUrl(child, pattern, timeoutMs) {
|
|
|
4194
5320
|
if (!settled) {
|
|
4195
5321
|
settled = true;
|
|
4196
5322
|
clearTimeout(timeout);
|
|
4197
|
-
reject(new Error(
|
|
5323
|
+
reject(new Error([
|
|
5324
|
+
`Tunnel process exited before producing a URL${code === null ? "" : ` with code ${code}`}.`,
|
|
5325
|
+
tunnelOutputHint(output)
|
|
5326
|
+
].filter(Boolean).join("\n")));
|
|
4198
5327
|
}
|
|
4199
5328
|
});
|
|
4200
5329
|
});
|
|
4201
5330
|
}
|
|
5331
|
+
async function validateProtectedResourceMetadata(options) {
|
|
5332
|
+
const metadataUrl = protectedResourceMetadataUrl(options.mcpUrl);
|
|
5333
|
+
const response = await fetchWithTimeout(metadataUrl, {
|
|
5334
|
+
headers: { accept: "application/json" },
|
|
5335
|
+
timeoutMs: options.timeoutMs
|
|
5336
|
+
});
|
|
5337
|
+
const body = await readValidationBody(response);
|
|
5338
|
+
assertNotHtml(response, body, metadataUrl);
|
|
5339
|
+
if (response.status !== 200) {
|
|
5340
|
+
if (isTransientHttpStatus(response.status)) {
|
|
5341
|
+
throw new TransientTunnelValidationError(`Tunnel validation failed: ${metadataUrl} returned HTTP ${response.status}.`);
|
|
5342
|
+
}
|
|
5343
|
+
throw new Error(`Tunnel validation failed: ${metadataUrl} returned HTTP ${response.status}.`);
|
|
5344
|
+
}
|
|
5345
|
+
const metadata = parseJsonObject(body, metadataUrl);
|
|
5346
|
+
if (metadata.resource !== options.expectedResource) {
|
|
5347
|
+
throw new Error([
|
|
5348
|
+
"Tunnel validation failed: OAuth protected-resource metadata does not match the public MCP URL.",
|
|
5349
|
+
` expected resource: ${options.expectedResource}`,
|
|
5350
|
+
` actual resource: ${String(metadata.resource)}`
|
|
5351
|
+
].join("\n"));
|
|
5352
|
+
}
|
|
5353
|
+
}
|
|
5354
|
+
async function validateAuthChallenge(options) {
|
|
5355
|
+
const response = await postJsonRpc(options.mcpUrl, {
|
|
5356
|
+
jsonrpc: "2.0",
|
|
5357
|
+
id: 1,
|
|
5358
|
+
method: "initialize",
|
|
5359
|
+
params: {
|
|
5360
|
+
protocolVersion: "2025-11-25",
|
|
5361
|
+
capabilities: {},
|
|
5362
|
+
clientInfo: { name: "sidecar-tunnel-check", version: "0.0.0" }
|
|
5363
|
+
}
|
|
5364
|
+
}, options.timeoutMs);
|
|
5365
|
+
const body = await readValidationBody(response);
|
|
5366
|
+
assertNotHtml(response, body, options.mcpUrl.toString());
|
|
5367
|
+
if (response.status !== 401) {
|
|
5368
|
+
if (isTransientHttpStatus(response.status)) {
|
|
5369
|
+
throw new TransientTunnelValidationError(`Tunnel validation failed: authenticated MCP endpoint returned HTTP ${response.status}.`);
|
|
5370
|
+
}
|
|
5371
|
+
throw new Error(`Tunnel validation failed: authenticated MCP endpoint should return HTTP 401 without a bearer token, received HTTP ${response.status}.`);
|
|
5372
|
+
}
|
|
5373
|
+
if (!response.headers.get("www-authenticate")?.toLowerCase().includes("bearer")) {
|
|
5374
|
+
throw new Error("Tunnel validation failed: authenticated MCP endpoint did not include a Bearer WWW-Authenticate challenge.");
|
|
5375
|
+
}
|
|
5376
|
+
parseJsonObject(body, options.mcpUrl.toString());
|
|
5377
|
+
}
|
|
5378
|
+
async function validateInitialize(options) {
|
|
5379
|
+
const response = await postJsonRpc(options.mcpUrl, {
|
|
5380
|
+
jsonrpc: "2.0",
|
|
5381
|
+
id: 1,
|
|
5382
|
+
method: "initialize",
|
|
5383
|
+
params: {
|
|
5384
|
+
protocolVersion: "2025-11-25",
|
|
5385
|
+
capabilities: {},
|
|
5386
|
+
clientInfo: { name: "sidecar-tunnel-check", version: "0.0.0" }
|
|
5387
|
+
}
|
|
5388
|
+
}, options.timeoutMs);
|
|
5389
|
+
const body = await readValidationBody(response);
|
|
5390
|
+
assertNotHtml(response, body, options.mcpUrl.toString());
|
|
5391
|
+
if (response.status !== 200) {
|
|
5392
|
+
if (isTransientHttpStatus(response.status)) {
|
|
5393
|
+
throw new TransientTunnelValidationError(`Tunnel validation failed: initialize returned HTTP ${response.status}.`);
|
|
5394
|
+
}
|
|
5395
|
+
throw new Error(`Tunnel validation failed: initialize returned HTTP ${response.status}.`);
|
|
5396
|
+
}
|
|
5397
|
+
const payload = parseJsonObject(body, options.mcpUrl.toString());
|
|
5398
|
+
if (!isRecord3(payload.result) || typeof payload.result.protocolVersion !== "string") {
|
|
5399
|
+
throw new Error("Tunnel validation failed: initialize did not return an MCP server result.");
|
|
5400
|
+
}
|
|
5401
|
+
}
|
|
5402
|
+
async function validateToolsList(options) {
|
|
5403
|
+
const response = await postJsonRpc(options.mcpUrl, {
|
|
5404
|
+
jsonrpc: "2.0",
|
|
5405
|
+
id: 2,
|
|
5406
|
+
method: "tools/list"
|
|
5407
|
+
}, options.timeoutMs);
|
|
5408
|
+
const body = await readValidationBody(response);
|
|
5409
|
+
assertNotHtml(response, body, options.mcpUrl.toString());
|
|
5410
|
+
if (response.status !== 200) {
|
|
5411
|
+
if (isTransientHttpStatus(response.status)) {
|
|
5412
|
+
throw new TransientTunnelValidationError(`Tunnel validation failed: tools/list returned HTTP ${response.status}.`);
|
|
5413
|
+
}
|
|
5414
|
+
throw new Error(`Tunnel validation failed: tools/list returned HTTP ${response.status}.`);
|
|
5415
|
+
}
|
|
5416
|
+
const payload = parseJsonObject(body, options.mcpUrl.toString());
|
|
5417
|
+
if (!isRecord3(payload.result) || !Array.isArray(payload.result.tools)) {
|
|
5418
|
+
throw new Error("Tunnel validation failed: tools/list did not return a tools array.");
|
|
5419
|
+
}
|
|
5420
|
+
}
|
|
5421
|
+
function postJsonRpc(url, payload, timeoutMs) {
|
|
5422
|
+
return fetchWithTimeout(url.toString(), {
|
|
5423
|
+
method: "POST",
|
|
5424
|
+
headers: {
|
|
5425
|
+
accept: "application/json, text/event-stream",
|
|
5426
|
+
"content-type": "application/json"
|
|
5427
|
+
},
|
|
5428
|
+
body: JSON.stringify(payload),
|
|
5429
|
+
timeoutMs
|
|
5430
|
+
});
|
|
5431
|
+
}
|
|
5432
|
+
async function fetchWithTimeout(url, options) {
|
|
5433
|
+
const controller = new AbortController();
|
|
5434
|
+
const timeout = setTimeout(() => controller.abort(), options.timeoutMs);
|
|
5435
|
+
try {
|
|
5436
|
+
return await fetch(url, {
|
|
5437
|
+
...options,
|
|
5438
|
+
signal: controller.signal
|
|
5439
|
+
});
|
|
5440
|
+
} catch (error) {
|
|
5441
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
5442
|
+
throw new TransientTunnelValidationError(`Tunnel validation failed: timed out fetching ${url}.`);
|
|
5443
|
+
}
|
|
5444
|
+
throw new TransientTunnelValidationError(
|
|
5445
|
+
`Tunnel validation failed: could not fetch ${url}: ${errorMessage(error)}.`
|
|
5446
|
+
);
|
|
5447
|
+
} finally {
|
|
5448
|
+
clearTimeout(timeout);
|
|
5449
|
+
}
|
|
5450
|
+
}
|
|
5451
|
+
async function retryTunnelValidation(validate, timeoutMs) {
|
|
5452
|
+
const startedAt = Date.now();
|
|
5453
|
+
let lastError;
|
|
5454
|
+
while (Date.now() - startedAt <= timeoutMs) {
|
|
5455
|
+
try {
|
|
5456
|
+
await validate();
|
|
5457
|
+
return;
|
|
5458
|
+
} catch (error) {
|
|
5459
|
+
lastError = error;
|
|
5460
|
+
if (!(error instanceof TransientTunnelValidationError)) {
|
|
5461
|
+
throw error;
|
|
5462
|
+
}
|
|
5463
|
+
await delay(500);
|
|
5464
|
+
}
|
|
5465
|
+
}
|
|
5466
|
+
throw lastError instanceof Error ? lastError : new Error("Tunnel validation failed before the public endpoint became reachable.");
|
|
5467
|
+
}
|
|
5468
|
+
var TransientTunnelValidationError = class extends Error {
|
|
5469
|
+
constructor(message) {
|
|
5470
|
+
super(message);
|
|
5471
|
+
this.name = "TransientTunnelValidationError";
|
|
5472
|
+
}
|
|
5473
|
+
};
|
|
5474
|
+
function isTransientHttpStatus(status) {
|
|
5475
|
+
return status === 408 || status === 425 || status === 429 || status >= 500;
|
|
5476
|
+
}
|
|
5477
|
+
function delay(ms) {
|
|
5478
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
5479
|
+
}
|
|
5480
|
+
async function readValidationBody(response) {
|
|
5481
|
+
const text = await response.text();
|
|
5482
|
+
return text.slice(0, 2e4);
|
|
5483
|
+
}
|
|
5484
|
+
function assertNotHtml(response, body, url) {
|
|
5485
|
+
const contentType = response.headers.get("content-type")?.toLowerCase() ?? "";
|
|
5486
|
+
const trimmed = body.trimStart().toLowerCase();
|
|
5487
|
+
if (contentType.includes("text/html") || trimmed.startsWith("<!doctype html") || trimmed.startsWith("<html")) {
|
|
5488
|
+
throw new Error([
|
|
5489
|
+
`Tunnel validation failed: ${url} returned HTML instead of MCP JSON.`,
|
|
5490
|
+
"This usually means the tunnel provider returned an interstitial, warning page, or bad route."
|
|
5491
|
+
].join("\n"));
|
|
5492
|
+
}
|
|
5493
|
+
}
|
|
5494
|
+
function parseJsonObject(body, url) {
|
|
5495
|
+
try {
|
|
5496
|
+
const parsed = JSON.parse(body);
|
|
5497
|
+
if (isRecord3(parsed)) {
|
|
5498
|
+
return parsed;
|
|
5499
|
+
}
|
|
5500
|
+
} catch {
|
|
5501
|
+
}
|
|
5502
|
+
throw new Error(`Tunnel validation failed: ${url} did not return a JSON object.`);
|
|
5503
|
+
}
|
|
5504
|
+
function protectedResourceMetadataUrl(mcpUrl) {
|
|
5505
|
+
const url = new URL(mcpUrl.origin);
|
|
5506
|
+
const pathname = mcpUrl.pathname.replace(/\/+$/, "") || "/";
|
|
5507
|
+
url.pathname = pathname === "/" ? "/.well-known/oauth-protected-resource" : `/.well-known/oauth-protected-resource${pathname}`;
|
|
5508
|
+
return url.toString();
|
|
5509
|
+
}
|
|
5510
|
+
function tunnelOutputHint(output) {
|
|
5511
|
+
const trimmed = output.trim();
|
|
5512
|
+
return trimmed ? `Tunnel output:
|
|
5513
|
+
${trimmed}` : "";
|
|
5514
|
+
}
|
|
5515
|
+
function tail(value, maxLength) {
|
|
5516
|
+
return value.length > maxLength ? value.slice(value.length - maxLength) : value;
|
|
5517
|
+
}
|
|
5518
|
+
function errorMessage(error) {
|
|
5519
|
+
if (error instanceof Error) {
|
|
5520
|
+
const cause = error.cause;
|
|
5521
|
+
const causeMessage = cause instanceof Error ? ` (${cause.message})` : "";
|
|
5522
|
+
return `${error.message}${causeMessage}`;
|
|
5523
|
+
}
|
|
5524
|
+
return String(error);
|
|
5525
|
+
}
|
|
4202
5526
|
function hasCommand(command) {
|
|
4203
5527
|
return spawnSync("which", [command], { stdio: "ignore" }).status === 0;
|
|
4204
5528
|
}
|
|
@@ -4215,8 +5539,11 @@ function runInherited(command, args) {
|
|
|
4215
5539
|
});
|
|
4216
5540
|
});
|
|
4217
5541
|
}
|
|
4218
|
-
function appendPath(origin,
|
|
4219
|
-
return `${origin.replace(/\/+$/, "")}/${
|
|
5542
|
+
function appendPath(origin, path21) {
|
|
5543
|
+
return `${origin.replace(/\/+$/, "")}/${path21.replace(/^\/+/, "")}`;
|
|
5544
|
+
}
|
|
5545
|
+
function isRecord3(value) {
|
|
5546
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
4220
5547
|
}
|
|
4221
5548
|
|
|
4222
5549
|
// packages/cli/src/index.ts
|
|
@@ -4225,19 +5552,27 @@ async function main(argv) {
|
|
|
4225
5552
|
const rootDir = readOption(argv, "--cwd") ?? cwd();
|
|
4226
5553
|
switch (command) {
|
|
4227
5554
|
case "build": {
|
|
4228
|
-
const target =
|
|
4229
|
-
const
|
|
4230
|
-
const
|
|
5555
|
+
const target = readOptionalTarget(argv);
|
|
5556
|
+
const host = readOptionalHost(argv) ?? detectHostFromEnvironment();
|
|
5557
|
+
const outDir = readOption(argv, "--out");
|
|
5558
|
+
const plugins = readOptionalPlugins(argv);
|
|
4231
5559
|
const strict = argv.includes("--strict");
|
|
4232
|
-
const manifest = await buildProject({ rootDir, outDir, plugins, strict, target });
|
|
5560
|
+
const manifest = await buildProject({ rootDir, host, outDir, plugins, strict, target });
|
|
5561
|
+
const resolvedOutDir = outDir ?? manifest.config.build.outDir ?? defaultBuildOutDir2(manifest.host, manifest.target);
|
|
4233
5562
|
printDiagnostics(manifest.diagnostics ?? []);
|
|
4234
5563
|
if (strict && manifest.diagnostics?.length) {
|
|
4235
5564
|
exit(1);
|
|
4236
5565
|
}
|
|
4237
5566
|
console.log(
|
|
4238
|
-
`Built ${manifest.tools.length} ${target} tool${manifest.tools.length === 1 ? "" : "s"}, ${manifest.resources.length} resource${manifest.resources.length === 1 ? "" : "s"}, and ${manifest.prompts.length} prompt${manifest.prompts.length === 1 ? "" : "s"} to ${
|
|
5567
|
+
`Built ${manifest.tools.length} ${manifest.target} tool${manifest.tools.length === 1 ? "" : "s"}, ${manifest.resources.length} resource${manifest.resources.length === 1 ? "" : "s"}, and ${manifest.prompts.length} prompt${manifest.prompts.length === 1 ? "" : "s"} to ${resolvedOutDir}.`
|
|
4239
5568
|
);
|
|
4240
|
-
|
|
5569
|
+
console.log(`Host runtime: ${manifest.host}`);
|
|
5570
|
+
if (manifest.host === "vercel") {
|
|
5571
|
+
console.log(`Vercel MCP function: ${path20.join(resolvedOutDir, VERCEL_ENTRYPOINT)}`);
|
|
5572
|
+
} else {
|
|
5573
|
+
console.log(`Hostable MCP server: ${path20.join(resolvedOutDir, SERVER_ENTRYPOINT)}`);
|
|
5574
|
+
}
|
|
5575
|
+
if (plugins ?? manifest.config.build.plugins) {
|
|
4241
5576
|
console.log("Built claude-plugin package.");
|
|
4242
5577
|
}
|
|
4243
5578
|
return;
|
|
@@ -4268,18 +5603,20 @@ async function main(argv) {
|
|
|
4268
5603
|
const target = readTarget(argv);
|
|
4269
5604
|
const tunnelProvider = readTunnelProvider(argv);
|
|
4270
5605
|
const outDir = `.sidecar/dev/${target}`;
|
|
5606
|
+
const loadedAuth = await loadRuntimeAuth(rootDir);
|
|
5607
|
+
const runtimeConfig = await loadRuntimeConfig(rootDir);
|
|
5608
|
+
const proxy = await loadRuntimeProxy(rootDir);
|
|
5609
|
+
const runtimeToolEntries = await analyzeProjectTools(rootDir, { target });
|
|
5610
|
+
const runtimeTools = await loadRuntimeTools(rootDir, runtimeToolEntries);
|
|
4271
5611
|
const manifest = await buildProject({ rootDir, outDir, target });
|
|
4272
5612
|
printDiagnostics(manifest.diagnostics ?? []);
|
|
4273
|
-
const tools =
|
|
5613
|
+
const tools = attachBuiltToolDescriptors(runtimeTools, manifest);
|
|
4274
5614
|
let tunnel;
|
|
4275
5615
|
if (tunnelProvider) {
|
|
4276
5616
|
tunnel = await startTunnel({ provider: tunnelProvider, port, path: "/mcp" });
|
|
4277
5617
|
process.env.SIDECAR_MCP_URL = tunnel.mcpUrl;
|
|
4278
5618
|
}
|
|
4279
|
-
const loadedAuth = await loadRuntimeAuth(rootDir);
|
|
4280
|
-
const runtimeConfig = await loadRuntimeConfig(rootDir);
|
|
4281
5619
|
const auth = loadedAuth && tunnel ? loadedAuth.withResource(tunnel.mcpUrl) : loadedAuth;
|
|
4282
|
-
const proxy = await loadRuntimeProxy(rootDir);
|
|
4283
5620
|
const resources = await loadResources(rootDir, outDir, manifest);
|
|
4284
5621
|
const prompts = await loadRuntimePrompts(rootDir, manifest);
|
|
4285
5622
|
const server = createSidecarHttpServer({
|
|
@@ -4300,14 +5637,29 @@ async function main(argv) {
|
|
|
4300
5637
|
pageSize: manifest.config.pagination.pageSize
|
|
4301
5638
|
}
|
|
4302
5639
|
});
|
|
4303
|
-
|
|
5640
|
+
let listening = false;
|
|
5641
|
+
try {
|
|
5642
|
+
await listenOnLocalhost(server, port);
|
|
5643
|
+
listening = true;
|
|
5644
|
+
if (tunnel) {
|
|
5645
|
+
await validateTunnelEndpoint({
|
|
5646
|
+
mcpUrl: tunnel.mcpUrl,
|
|
5647
|
+
auth: Boolean(auth)
|
|
5648
|
+
});
|
|
5649
|
+
}
|
|
4304
5650
|
console.log(`MCP running on Streamable HTTP (${target}) at http://127.0.0.1:${port}/mcp`);
|
|
4305
5651
|
console.log(`Loaded ${tools.length} tool${tools.length === 1 ? "" : "s"}, ${resources.length} resource${resources.length === 1 ? "" : "s"}, and ${prompts.length} prompt${prompts.length === 1 ? "" : "s"}.`);
|
|
4306
5652
|
if (tunnel) {
|
|
4307
|
-
console.log(`HTTPS tunnel (${tunnel.provider})
|
|
5653
|
+
console.log(`HTTPS tunnel (${tunnel.provider}) validated: ${tunnel.mcpUrl}`);
|
|
4308
5654
|
console.log("Use this HTTPS MCP URL in ChatGPT, Claude, or a Claude plugin install.");
|
|
4309
5655
|
}
|
|
4310
|
-
})
|
|
5656
|
+
} catch (error) {
|
|
5657
|
+
tunnel?.close();
|
|
5658
|
+
if (listening) {
|
|
5659
|
+
await closeServer(server);
|
|
5660
|
+
}
|
|
5661
|
+
throw error;
|
|
5662
|
+
}
|
|
4311
5663
|
const shutdown = () => {
|
|
4312
5664
|
tunnel?.close();
|
|
4313
5665
|
server.close(() => exit(0));
|
|
@@ -4350,7 +5702,7 @@ async function main(argv) {
|
|
|
4350
5702
|
console.log(`Sidecar
|
|
4351
5703
|
|
|
4352
5704
|
Usage:
|
|
4353
|
-
sidecar build [--cwd <dir>] [--target mcp|chatgpt|claude] [--out <dir>] [--plugins] [--strict]
|
|
5705
|
+
sidecar build [--cwd <dir>] [--target mcp|chatgpt|claude] [--host node|vercel] [--out <dir>] [--plugins|--no-plugins] [--strict]
|
|
4354
5706
|
sidecar check [--cwd <dir>] [--target mcp|chatgpt|claude] [--strict]
|
|
4355
5707
|
sidecar dev [--cwd <dir>] [--target mcp|chatgpt|claude] [--port <port>] [--tunnel [cloudflared|wrangler]]
|
|
4356
5708
|
sidecar inspect [--cwd <dir>] [--target mcp|chatgpt|claude]
|
|
@@ -4358,16 +5710,54 @@ Usage:
|
|
|
4358
5710
|
`);
|
|
4359
5711
|
}
|
|
4360
5712
|
}
|
|
5713
|
+
function defaultBuildOutDir2(host, target) {
|
|
5714
|
+
if (host === "vercel") {
|
|
5715
|
+
return ".vercel/output";
|
|
5716
|
+
}
|
|
5717
|
+
return `out/${target}`;
|
|
5718
|
+
}
|
|
4361
5719
|
function readTarget(argv) {
|
|
4362
|
-
const target =
|
|
5720
|
+
const target = readOptionalTarget(argv) ?? "mcp";
|
|
5721
|
+
return target;
|
|
5722
|
+
}
|
|
5723
|
+
function readOptionalTarget(argv) {
|
|
5724
|
+
const target = readOption(argv, "--target");
|
|
5725
|
+
if (!target) {
|
|
5726
|
+
return void 0;
|
|
5727
|
+
}
|
|
4363
5728
|
if (target === "mcp" || target === "chatgpt" || target === "claude") {
|
|
4364
5729
|
return target;
|
|
4365
5730
|
}
|
|
4366
5731
|
throw new Error(`Unsupported Sidecar target "${target}". Expected mcp, chatgpt, or claude.`);
|
|
4367
5732
|
}
|
|
5733
|
+
function readOptionalHost(argv) {
|
|
5734
|
+
const host = readOption(argv, "--host");
|
|
5735
|
+
if (!host) {
|
|
5736
|
+
return void 0;
|
|
5737
|
+
}
|
|
5738
|
+
if (host === "node" || host === "vercel") {
|
|
5739
|
+
return host;
|
|
5740
|
+
}
|
|
5741
|
+
throw new Error(`Unsupported Sidecar host "${host}". Expected node or vercel.`);
|
|
5742
|
+
}
|
|
5743
|
+
function detectHostFromEnvironment() {
|
|
5744
|
+
if (process.env.VERCEL === "1") {
|
|
5745
|
+
return "vercel";
|
|
5746
|
+
}
|
|
5747
|
+
return void 0;
|
|
5748
|
+
}
|
|
5749
|
+
function readOptionalPlugins(argv) {
|
|
5750
|
+
if (argv.includes("--plugins")) {
|
|
5751
|
+
return true;
|
|
5752
|
+
}
|
|
5753
|
+
if (argv.includes("--no-plugins")) {
|
|
5754
|
+
return false;
|
|
5755
|
+
}
|
|
5756
|
+
return void 0;
|
|
5757
|
+
}
|
|
4368
5758
|
async function previewComponents(options) {
|
|
4369
|
-
const cliDir =
|
|
4370
|
-
const css = await readFile8(
|
|
5759
|
+
const cliDir = path20.dirname(fileURLToPath(import.meta.url));
|
|
5760
|
+
const css = await readFile8(path20.join(cliDir, "../../native/src/styles.css"), "utf8").catch(() => readFile8(path20.join(process.cwd(), "packages/native/src/styles.css"), "utf8")).catch(() => "");
|
|
4371
5761
|
const html = renderComponentPreviewHtml(
|
|
4372
5762
|
options.host,
|
|
4373
5763
|
options.compare,
|
|
@@ -4407,10 +5797,10 @@ async function previewComponents(options) {
|
|
|
4407
5797
|
server.close();
|
|
4408
5798
|
}
|
|
4409
5799
|
async function writeComponentApproval(rootDir, approval) {
|
|
4410
|
-
const dir =
|
|
4411
|
-
await
|
|
4412
|
-
await
|
|
4413
|
-
|
|
5800
|
+
const dir = path20.join(rootDir, ".sidecar", "approvals");
|
|
5801
|
+
await mkdir10(dir, { recursive: true });
|
|
5802
|
+
await writeFile10(
|
|
5803
|
+
path20.join(dir, `components.${approval.host}.json`),
|
|
4414
5804
|
`${JSON.stringify(approval, null, 2)}
|
|
4415
5805
|
`
|
|
4416
5806
|
);
|
|
@@ -4762,84 +6152,98 @@ function printDiagnostics(diagnostics) {
|
|
|
4762
6152
|
}
|
|
4763
6153
|
}
|
|
4764
6154
|
async function loadRuntimeAuth(rootDir) {
|
|
4765
|
-
const authPath =
|
|
4766
|
-
if (!
|
|
6155
|
+
const authPath = path20.join(rootDir, "auth.ts");
|
|
6156
|
+
if (!existsSync7(authPath)) {
|
|
4767
6157
|
return void 0;
|
|
4768
6158
|
}
|
|
4769
|
-
const parentURL =
|
|
4770
|
-
const tsconfigPath =
|
|
4771
|
-
const tsconfig =
|
|
4772
|
-
const module = await
|
|
6159
|
+
const parentURL = pathToFileURL3(path20.join(rootDir, "sidecar.config.ts")).href;
|
|
6160
|
+
const tsconfigPath = path20.join(rootDir, "tsconfig.json");
|
|
6161
|
+
const tsconfig = existsSync7(tsconfigPath) ? tsconfigPath : false;
|
|
6162
|
+
const module = await tsImport2(pathToFileURL3(authPath).href, {
|
|
4773
6163
|
parentURL,
|
|
4774
6164
|
tsconfig
|
|
4775
6165
|
});
|
|
4776
|
-
|
|
6166
|
+
const defaultExport = unwrapRuntimeDefault2(module.default);
|
|
6167
|
+
if (!isSidecarAuth(defaultExport)) {
|
|
4777
6168
|
throw new Error("auth.ts must default-export auth({ ... }) from sidecar-ai.");
|
|
4778
6169
|
}
|
|
4779
|
-
return
|
|
6170
|
+
return defaultExport;
|
|
4780
6171
|
}
|
|
4781
6172
|
async function loadRuntimeProxy(rootDir) {
|
|
4782
|
-
const proxyPath =
|
|
4783
|
-
if (!
|
|
6173
|
+
const proxyPath = path20.join(rootDir, "proxy.ts");
|
|
6174
|
+
if (!existsSync7(proxyPath)) {
|
|
4784
6175
|
return void 0;
|
|
4785
6176
|
}
|
|
4786
|
-
const parentURL =
|
|
4787
|
-
const tsconfigPath =
|
|
4788
|
-
const tsconfig =
|
|
4789
|
-
const module = await
|
|
6177
|
+
const parentURL = pathToFileURL3(path20.join(rootDir, "sidecar.config.ts")).href;
|
|
6178
|
+
const tsconfigPath = path20.join(rootDir, "tsconfig.json");
|
|
6179
|
+
const tsconfig = existsSync7(tsconfigPath) ? tsconfigPath : false;
|
|
6180
|
+
const module = await tsImport2(pathToFileURL3(proxyPath).href, {
|
|
4790
6181
|
parentURL,
|
|
4791
6182
|
tsconfig
|
|
4792
6183
|
});
|
|
4793
|
-
|
|
6184
|
+
const defaultExport = unwrapRuntimeDefault2(module.default);
|
|
6185
|
+
if (!isSidecarProxy(defaultExport)) {
|
|
4794
6186
|
throw new Error("proxy.ts must default-export proxy({ ... }) from @sidecar-ai/server/proxy.");
|
|
4795
6187
|
}
|
|
4796
|
-
return
|
|
6188
|
+
return defaultExport;
|
|
4797
6189
|
}
|
|
4798
6190
|
async function loadRuntimeConfig(rootDir) {
|
|
4799
|
-
const configPath =
|
|
4800
|
-
if (!
|
|
6191
|
+
const configPath = path20.join(rootDir, "sidecar.config.ts");
|
|
6192
|
+
if (!existsSync7(configPath)) {
|
|
4801
6193
|
return void 0;
|
|
4802
6194
|
}
|
|
4803
|
-
const parentURL =
|
|
4804
|
-
const tsconfigPath =
|
|
4805
|
-
const tsconfig =
|
|
4806
|
-
const module = await
|
|
6195
|
+
const parentURL = pathToFileURL3(configPath).href;
|
|
6196
|
+
const tsconfigPath = path20.join(rootDir, "tsconfig.json");
|
|
6197
|
+
const tsconfig = existsSync7(tsconfigPath) ? tsconfigPath : false;
|
|
6198
|
+
const module = await tsImport2(pathToFileURL3(configPath).href, {
|
|
4807
6199
|
parentURL,
|
|
4808
6200
|
tsconfig
|
|
4809
6201
|
});
|
|
4810
|
-
|
|
6202
|
+
const defaultExport = unwrapRuntimeDefault2(module.default);
|
|
6203
|
+
if (!defaultExport || typeof defaultExport !== "object") {
|
|
4811
6204
|
throw new Error("sidecar.config.ts must default-export defineConfig({ ... }) from sidecar-ai.");
|
|
4812
6205
|
}
|
|
4813
|
-
return
|
|
6206
|
+
return defaultExport;
|
|
4814
6207
|
}
|
|
4815
|
-
async function loadRuntimeTools(rootDir,
|
|
4816
|
-
const parentURL =
|
|
4817
|
-
const tsconfigPath =
|
|
4818
|
-
const tsconfig =
|
|
6208
|
+
async function loadRuntimeTools(rootDir, entries) {
|
|
6209
|
+
const parentURL = pathToFileURL3(path20.join(rootDir, "sidecar.config.ts")).href;
|
|
6210
|
+
const tsconfigPath = path20.join(rootDir, "tsconfig.json");
|
|
6211
|
+
const tsconfig = existsSync7(tsconfigPath) ? tsconfigPath : false;
|
|
4819
6212
|
const loaded = [];
|
|
4820
|
-
for (const entry of
|
|
4821
|
-
const sourcePath =
|
|
4822
|
-
const module = await
|
|
6213
|
+
for (const entry of entries) {
|
|
6214
|
+
const sourcePath = path20.join(rootDir, entry.sourceFile);
|
|
6215
|
+
const module = await tsImport2(pathToFileURL3(sourcePath).href, {
|
|
4823
6216
|
parentURL,
|
|
4824
6217
|
tsconfig
|
|
4825
6218
|
});
|
|
4826
|
-
|
|
6219
|
+
const defaultExport = unwrapRuntimeDefault2(module.default);
|
|
6220
|
+
if (!isSidecarTool(defaultExport)) {
|
|
4827
6221
|
throw new Error(`${entry.sourceFile} did not default-export a Sidecar tool.`);
|
|
4828
6222
|
}
|
|
4829
6223
|
loaded.push({
|
|
4830
|
-
|
|
6224
|
+
sourceFile: entry.sourceFile,
|
|
6225
|
+
tool: defaultExport,
|
|
4831
6226
|
descriptor: entry.descriptor
|
|
4832
6227
|
});
|
|
4833
6228
|
}
|
|
4834
6229
|
return loaded;
|
|
4835
6230
|
}
|
|
6231
|
+
function attachBuiltToolDescriptors(loadedTools, manifest) {
|
|
6232
|
+
const descriptorsBySource = new Map(
|
|
6233
|
+
manifest.tools.map((entry) => [entry.sourceFile, entry.descriptor])
|
|
6234
|
+
);
|
|
6235
|
+
return loadedTools.map(({ sourceFile, tool, descriptor }) => ({
|
|
6236
|
+
tool,
|
|
6237
|
+
descriptor: descriptorsBySource.get(sourceFile) ?? descriptor
|
|
6238
|
+
}));
|
|
6239
|
+
}
|
|
4836
6240
|
async function loadResources(rootDir, outDir, manifest) {
|
|
4837
6241
|
const resources = [];
|
|
4838
6242
|
for (const entry of manifest.tools) {
|
|
4839
6243
|
if (!entry.widget?.outputFile) {
|
|
4840
6244
|
continue;
|
|
4841
6245
|
}
|
|
4842
|
-
const text = await readFile8(
|
|
6246
|
+
const text = await readFile8(path20.join(rootDir, outDir, entry.widget.outputFile), "utf8");
|
|
4843
6247
|
resources.push({
|
|
4844
6248
|
uri: entry.widget.resourceUri,
|
|
4845
6249
|
name: entry.name,
|
|
@@ -4849,47 +6253,72 @@ async function loadResources(rootDir, outDir, manifest) {
|
|
|
4849
6253
|
_meta: entry.widget.resourceMeta
|
|
4850
6254
|
});
|
|
4851
6255
|
}
|
|
4852
|
-
const parentURL =
|
|
4853
|
-
const tsconfigPath =
|
|
4854
|
-
const tsconfig =
|
|
6256
|
+
const parentURL = pathToFileURL3(path20.join(rootDir, "sidecar.config.ts")).href;
|
|
6257
|
+
const tsconfigPath = path20.join(rootDir, "tsconfig.json");
|
|
6258
|
+
const tsconfig = existsSync7(tsconfigPath) ? tsconfigPath : false;
|
|
4855
6259
|
for (const entry of manifest.resources) {
|
|
4856
|
-
const sourcePath =
|
|
4857
|
-
const module = await
|
|
6260
|
+
const sourcePath = path20.join(rootDir, entry.sourceFile);
|
|
6261
|
+
const module = await tsImport2(pathToFileURL3(sourcePath).href, {
|
|
4858
6262
|
parentURL,
|
|
4859
6263
|
tsconfig
|
|
4860
6264
|
});
|
|
4861
|
-
|
|
6265
|
+
const defaultExport = unwrapRuntimeDefault2(module.default);
|
|
6266
|
+
if (!isSidecarResource(defaultExport)) {
|
|
4862
6267
|
throw new Error(`${entry.sourceFile} did not default-export a Sidecar resource.`);
|
|
4863
6268
|
}
|
|
4864
6269
|
resources.push({
|
|
4865
6270
|
uri: entry.uri,
|
|
4866
6271
|
descriptor: entry.descriptor,
|
|
4867
|
-
resource:
|
|
6272
|
+
resource: defaultExport
|
|
4868
6273
|
});
|
|
4869
6274
|
}
|
|
4870
6275
|
return resources;
|
|
4871
6276
|
}
|
|
4872
6277
|
async function loadRuntimePrompts(rootDir, manifest) {
|
|
4873
|
-
const parentURL =
|
|
4874
|
-
const tsconfigPath =
|
|
4875
|
-
const tsconfig =
|
|
6278
|
+
const parentURL = pathToFileURL3(path20.join(rootDir, "sidecar.config.ts")).href;
|
|
6279
|
+
const tsconfigPath = path20.join(rootDir, "tsconfig.json");
|
|
6280
|
+
const tsconfig = existsSync7(tsconfigPath) ? tsconfigPath : false;
|
|
4876
6281
|
const loaded = [];
|
|
4877
6282
|
for (const entry of manifest.prompts) {
|
|
4878
|
-
const sourcePath =
|
|
4879
|
-
const module = await
|
|
6283
|
+
const sourcePath = path20.join(rootDir, entry.sourceFile);
|
|
6284
|
+
const module = await tsImport2(pathToFileURL3(sourcePath).href, {
|
|
4880
6285
|
parentURL,
|
|
4881
6286
|
tsconfig
|
|
4882
6287
|
});
|
|
4883
|
-
|
|
6288
|
+
const defaultExport = unwrapRuntimeDefault2(module.default);
|
|
6289
|
+
if (!isSidecarPrompt(defaultExport)) {
|
|
4884
6290
|
throw new Error(`${entry.sourceFile} did not default-export a Sidecar prompt.`);
|
|
4885
6291
|
}
|
|
4886
6292
|
loaded.push({
|
|
4887
|
-
prompt:
|
|
6293
|
+
prompt: defaultExport,
|
|
4888
6294
|
descriptor: entry.descriptor
|
|
4889
6295
|
});
|
|
4890
6296
|
}
|
|
4891
6297
|
return loaded;
|
|
4892
6298
|
}
|
|
6299
|
+
function unwrapRuntimeDefault2(value) {
|
|
6300
|
+
if (value && typeof value === "object" && "default" in value && Object.keys(value).every((key) => key === "default" || key === "__esModule")) {
|
|
6301
|
+
return unwrapRuntimeDefault2(value.default);
|
|
6302
|
+
}
|
|
6303
|
+
return value;
|
|
6304
|
+
}
|
|
6305
|
+
function listenOnLocalhost(server, port) {
|
|
6306
|
+
return new Promise((resolve, reject) => {
|
|
6307
|
+
const onError = (error) => {
|
|
6308
|
+
reject(error);
|
|
6309
|
+
};
|
|
6310
|
+
server.once("error", onError);
|
|
6311
|
+
server.listen(port, "127.0.0.1", () => {
|
|
6312
|
+
server.off("error", onError);
|
|
6313
|
+
resolve();
|
|
6314
|
+
});
|
|
6315
|
+
});
|
|
6316
|
+
}
|
|
6317
|
+
function closeServer(server) {
|
|
6318
|
+
return new Promise((resolve) => {
|
|
6319
|
+
server.close(() => resolve());
|
|
6320
|
+
});
|
|
6321
|
+
}
|
|
4893
6322
|
function readOption(argv, name) {
|
|
4894
6323
|
const index = argv.indexOf(name);
|
|
4895
6324
|
if (index === -1) {
|
|
@@ -4903,7 +6332,7 @@ function isDirectRun() {
|
|
|
4903
6332
|
return false;
|
|
4904
6333
|
}
|
|
4905
6334
|
const entryPath = realpathSync.native(entry);
|
|
4906
|
-
return import.meta.url ===
|
|
6335
|
+
return import.meta.url === pathToFileURL3(entryPath).href;
|
|
4907
6336
|
}
|
|
4908
6337
|
if (isDirectRun()) {
|
|
4909
6338
|
main(process.argv).catch((error) => {
|