diff --git a/heicode/VERSION b/heicode/VERSION index 04e0d3f1..f689e8c1 100644 --- a/heicode/VERSION +++ b/heicode/VERSION @@ -1 +1 @@ -1.4.17 +1.4.18 diff --git a/heicode/controller/agnet_callback.go b/heicode/controller/agnet_callback.go index 7d522ed6..b5d71f15 100644 --- a/heicode/controller/agnet_callback.go +++ b/heicode/controller/agnet_callback.go @@ -164,13 +164,14 @@ func agnetCallbackDeploymentContext(deploymentID string, swarmID string) (agnetD return record, true } } - if swarmID == "" { + runtimeID := firstNonEmpty(swarmID, deploymentID) + if runtimeID == "" { return agnetDeploymentRecord{}, false } agnetMu.RLock() for _, record := range agnetDeployments { - if strings.TrimSpace(record.RuntimeSwarmID) == swarmID { + if strings.TrimSpace(record.RuntimeSwarmID) == runtimeID || strings.TrimSpace(record.RuntimeDeploymentID) == runtimeID { agnetMu.RUnlock() return record, true } @@ -181,7 +182,7 @@ func agnetCallbackDeploymentContext(deploymentID string, swarmID string) (agnetD return agnetDeploymentRecord{}, false } var row model.AgnetDeployment - if err := model.DB.Where("runtime_swarm_id = ?", swarmID).First(&row).Error; err != nil { + if err := model.DB.Where("runtime_swarm_id = ? OR runtime_deployment_id = ?", runtimeID, runtimeID).First(&row).Error; err != nil { return agnetDeploymentRecord{}, false } record, err := agnetDeploymentModelToRecord(row) @@ -553,9 +554,14 @@ func AgnetReceiveSwarmEventCallback(c *gin.Context) { return } - record, _ := agnetCallbackDeploymentContext(strings.TrimSpace(payload.DeploymentID), strings.TrimSpace(payload.SwarmID)) - if strings.TrimSpace(payload.DeploymentID) == "" && strings.TrimSpace(record.DeploymentID) != "" { + incomingDeploymentID := strings.TrimSpace(payload.DeploymentID) + incomingSwarmID := strings.TrimSpace(payload.SwarmID) + record, _ := agnetCallbackDeploymentContext(incomingDeploymentID, incomingSwarmID) + if strings.TrimSpace(record.DeploymentID) != "" { payload.DeploymentID = record.DeploymentID + if strings.TrimSpace(payload.SwarmID) == "" { + payload.SwarmID = firstNonEmpty(record.RuntimeSwarmID, record.RuntimeDeploymentID, incomingDeploymentID) + } } if payload.CorrelationID == "" { payload.CorrelationID = record.Plan.Metadata.CorrelationID diff --git a/heicode/controller/agnet_control_plane.go b/heicode/controller/agnet_control_plane.go index c9183653..94d212cc 100644 --- a/heicode/controller/agnet_control_plane.go +++ b/heicode/controller/agnet_control_plane.go @@ -48,10 +48,13 @@ type agnetUserContext struct { } type agnetBillingContext struct { - Provider string `json:"provider"` - NewAPIUserRef string `json:"newapi_user_ref"` - NewAPIGroup string `json:"newapi_group"` - QuotaRef string `json:"quota_ref"` + Provider string `json:"provider"` + NewAPIUserRef string `json:"newapi_user_ref"` + NewAPIGroup string `json:"newapi_group"` + QuotaRef string `json:"quota_ref"` + DefaultModelID string `json:"default_model_id"` + AllowedModelIDs []string `json:"allowed_model_ids"` + SecretRef string `json:"secret_ref"` } type agnetAgileContext struct { diff --git a/heicode/controller/agnet_control_plane_test.go b/heicode/controller/agnet_control_plane_test.go index 767a9379..0d7bcb93 100644 --- a/heicode/controller/agnet_control_plane_test.go +++ b/heicode/controller/agnet_control_plane_test.go @@ -1154,6 +1154,70 @@ func TestAgnetCallbackUsesSwarmIDFallbackAndCreatesApproval(t *testing.T) { require.Equal(t, "azkv://heicode-kv.vault.azure.net/secrets/repo-main", approval.SecretRef) } +func TestAgnetCallbackUsesRuntimeDeploymentIDFallback(t *testing.T) { + db := setupAgnetControlPlaneTestDB(t) + resetAgnetControlPlaneState(t) + t.Setenv("AGNET_CALLBACK_TOKEN", "callback-token") + + plan := baseAgnetResourceGrantPlan() + plan.UserContext.UserID = "7" + plan.BillingContext.DefaultModelID = "model-runtime-smoke" + plan.BillingContext.AllowedModelIDs = []string{"model-runtime-smoke", "model-runtime-fallback"} + plan.BillingContext.SecretRef = "azkv://heicode-kv.vault.azure.net/secrets/model-gateway-key" + for idx := range plan.Agents[0].ResourceGrants { + plan.Agents[0].ResourceGrants[idx].UserID = "7" + } + createRecorder, createEnvelope := postAgnetCreateUserDeployment(t, 7, plan) + require.True(t, createEnvelope.Success) + var createBody map[string]any + require.NoError(t, common.Unmarshal(createRecorder.Body.Bytes(), &createBody)) + deploymentID := createBody["data"].(map[string]any)["deployment_id"].(string) + runtimeID := "swm-runtime-deployment-only-1" + require.NoError(t, db.Model(&model.AgnetDeployment{}). + Where("deployment_id = ?", deploymentID). + Updates(map[string]any{ + "runtime_deployment_id": runtimeID, + "runtime_swarm_id": runtimeID, + }).Error) + resetAgnetControlPlaneState(t) + + body := `{ + "event_id":"evt-runtime-deployment-fallback", + "event_type":"timeline.updated", + "deployment_id":"swm-runtime-deployment-only-1", + "agent_instance_id":"agi-backend-runtime", + "occurred_at":"2026-05-28T12:30:00Z", + "payload":{ + "title":"Runtime accepted", + "summary":"Agent Manager returned a runtime deployment id only", + "stage":"testing", + "checkpoint":"runtime_accepted" + } + }` + + recorder := httptest.NewRecorder() + ctx, _ := gin.CreateTestContext(recorder) + ctx.Request = httptest.NewRequest(http.MethodPost, "/api/agnet/callbacks/swarm-events", strings.NewReader(body)) + ctx.Request.Header.Set("Content-Type", "application/json") + ctx.Request.Header.Set("X-Agnet-Service-Token", "callback-token") + AgnetReceiveSwarmEventCallback(ctx) + require.Equal(t, http.StatusOK, recorder.Code) + require.Contains(t, recorder.Body.String(), `"success":true`) + require.Contains(t, recorder.Body.String(), `"deployment_id":"`+deploymentID+`"`) + + var callback model.AgnetCallbackEvent + require.NoError(t, db.Where("event_id = ?", "evt-runtime-deployment-fallback").First(&callback).Error) + require.Equal(t, deploymentID, callback.DeploymentID) + require.Equal(t, runtimeID, callback.SwarmID) + require.Equal(t, "7", callback.UserID) + + var stored model.AgnetDeployment + require.NoError(t, db.Where("deployment_id = ?", deploymentID).First(&stored).Error) + require.Contains(t, stored.PlanJSON, `"default_model_id":"model-runtime-smoke"`) + require.Contains(t, stored.PlanJSON, `"allowed_model_ids":["model-runtime-smoke","model-runtime-fallback"]`) + require.Contains(t, stored.PlanJSON, `"secret_ref":"azkv://heicode-kv.vault.azure.net/secrets/model-gateway-key"`) +} + func TestAgnetCallbackRejectsPlaintextSecretsAndMissingToken(t *testing.T) { setupAgnetControlPlaneTestDB(t) resetAgnetControlPlaneState(t)