001/*
002 * SPDX-License-Identifier: Apache-2.0
003 *
004 * Copyright 2025-2026 The Enola <https://enola.dev> Authors
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License");
007 * you may not use this file except in compliance with the License.
008 * You may obtain a copy of the License at
009 *
010 *     https://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package dev.enola.ai.dotagent;
019
020import com.google.adk.agents.BaseAgent;
021import com.google.adk.agents.LlmAgent;
022import com.google.adk.models.BaseLlm;
023import com.google.adk.tools.BaseToolset;
024import com.google.common.base.Strings;
025import com.google.common.collect.ImmutableList;
026import com.google.common.net.MediaType;
027import com.google.genai.types.Schema;
028
029import dev.enola.ai.adk.tool.ToolsetProvider;
030import dev.enola.ai.dotprompt.DotPromptLoader;
031import dev.enola.ai.iri.Provider;
032import dev.enola.common.function.MoreStreams;
033import dev.enola.common.io.iri.URIs;
034import dev.enola.common.io.object.ObjectWriter;
035import dev.enola.common.io.object.jackson.JsonObjectReaderWriter;
036import dev.enola.common.io.resource.ResourceProvider;
037
038import java.io.IOException;
039import java.net.URI;
040import java.util.ArrayList;
041import java.util.List;
042import java.util.Map;
043import java.util.stream.Stream;
044
045public class AgentsLoader {
046
047    // TODO Detect Agent name/id conflicts between agents in DIFFERENT resources
048
049    private final ToolsetProvider toolsProvider;
050    private final Provider<BaseLlm> llmProvider;
051    private final BaseLlm defaultLLM;
052
053    private final DotPromptLoader dotPromptLoader;
054    // TODO private final DotPrompt2LlmAgentConverter dotPrompt2LlmAgentConverter;
055    private final ObjectWriter objectWriter = new JsonObjectReaderWriter();
056
057    private final AgentsModelLoader agentsModelLoader;
058
059    public AgentsLoader(
060            ResourceProvider resourceProvider,
061            URI defaultLLM,
062            Provider<BaseLlm> llmProvider,
063            ToolsetProvider toolsProvider) {
064        this.dotPromptLoader = new DotPromptLoader(resourceProvider, defaultLLM);
065        this.agentsModelLoader = new AgentsModelLoader(resourceProvider);
066        this.llmProvider = llmProvider;
067        this.defaultLLM = llmProvider.get(defaultLLM);
068        this.toolsProvider = toolsProvider;
069    }
070
071    public Iterable<BaseAgent> load(Stream<URI> uris) throws IOException {
072        var allLoadedAgents = new ArrayList<BaseAgent>();
073        // TODO Load in parallel! (Req. using ConcurrentLinkedQueue instead of ArrayList.)
074        MoreStreams.forEach(
075                uris,
076                uri -> {
077                    var agents = load(uri);
078                    allLoadedAgents.addAll(agents);
079                });
080        return ImmutableList.copyOf(allLoadedAgents);
081    }
082
083    private List<BaseAgent> load(URI uri) throws IOException {
084        if (URIs.hasExtension(uri, ".prompt", ".prompt.md")) {
085            var loadedDotPrompt = dotPromptLoader.load(uri);
086            throw new UnsupportedOperationException(
087                    "https://github.com/google/adk-java/issues/288");
088        }
089
090        var agentsModel = agentsModelLoader.load(uri);
091        var agents = ImmutableList.<BaseAgent>builderWithExpectedSize(agentsModel.agents.size());
092        for (var agent : agentsModel.agents) {
093            // TODO Support SequentialAgent, ParallelAgent, LoopAgent
094            var agentBuilder = new LlmAgent.Builder();
095
096            // TODO Share this code with DotPromptLoader / DotPrompt2LlmAgent !!
097
098            agentBuilder.name(agent.name);
099            // TODO Handle agent.variant ...
100
101            agentBuilder.description(agent.description);
102            agentBuilder.instruction(agent.instruction);
103
104            if (Strings.isNullOrEmpty(agent.model)) agentBuilder.model(defaultLLM);
105            else agentBuilder.model(llmProvider.get(agent.model, uri));
106
107            // TODO Validate both input & output JSON Schemas!!
108            if (agent.input != null) agentBuilder.inputSchema(schema(agent.input.schema));
109            if (agent.output != null) agentBuilder.outputSchema(schema(agent.output.schema));
110
111            var tools = new ArrayList<BaseToolset>(agent.tools.size());
112            for (var toolName : agent.tools) {
113                tools.add(toolsProvider.get(toolName, uri));
114            }
115            agentBuilder.tools(tools);
116
117            agents.add(agentBuilder.build());
118        }
119
120        return agents.build();
121    }
122
123    private Schema schema(Map<String, Object> jsonSchemaMap) throws IOException {
124        var jsonText = objectWriter.write(jsonSchemaMap, MediaType.JSON_UTF_8).get();
125        return Schema.fromJson(jsonText);
126    }
127}