|
| 1 | +import { mkdirSync } from "node:fs"; |
| 2 | +import { dirname, join } from "node:path"; |
| 3 | +import { |
| 4 | + type BackendRuntimeInput, |
| 5 | + LocalFileStateBackend, |
| 6 | + resolveProjectConfigFromObject, |
| 7 | +} from "@openagentpack/sdk"; |
| 8 | +import { getConfigDir } from "bailian-cli-core"; |
| 9 | +import { |
| 10 | + assertProviderCredentials, |
| 11 | + type CredentialHost, |
| 12 | + injectProviderCredentials, |
| 13 | + normalizeInterpolatedProviderBlocks, |
| 14 | + prepareProviderEnv, |
| 15 | + scrubCredentialEnv, |
| 16 | +} from "./credentials.ts"; |
| 17 | +import { type HostContext, installSdkTransport } from "./transport.ts"; |
| 18 | + |
| 19 | +/** Default agent identity `bl managed-agent run` materializes and reuses. */ |
| 20 | +export const DEFAULT_INLINE_AGENT = "dsh-remote-runner"; |
| 21 | + |
| 22 | +/** Default model for the materialized agent. */ |
| 23 | +export const DEFAULT_INLINE_MODEL = "qwen3.8-max"; |
| 24 | + |
| 25 | +/** Default role when the caller supplies no `--instructions`. */ |
| 26 | +export const DEFAULT_INLINE_INSTRUCTIONS = "You are a helpful assistant. Complete the task."; |
| 27 | + |
| 28 | +/** Environment name declared in the inline config; one cloud env per agent. */ |
| 29 | +const INLINE_ENVIRONMENT = "cloud"; |
| 30 | + |
| 31 | +export interface InlineAgentOptions { |
| 32 | + agentName: string; |
| 33 | + instructions: string; |
| 34 | + model: string; |
| 35 | + /** Override the persisted state location (defaults under the bl config dir). */ |
| 36 | + statePath?: string; |
| 37 | +} |
| 38 | + |
| 39 | +/** |
| 40 | + * Slugify an agent name into a filesystem- and project-id-safe token. The state |
| 41 | + * for each distinct agent lives in its own directory so repeat runs reuse the |
| 42 | + * same materialized remote agent. |
| 43 | + */ |
| 44 | +function slugify(agentName: string): string { |
| 45 | + const slug = agentName |
| 46 | + .toLowerCase() |
| 47 | + .replace(/[^a-z0-9._-]+/g, "-") |
| 48 | + .replace(/^-+|-+$/g, ""); |
| 49 | + return slug.length > 0 ? slug : "agent"; |
| 50 | +} |
| 51 | + |
| 52 | +/** Where a materialized agent's state is persisted (not the user's cwd). */ |
| 53 | +export function inlineStatePath(agentName: string): string { |
| 54 | + return join(getConfigDir(), "managed-agent", slugify(agentName), "state.json"); |
| 55 | +} |
| 56 | + |
| 57 | +/** |
| 58 | + * The minimal in-memory project config that materializes into one cloud agent. |
| 59 | + * `providers.bailian` carries empty `api_key`/`base_url` placeholders so |
| 60 | + * {@link injectProviderCredentials} fills them from bl's auth chain (it only |
| 61 | + * writes fields the block already declares). |
| 62 | + */ |
| 63 | +export function buildInlineConfig(opts: InlineAgentOptions): Record<string, unknown> { |
| 64 | + return { |
| 65 | + version: "1", |
| 66 | + providers: { |
| 67 | + bailian: { api_key: "", base_url: "" }, |
| 68 | + }, |
| 69 | + defaults: { provider: "bailian" }, |
| 70 | + environments: { |
| 71 | + [INLINE_ENVIRONMENT]: { |
| 72 | + description: "Bailian CLI cloud environment", |
| 73 | + config: { type: "cloud", networking: { type: "unrestricted" } }, |
| 74 | + }, |
| 75 | + }, |
| 76 | + agents: { |
| 77 | + [opts.agentName]: { |
| 78 | + description: opts.agentName, |
| 79 | + model: opts.model, |
| 80 | + instructions: opts.instructions, |
| 81 | + environment: INLINE_ENVIRONMENT, |
| 82 | + provider: "bailian", |
| 83 | + }, |
| 84 | + }, |
| 85 | + }; |
| 86 | +} |
| 87 | + |
| 88 | +/** |
| 89 | + * Build the `BackendRuntimeInput` shared by ensure (`syncAgentResourcesWith |
| 90 | + * StateBackend`) and run (`readProjectRuntime` + `startSessionRun`). Mirrors the |
| 91 | + * credential spine of {@link buildAgentRuntime} but sources config from an |
| 92 | + * in-memory object instead of a file, so no `agents.yaml` or `apply` is required. |
| 93 | + */ |
| 94 | +export async function buildInlineBackendInput( |
| 95 | + host: HostContext & CredentialHost, |
| 96 | + opts: InlineAgentOptions, |
| 97 | +): Promise<BackendRuntimeInput> { |
| 98 | + installSdkTransport(host); |
| 99 | + prepareProviderEnv(); |
| 100 | + |
| 101 | + const rawConfig = buildInlineConfig(opts); |
| 102 | + const { config, projectName } = await resolveProjectConfigFromObject(rawConfig, { |
| 103 | + projectName: slugify(opts.agentName), |
| 104 | + }); |
| 105 | + |
| 106 | + normalizeInterpolatedProviderBlocks(config.providers); |
| 107 | + injectProviderCredentials(config.providers, host); |
| 108 | + scrubCredentialEnv(); |
| 109 | + assertProviderCredentials(config.providers); |
| 110 | + |
| 111 | + const statePath = opts.statePath ?? inlineStatePath(opts.agentName); |
| 112 | + mkdirSync(dirname(statePath), { recursive: true }); |
| 113 | + const stateBackend = new LocalFileStateBackend({ statePath }); |
| 114 | + |
| 115 | + return { |
| 116 | + projectName, |
| 117 | + config, |
| 118 | + stateBackend, |
| 119 | + stateScope: { projectId: slugify(opts.agentName) }, |
| 120 | + providers: config.providers, |
| 121 | + }; |
| 122 | +} |
0 commit comments