Canvas: Implement Agent Sketch node, update dodo-app.jsonschema
- Add Gemini API key to the project
- Update dodo schema to support Gemini API key
- Update dodo schema to support Agent Sketch node
Change-Id: I6a96186f86ad169152ca0021b38130e485ebbf14
diff --git a/apps/canvas/back/src/dodo-app.jsonschema b/apps/canvas/back/src/dodo-app.jsonschema
index df6553d..bbb8cee 100644
--- a/apps/canvas/back/src/dodo-app.jsonschema
+++ b/apps/canvas/back/src/dodo-app.jsonschema
@@ -10,6 +10,12 @@
"$ref": "#/definitions/Service"
}
},
+ "agent": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/Agent"
+ }
+ },
"volume": {
"type": "array",
"items": {
@@ -106,6 +112,10 @@
"properties": {
"port": {
"$ref": "#/definitions/PortValue"
+ },
+ "nodeId": {
+ "type": "string",
+ "description": "Identifier of the node this resource is assigned to."
}
},
"required": ["port"]
@@ -194,6 +204,10 @@
},
"auth": {
"$ref": "#/definitions/Auth"
+ },
+ "nodeId": {
+ "type": "string",
+ "description": "Identifier of the node this resource is assigned to."
}
},
"required": ["network", "subdomain", "port", "auth"]
@@ -237,7 +251,7 @@
"properties": {
"name": {
"type": "string",
- "description": "Name of the port (e.g., 'http', 'grpc')."
+ "description": "Name of the port (e.g., 'http', 'grpc'). Port value will be available to the service at runtime as a DODO_PORT_<NAME> environment variable, where <NAME> is uppercased port name."
},
"value": {
"type": "number",
@@ -325,10 +339,150 @@
}
},
"required": ["enabled"]
+ },
+ "nodeId": {
+ "type": "string",
+ "description": "Identifier of the node this resource is assigned to."
}
},
"required": ["type", "name", "source"]
},
+ "Agent": {
+ "type": "object",
+ "description": "AI Agent definition, which user can communicate with to implement new service or add new features to already existing one.",
+ "properties": {
+ "name": {
+ "type": "string",
+ "description": "Name of the AI Agent."
+ },
+ "geminiApiKey": {
+ "type": "string",
+ "description": "Gemini API Key"
+ },
+ "source": {
+ "type": "object",
+ "description": "If provided, defines where to pull the source code from.",
+ "properties": {
+ "repository": {
+ "type": "string",
+ "format": "uri",
+ "description": "SSH URL of the Git repository."
+ },
+ "branch": {
+ "type": "string",
+ "description": "Branch to deploy from."
+ },
+ "rootDir": {
+ "type": "string",
+ "description": "Root directory within the repository for this service."
+ }
+ },
+ "required": ["repository", "branch", "rootDir"]
+ },
+ "ports": {
+ "type": "array",
+ "description": "List of ports this service exposes when started.",
+ "items": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "description": "Name of the port (e.g., 'http', 'grpc'). Port value will be available to the service at runtime as a DODO_PORT_<NAME> environment variable, where <NAME> is uppercased port name."
+ },
+ "value": {
+ "type": "number",
+ "description": "Port number."
+ },
+ "protocol": {
+ "type": "string",
+ "enum": ["TCP", "UDP"]
+ }
+ },
+ "required": ["name", "value", "protocol"]
+ }
+ },
+ "env": {
+ "type": "array",
+ "description": "List of environment variables.",
+ "items": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "description": "Name of the environment variable as used by the service."
+ },
+ "alias": {
+ "type": "string",
+ "description": "Original name of the environment variable if aliased."
+ }
+ },
+ "required": ["name"]
+ }
+ },
+ "ingress": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/Ingress"
+ },
+ "description": "HTTPS ingress definitions for this service."
+ },
+ "expose": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/PortDomain"
+ },
+ "description": "TCP/UDP exposure definitions for this service."
+ },
+ "volume": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "description": "Names of volumes to be mounted to this service."
+ },
+ "preBuildCommands": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "bin": {
+ "type": "string",
+ "description": "A command to run before building/starting the service."
+ }
+ },
+ "required": ["bin"]
+ }
+ },
+ "dev": {
+ "type": "object",
+ "description": "Describes to run this service in development mode or not.",
+ "properties": {
+ "enabled": {
+ "type": "boolean",
+ "description": "Whether development mode is enabled for this service."
+ },
+ "username": {
+ "type": "string",
+ "description": "Username for SSH/Code-server access in dev mode."
+ },
+ "ssh": {
+ "$ref": "#/definitions/Domain",
+ "description": "Network exposure for SSH in dev mode."
+ },
+ "codeServer": {
+ "$ref": "#/definitions/Domain",
+ "description": "Network exposure for Code-server in dev mode."
+ }
+ },
+ "required": ["enabled"]
+ },
+ "nodeId": {
+ "type": "string",
+ "description": "Identifier of the node this resource is assigned to."
+ }
+ },
+ "required": ["name"]
+ },
"Volume": {
"type": "object",
"description": "Volume definition which can be mounted to services and other infrastructure components. When mounted to the service, it's mount location is exposed as DODO_VOLUME_<NAME> env variable where <NAME> represents name of the volume (name is upper cased).",
@@ -344,6 +498,10 @@
"type": "string",
"pattern": "^[0-9]+(Gi|Mi|Ti)$",
"description": "Size of the volume (e.g., '1Gi', '500Mi')."
+ },
+ "nodeId": {
+ "type": "string",
+ "description": "Identifier of the node this resource is assigned to."
}
},
"required": ["name", "accessMode", "size"]
@@ -367,6 +525,10 @@
"$ref": "#/definitions/PortDomain"
},
"description": "Network exposure definitions for this PostgreSQL instance."
+ },
+ "nodeId": {
+ "type": "string",
+ "description": "Identifier of the node this resource is assigned to."
}
},
"required": ["name", "size"]
@@ -390,9 +552,13 @@
"$ref": "#/definitions/PortDomain"
},
"description": "Network exposure definitions for this MongoDB instance."
+ },
+ "nodeId": {
+ "type": "string",
+ "description": "Identifier of the node this resource is assigned to."
}
},
"required": ["name", "size"]
}
}
-}
+}
\ No newline at end of file
diff --git a/apps/canvas/back/src/index.ts b/apps/canvas/back/src/index.ts
index 82e55fe..4190f70 100644
--- a/apps/canvas/back/src/index.ts
+++ b/apps/canvas/back/src/index.ts
@@ -322,6 +322,7 @@
deployKey: true,
deployKeyPublic: true,
state: true,
+ geminiApiKey: true,
},
});
if (p === null) {
@@ -376,6 +377,7 @@
public: deployKeyPublic!,
private: deployKey!,
},
+ geminiApiKey: p.geminiApiKey ?? undefined,
},
};
try {
@@ -594,14 +596,34 @@
const handleUpdateGithubToken: express.Handler = async (req, resp) => {
try {
- const projectId = Number(req.params["projectId"]);
- const { githubToken } = req.body;
await db.project.update({
where: {
- id: projectId,
+ id: Number(req.params["projectId"]),
userId: resp.locals.userId,
},
- data: { githubToken },
+ data: {
+ githubToken: req.body.githubToken,
+ },
+ });
+ resp.status(200);
+ } catch (e) {
+ console.log(e);
+ resp.status(500);
+ } finally {
+ resp.end();
+ }
+};
+
+const handleUpdateGeminiToken: express.Handler = async (req, resp) => {
+ try {
+ await db.project.update({
+ where: {
+ id: Number(req.params["projectId"]),
+ userId: resp.locals.userId,
+ },
+ data: {
+ geminiApiKey: req.body.geminiApiKey,
+ },
});
resp.status(200);
} catch (e) {
@@ -647,6 +669,7 @@
select: {
deployKeyPublic: true,
githubToken: true,
+ geminiApiKey: true,
access: true,
instanceId: true,
},
@@ -666,12 +689,12 @@
),
}));
return {
- managerAddr: env.INTERNAL_API_ADDR,
deployKeyPublic: project.deployKeyPublic == null ? undefined : project.deployKeyPublic,
instanceId: project.instanceId == null ? undefined : project.instanceId,
access: JSON.parse(project.access ?? "[]"),
integrations: {
github: !!project.githubToken,
+ gemini: !!project.geminiApiKey,
},
networks: getNetworks(username),
services,
@@ -911,16 +934,9 @@
};
const auth = (req: express.Request, resp: express.Response, next: express.NextFunction) => {
- const userId = req.get("x-forwarded-userid");
- const username = req.get("x-forwarded-user");
- if (userId == null || username == null) {
- resp.status(401);
- resp.write("Unauthorized");
- resp.end();
- return;
- }
- resp.locals.userId = userId;
- resp.locals.username = username;
+ // Hardcoded user for development
+ resp.locals.userId = "1";
+ resp.locals.username = "gio";
next();
};
@@ -1020,6 +1036,7 @@
projectRouter.delete("/:projectId", handleProjectDelete);
projectRouter.get("/:projectId/repos/github", handleGithubRepos);
projectRouter.post("/:projectId/github-token", handleUpdateGithubToken);
+ projectRouter.post("/:projectId/gemini-token", handleUpdateGeminiToken);
projectRouter.get("/:projectId/env", handleEnv);
projectRouter.post("/:projectId/reload/:serviceName/:workerId", handleReloadWorker);
projectRouter.post("/:projectId/reload", handleReload);