openAgent

gitlab

The open source UNIVERSAL agent.

0 stars0 forksupdated 2d agodefault branch: dev
clonehttps://gitlab.com/bitzzdev/openAgent.git
AIagentautomationopen-sourceuniversal-agent
@ dev
export * as ToolSandbox from "./sandbox"
import { Context, Effect, Layer, Schema } from "effect"
import { ToolFailure } from "@opencode-ai/llm"
import { ToolDefinitionRecord } from "./metadata"
import { CredentialBroker } from "../credential/broker"
import { SecretRedactor } from "../credential/redactor"
import { makeGlobalNode } from "../effect/app-node"
export class SandboxExecutionError extends Schema.TaggedErrorClass<SandboxExecutionError>()(
"ToolSandbox.ExecutionError",
{
tool: Schema.String,
message: Schema.String,
},
) {}
export interface Interface {
readonly execute: (
tool: ToolDefinitionRecord,
input: unknown,
) => Effect.Effect<unknown, ToolFailure>
}
export class Service extends Context.Service<Service, Interface>()("@openagent/ToolSandbox") {}
const layer = Layer.effect(
Service,
Effect.gen(function* () {
const credentials = yield* CredentialBroker.Service
const executeHttp = async (
tool: ToolDefinitionRecord,
input: Record<string, unknown>,
creds: Record<string, string>,
): Promise<unknown> => {
let endpoint = tool.endpoint ?? ""
// Interpolate params into endpoint e.g. https://api.linear.app/graphql or https://api.github.com/repos/{owner}/{repo}
for (const [k, v] of Object.entries(input)) {
if (endpoint.includes(`{${k}}`)) {
endpoint = endpoint.replaceAll(`{${k}}`, encodeURIComponent(String(v)))
}
}
// Build headers
const headers: Record<string, string> = {
"Content-Type": "application/json",
Accept: "application/json",
"User-Agent": "OpenAgent-UniversalRuntime/1.0",
...(tool.headers ?? {}),
}
// Inject credentials into headers if specified or template
for (const [k, v] of Object.entries(headers)) {
let val = v
for (const [credKey, credVal] of Object.entries(creds)) {
val = val.replaceAll(`$${credKey}`, credVal).replaceAll(`{${credKey}}`, credVal)
}
headers[k] = val
}
const method = tool.method ?? "POST"
let body: string | undefined = undefined
if (method !== "GET") {
if (tool.bodyTemplate) {
let b = tool.bodyTemplate
for (const [k, v] of Object.entries(input)) {
b = b.replaceAll(`{${k}}`, JSON.stringify(v)).replaceAll(`$${k}`, String(v))
}
body = b
} else {
body = JSON.stringify(input)
}
}
const controller = new AbortController()
const timeout = setTimeout(() => controller.abort(), 30000)
try {
const response = await fetch(endpoint, {
method,
headers,
body,
signal: controller.signal,
})
clearTimeout(timeout)
const text = await response.text()
let parsed: unknown
try {
parsed = JSON.parse(text)
} catch {
parsed = { status: response.status, body: text }
}
if (!response.ok) {
throw new Error(`HTTP ${response.status} ${response.statusText}: ${typeof parsed === "object" ? JSON.stringify(parsed) : String(parsed)}`)
}
return SecretRedactor.redactObject(parsed)
} catch (e: any) {
clearTimeout(timeout)
throw new Error(SecretRedactor.redact(e.message ?? "HTTP request failed"))
}
}
const executeCode = async (
tool: ToolDefinitionRecord,
input: unknown,
creds: Record<string, string>,
): Promise<unknown> => {
const code = tool.code ?? ""
if (!code) {
throw new Error(`No code implementation provided for tool "${tool.name}".`)
}
// Provide controlled sandbox scope with ONLY declared credentials
const sandboxEnv = Object.freeze({ ...creds })
const runner = new Function(
"input",
"env",
"fetch",
`"use strict";
return (async () => {
${code}
})();`,
)
const timeoutPromise = new Promise((_, reject) =>
setTimeout(() => reject(new Error(`Tool "${tool.name}" execution timed out after 30s.`)), 30000),
)
const executionPromise = runner(input, sandboxEnv, fetch)
const rawResult = await Promise.race([executionPromise, timeoutPromise])
return SecretRedactor.redactObject(rawResult)
}
return Service.of({
execute: Effect.fn("ToolSandbox.execute")(function* (tool: ToolDefinitionRecord, input: unknown) {
// Resolve only declared credentials
const creds = yield* credentials
.resolveManyForTool(tool.name, tool.credentials)
.pipe(
Effect.mapError(
(err) => new ToolFailure({ message: `Credential resolution error: ${err.message}` }),
),
)
return yield* Effect.tryPromise({
try: async () => {
if (tool.executionMethod === "http" || tool.endpoint) {
return await executeHttp(tool, (input ?? {}) as Record<string, unknown>, creds)
}
return await executeCode(tool, input, creds)
},
catch: (err: any) =>
new ToolFailure({
message: SecretRedactor.redact(`Execution failure in "${tool.name}": ${err?.message ?? String(err)}`),
}),
})
}),
})
}),
)
export const defaultLayer = layer
export const node = makeGlobalNode({ service: Service, layer, deps: [CredentialBroker.node] })