diff --git a/packages/services/agent-service/src/infrastructure/services/agent-instance-deploy.service.ts b/packages/services/agent-service/src/infrastructure/services/agent-instance-deploy.service.ts index f0b5320..13a6252 100644 --- a/packages/services/agent-service/src/infrastructure/services/agent-instance-deploy.service.ts +++ b/packages/services/agent-service/src/infrastructure/services/agent-instance-deploy.service.ts @@ -38,6 +38,12 @@ export class AgentInstanceDeployService { private readonly encKey: string; private readonly agentServicePublicUrl: string; + // iConsulting LLM gateway URL — OpenClaw containers MUST route all Anthropic calls here, + // never directly to api.anthropic.com. The gateway key is provisioned in the iConsulting + // admin panel ("OpenClaw Training Server" key). + private readonly llmGatewayUrl: string; + private readonly openclawLlmGatewayKey: string; + constructor( private readonly configService: ConfigService, private readonly instanceRepo: AgentInstanceRepository, @@ -47,6 +53,8 @@ export class AgentInstanceDeployService { this.internalApiKey = this.configService.get('INTERNAL_API_KEY', ''); this.encKey = this.configService.get('VAULT_MASTER_KEY', 'dev-master-key'); this.agentServicePublicUrl = this.configService.get('AGENT_SERVICE_PUBLIC_URL', ''); + this.llmGatewayUrl = this.configService.get('OPENCLAW_LLM_GATEWAY_URL', 'http://154.84.135.121:3008'); + this.openclawLlmGatewayKey = this.configService.get('OPENCLAW_LLM_GATEWAY_KEY', ''); } // ── Public API ──────────────────────────────────────────────────────────── @@ -182,10 +190,15 @@ export class AgentInstanceDeployService { instance.openclawToken = encrypted; instance.openclawTokenIv = iv; + // Use the iConsulting LLM gateway key for OpenClaw. + // OpenClaw MUST NOT call api.anthropic.com directly — it goes through the gateway. + const effectiveApiKey = this.openclawLlmGatewayKey || claudeApiKey; + const envParts = [ `-e OPENCLAW_GATEWAY_TOKEN=${token}`, `-e IT0_INSTANCE_ID=${instance.id}`, - `-e CLAUDE_API_KEY=${claudeApiKey}`, + `-e CLAUDE_API_KEY=${effectiveApiKey}`, + `-e ANTHROPIC_BASE_URL=${this.llmGatewayUrl}`, `-e IT0_AGENT_SERVICE_URL=${this.agentServicePublicUrl}`, ]; if (dingTalkClientId && dingTalkClientSecret) { @@ -229,6 +242,22 @@ export class AgentInstanceDeployService { await this.sshExec(sshCreds, cmd); + // Wait for container to start, then patch models.generated.js to route all + // Anthropic API calls through the iConsulting LLM gateway instead of api.anthropic.com. + await new Promise(r => setTimeout(r, 3000)); + await this.sshExec(sshCreds, + `docker exec -u root ${instance.containerName} ` + + `find /app/openclaw/node_modules -name "models.generated.js" ` + + `-exec sed -i 's|https://api.anthropic.com|${this.llmGatewayUrl}|g' {} \\; 2>/dev/null || true`, + ); + + // Fix missing AGENTS.md template: create symlink at expected path if not already present. + await this.sshExec(sshCreds, + `docker exec -u root ${instance.containerName} bash -c ` + + `'TMPL=$(find /app/openclaw/node_modules -path "*/openclaw/docs/reference/templates" -type d 2>/dev/null | head -1); ` + + `[ -n "$TMPL" ] && mkdir -p /app/openclaw/docs/reference && ln -sfn "$TMPL" /app/openclaw/docs/reference/templates || true'`, + ); + instance.status = 'running'; }