agent-contracts-runtime
Version:
Runtime bridge for executing agent-contracts workflows on Agent SDKs
185 lines (184 loc) • 6.35 kB
JavaScript
import {
getBlockingViolations
} from "../chunk-MGRZBGQL.js";
// src/adapters/adk-sdk.ts
function sanitizeAgentName(name) {
let id = name.replace(/[^a-zA-Z0-9_]/g, "_");
if (id.length === 0 || /^[0-9]/.test(id)) id = `agent_${id}`;
if (id === "user") id = "user_agent";
return id;
}
function extractEventText(event) {
const parts = event.content?.parts;
if (!parts) return "";
return parts.map((p) => p.text ?? "").join("");
}
var AdkSdkAdapter = class _AdkSdkAdapter {
apiKey;
model;
rootAgentName;
guardrailHooks;
cacheConfig;
lastMemoryRef = null;
runCounter = 0;
sdk = null;
constructor(config = {}) {
this.apiKey = config.apiKey;
this.model = config.model ?? "gemini-2.5-flash";
this.rootAgentName = config.rootAgentName ?? "root_agent";
this.guardrailHooks = config.guardrailHooks;
this.cacheConfig = config.cacheConfig ?? { enabled: true };
}
// -------------------------------------------------------------------------
// Internal helpers
// -------------------------------------------------------------------------
async resolveSdk() {
if (this.sdk) return this.sdk;
const mod = await import("@google/adk");
this.sdk = {
LlmAgent: mod.LlmAgent,
InMemoryRunner: mod.InMemoryRunner,
isFinalResponse: mod.isFinalResponse,
stringifyContent: mod.stringifyContent,
Gemini: mod.Gemini
};
return this.sdk;
}
/** Resolve the `model` field for LlmAgent — a Gemini instance when an explicit
* API key is supplied, otherwise the bare model id (ADK reads env keys). */
resolveModel(sdk) {
if (this.apiKey) {
return new sdk.Gemini({ model: this.model, apiKey: this.apiKey });
}
return this.model;
}
/** Build the root LlmAgent with candidate sub-agents registered for transfer. */
buildRootAgent(sdk, instruction, agents) {
const subAgents = (agents ?? []).map((a) => {
const config = {
name: sanitizeAgentName(a.name),
description: a.description,
instruction: a.prompt,
model: a.model ?? this.resolveModel(sdk)
};
if (a.tools) config.tools = a.tools;
return new sdk.LlmAgent(config);
});
const rootConfig = {
name: sanitizeAgentName(this.rootAgentName),
instruction,
model: this.resolveModel(sdk)
};
if (subAgents.length > 0) rootConfig.subAgents = subAgents;
return new sdk.LlmAgent(rootConfig);
}
async runAgent(instruction, userMessage, agents, onProgress) {
if (this.guardrailHooks) {
const results = this.guardrailHooks.runChecks({ command: userMessage });
const blocking = getBlockingViolations(results);
if (blocking.length > 0) {
const msg = blocking.map((r) => `[${r.guardrail_id}] ${r.message}`).join("\n");
throw new Error(msg);
}
}
const sdk = await this.resolveSdk();
const root = this.buildRootAgent(sdk, instruction, agents);
const runner = new sdk.InMemoryRunner({ agent: root });
let finalText = "";
const stream = runner.runEphemeral({
userId: "agent-runtime",
newMessage: { role: "user", parts: [{ text: userMessage }] }
});
for await (const event of stream) {
if (onProgress) emitProgressFromAdkEvent(event, onProgress);
const ev = event;
if (ev.errorCode || ev.errorMessage) {
throw new Error(
`Gemini API error (${ev.errorCode ?? "unknown"}): ${ev.errorMessage ?? "no details"}`
);
}
if (sdk.isFinalResponse(event) && event.content) {
const text = sdk.stringifyContent(event) || extractEventText(event);
if (text) finalText = text;
}
}
return finalText;
}
nextMemoryRef() {
return {
id: `adk-run-${++this.runCounter}-${Date.now()}`,
provider: "google-adk",
compat: `google-adk@${this.model}`,
created_at: (/* @__PURE__ */ new Date()).toISOString()
};
}
// -------------------------------------------------------------------------
// SdkAdapter interface
// -------------------------------------------------------------------------
async send(prompt, options) {
this.lastMemoryRef = null;
const split = options.splitPrompt;
let instruction = "";
let userMessage;
if (split && this.cacheConfig.enabled !== false) {
instruction = split.system.join("\n\n---\n\n");
userMessage = split.user;
} else {
userMessage = prompt;
}
const text = await this.runAgent(instruction, userMessage, options.agents, options.onProgress);
this.lastMemoryRef = this.nextMemoryRef();
return text;
}
async sendExecution(request) {
this.lastMemoryRef = null;
const split = request.splitPrompt;
let instruction = "";
let userMessage;
if (split && this.cacheConfig.enabled !== false) {
instruction = split.system.join("\n\n---\n\n");
userMessage = split.user;
} else {
userMessage = request.prompt;
}
const agents = request.options.agents ?? request.agents;
const text = await this.runAgent(instruction, userMessage, agents, request.options.onProgress);
this.lastMemoryRef = this.nextMemoryRef();
return text;
}
getLastMemoryRef() {
return this.lastMemoryRef;
}
isCompatible(compat) {
return compat.startsWith("google-adk@");
}
// -------------------------------------------------------------------------
// Test support
// -------------------------------------------------------------------------
/**
* Inject the resolved ADK SDK surface for testing.
* Bypasses the dynamic import of @google/adk and the API key requirement.
*/
static withSdk(sdk, config) {
const adapter = new _AdkSdkAdapter(config);
adapter.sdk = sdk;
return adapter;
}
};
function emitProgressFromAdkEvent(event, onProgress) {
const parts = event.content?.parts;
if (!parts) return;
for (const part of parts) {
if (part.functionCall?.name) {
onProgress({ type: "tool_use", tool_name: part.functionCall.name, input: part.functionCall.args });
} else if (part.functionResponse?.name) {
onProgress({ type: "tool_result", tool_name: part.functionResponse.name });
} else if (part.text) {
onProgress({ type: "text", message: part.text });
}
}
}
export {
AdkSdkAdapter
};
//# sourceMappingURL=adk-sdk.js.map